2

I created an update page in my react app.

To sum up; when I click a div it shows the data in input fields. For example, when I click the first field, type in there something, and click another div, the changes I made disappear. I want that if I make a change in there, It should stay there before save it. How can I do that?

<div className="detailsPage-panel-right">
                  {
                    this.state.activeFields?.fields?.map(field => {
                      const config = this.config.fields.find(fieldConfig =>
                        fieldConfig.key === field.key)
                      const inputConfig = {
                        type: config?.dataType.type,
                        id: config?.key,
                        label: config?.displayName,
                        required: false,
                        autofocus: false,
                        value: field.value
                      };
                      const inputBindings: ITextInputBindings = {}
                      return (
                        <div key={`${this.state.activeFields.key}-${field.key}`}>
                          <TextInput config={inputConfig} bindings={inputBindings}></TextInput>
                        </div>
                      )
                    })
                  }
</div>

Text input component

import "./text-field.scss";
import { Form } from "react-bootstrap";
import { Component } from "../../utils/stateless-component";

export interface ITextInputBindings {
}

export interface ITextInputConfig {
  type: "text" | "dateTime" | "enumeration" | "guid" | undefined,
  id: string | undefined,
  label: string | undefined,
  placeholder?: string,
  required: boolean,
  autofocus?: boolean,
  value?: string
}

class TextInput extends Component<ITextInputConfig,ITextInputBindings> {
  render() {
    return (
      <div className="textInput">
        <Form.Group className="mb-3 textInput-group">
          <Form.Label htmlFor={this.config.id}>{this.config.label}</Form.Label>
          <Form.Control type={this.config.type}
            placeholder={this.config.placeholder}
            required={this.config.required}
            id={this.config.id}
            autoFocus={this.config.autofocus}
            defaultValue={this.config.value} />
        </Form.Group>

      </div>
    );
  }
}

export default TextInput; 

I think I should use onChange method but I don't know how.

1 Answer 1

1

key prop

Remember to check re-render when your activeFields.field changes, because you had set the key in your TextInput.
This will result in the TextInput component be unmount and create a new one

       // 📌 check this state. Do not mutate to prevent re-render 
       this.state.activeFields?.fields?.map(field => {
                      const config = this.config.fields.find(fieldConfig =>
                        fieldConfig.key === field.key)
                      const inputConfig = {
                        type: config?.dataType.type,
                        id: config?.key,
                        label: config?.displayName,
                        required: false,
                        autofocus: false,
                        value: field.value
                      };
                      const inputBindings: ITextInputBindings = {}
                      return (
                        // 📌 if key be mutated from state, it will create a new component intead of old one
                        <div key={`${this.state.activeFields.key}-${field.key}`}>
                          <TextInput config={inputConfig} bindings={inputBindings}></TextInput>
                        </div>
                      )
                    })

Save Input value

And if you want to save the input value in TextInput, it is depends on which component you want to save the input value by state.

Save in the child component (In your case the TextInput)

Add a onChange event and a state in your TextInput component

Then add props because you are give props to it.

like this example edited from your code (maybe can not run, but the concept should work)

class TextInput extends Component<ITextInputConfig,ITextInputBindings> {
  constructor(props) {
    super(props);
    this.state = { ...this.props }
  }

  // set state 
  const handleChange = (e) => {
    this.setState({...this.state, 
    config: { ...this.state.config, value: e.target.value } 
    })
  }

  render() {
     return (
      <div className="textInput">
        <Form.Group className="mb-3 textInput-group">
          <Form.Label htmlFor={this.config.id}>{this.config.label}</Form.Label>
          <Form.Control type={this.config.type}
            placeholder={this.config.placeholder}
            required={this.config.required}
            id={this.config.id}
            autoFocus={this.config.autofocus}
            defaultValue={this.config.value}
            // 📌 add onChange event on Form.Control
            onChange={handleChange}
             />
        </Form.Group>
      </div>
    );
  }
}

Save in parent component

And if you need control or save state changes from parent component
add a state and a changeState function in your parent component, and give changeState to TextInput's props and let the changeState prop mutate parent's value in child's input onChange event

example:

class ParentComponent extends React.Component {
   constructor(props) {
    super(props);
    this.state = { inputValue: undefined }
  }

  const handleChange = (e) =>{
    if(e.target)
      this.setState({...this.state, inputValue: e.target.value});
  }

