Search code examples
javascriptreactjstsx

Can I use one function for changing different states in React?


Can I pass the state and only use 1 function instead of calling a function for every state property?

Component state:

this.state =
{
  context: this.props.context,
  dataProvider: this.props.dataProvider,
  projectInformationDetails: {
    programName: "",
    projectName: "",
    projectStatus: "",
    projectStatusOptions: [],
    appStatus: "",
    appStatusOptions: [],
    governanceStatus: "",
    governanceStatusOptions: []
  },
};

Function for changing state when input changes:

private async setProgramName(e) {
    await this.setState(prevState => ({
        projectInformationDetails: {
            ...prevState.projectInformationDetails,
            programName: e.target.value,
        }
    }));

    this.props.updateProjectItem(this.state.projectInformationDetails);
}

private async setProjectName(e) {
   await this.setState(prevState => ({
      projectInformationDetails: {
         ...prevState.projectInformationDetails,
         projectName: e.target.value,
      }
   }));

   this.props.updateProjectItem(this.state.projectInformationDetails);
}
...

Text Component in render:

<TextField
    label={strings.ProgramNameLabel}
    defaultValue={this.state.projectInformationDetails.programName}
    onChange={this.setProgramName}
/>
<TextField
     label={strings.ProjectNameLabel}
     defaultValue={this.state.projectInformationDetails.projectName}
     onChange={this.setProjectName}
/>
...

So instead of using almost the same function for every state, I just want to use one single function:

func(state, value)

Maybe the code will be cleaner if I can access the property of projectInformationDetails without cloning everything back.

For example:

this.setState( {projectInformationDetails.programName: value});

Solution

  • If it is all input fields, you could add a name key to the input tag. It would look something like <input type="text" name="programName" value={programName} /> The name value would then correspond to key that you're trying to change in the state.

    With this you should be able to create a generic function to set the state value.

    const onInputChange = (e) => this.setState({
      ...this.state,
      projectInformationDetails: {
        ...this.state.projectInformationDetails,
        [e.target.name]: e.target.value,
      },
    });
    

    And use this function on the input field's onChange.

    I haven't used classes with React in a while, so there is probably some modification that needs to be done.

    Edit:

    You could incorporate part of @David Barker's answer by adding the value in the onChange function and using functions as first class citizens, by returning another function.

    const onChange = (property) => (e) => this.setState({
      ...this.state,
      projectInformationDetails: {
        ...this.state.projectInformationDetails,
        [property]: e.target.value,
      },
    });
    
    ...
    
    <TextField
        label={strings.ProjectNameLabel}
        defaultValue={this.state.projectInformationDetails.projectName}
        onChange={onChange('programName')}
    />