  render(){
    return (
      <div className="detailsPage-panel-right">
                  {
                    this.state.activeFields?.fields?.map(field => {
                      const config = 
                        this.config.fields.find(fieldConfig =>
                        fieldConfig.key === field.key)
                        const inputConfig = {
                        type: config?.dataType.type,
                        id: config?.key,
                        label: config?.displayName,
                        required: false,
                        autofocus: false,
                        value: field.value
                        };
                        const inputBindings: ITextInputBindings = {}
                        return (
                        <div key= 
                       {`${this.state.activeFields.key}-${field.key}`}
                        >
                        <TextInput 
                        config={inputConfig} 
                        bindings={inputBindings}
                        onChange={handleChange}>
                        </TextInput>
                        </div>
                      )
                    })
                  }
         </div>
    )
  }  
}

// TextInput
class TextInput extends Component<ITextInputConfig,ITextInputBindings> {
 constructor(props) {
    super(props);
    this.state = { ...this.props }
  }

  const handleChange = (e) => {
    this.props.onChange(e);
  }

  render() {
    return (
      <div className="textInput">
        <Form.Group className="mb-3 textInput-group">
          <Form.Label htmlFor={this.config.id}>{this.config.label} </Form.Label>
          <Form.Control 
            type={this.config.type}
            placeholder={this.config.placeholder}
            required={this.config.required}
            id={this.config.id}
            autoFocus={this.config.autofocus}
            defaultValue={this.config.value} 
            onChange={handleChange}/>
        </Form.Group>

      </div>
    );
  }
}
Code snippet example

a example that how child mutate parent's value, and how does the component destroyed when key changes. (written by functional component)

<script src="https://cdnjs.cloudflare.com/ajax/libs/react/18.2.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/18.2.0/umd/react-dom.production.min.js"></script>
<script src="https://unpkg.com/babel-standalone@6/babel.min.js"></script>

    <div id="root"></div>

    <script type="text/babel">
      function App () {
        const [keys, setKeys] = React.useState([1, 2]);
        const [inputValue, setInputValue] = React.useState(``);
        const [inputValue2, setInputValue2] = React.useState(``);

        const handleKeys = () =>{
          let temp = [...keys];
          temp[0] = temp[0] + 2;
          temp[1] = temp[1] + 2;
          setKeys([...temp])
        }

        return <div>
          <div><button>Click this still remain the changes you had made</button></div>
          <div><button onClick={handleKeys}>Click this to change keys, and will refresh the 'Number' prefix input component</button></div>
          <br />
          {
            keys.map((key)=>{ 
                if (key % 2 === 0) {
                  return <div key={key}>Number {key}: <Child setInputValue={setInputValue2}></Child></div> 
                }
                else {
                  return <div key={key}>Number {key}: <Child setInputValue={setInputValue}></Child></div>  
                }
              })
          }
          
          <br />
          <div>child components that do not have key</div>
          <div>First Child's Input: <Child setInputValue={setInputValue}></Child></div>
          <div>Second Child's Input: <Child setInputValue={setInputValue2}></Child></div>
          <br />
          <div>inputValue(in parent from first child): {inputValue}</div>
          <div>inputValue2(in parent from second child): {inputValue2}</div>
        </div>
      }
      
      function Child ({ setInputValue }) {
        const handleChange = (e) => {
          if(setInputValue)
            setInputValue(e.target.value);
        }
      
      return <input onChange={handleChange}></input>
      }
    </script>

    <script type="text/babel">
        ReactDOM.render(
            <App></App>
            , document.getElementById("root"));
    </script>

Dynamically mutate and save input value by state

I guess you need save value dynamically by this.state.activeFields?.fields.
Create a state object for recording your active input value. And add a handleChange function which can change value by e.target.id

// In your TextInput's parent
 constructor(props) {
    super(props);
    this.state = { inputValues: {} }
  }

const handleChange = (e)=>{
  const changeField = this.state.activeFields?.fields.find(x=>x.key === e.target.key);
  if(changeField) {
    this.setState({...this.state.inputValues, changeField.key: e.target.value})
  }
}

this.state.activeFields?.fields?.map( (field) => {
return (
<TextInput 
  config={inputConfig} 
  bindings={inputBindings}
  // add onChange event
  onChange={handleChange}
>
</TextInput>
)
})
more refernece:

Lifting State Up


Other

According to react-bootstrap's Form.Control API doc, should use the value intead of defaultValue

Sign up to request clarification or add additional context in comments.

2 Comments

When I use value instead of value, the input field turns readonly. I couldn't solve this problem. Do you have any idea?
Sorry, I dont understand the value instead of value means? Maybe a updated question and code can help me understand the situation

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.