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

782 views Asked by At

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});
2

There are 2 answers

3
Paalar On BEST ANSWER

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')}
/>
3
David Barker On

You could compose each event handler from a single method, I'm not sure if there would be render overheads by doing this as React wouldn't be able to keep track of the event handlers, if it does you could always assign the result to variables in componentDidMount.

private setComponentState(key) {
    const self = this;
    return (e) => {
        self.setState(prevState => ({
            projectInformationDetails: {
                ...prevState.projectInformationDetails,
                [key]: e.target.value,
            }
        }))
    };
}

Then call this function each time you want to use it with the key.

<TextField
    label={strings.ProgramNameLabel}
    defaultValue={this.state.projectInformationDetails.programName}
    onChange={this.setComponentState('programName')}
/>

<TextField
    label={strings.ProjectNameLabel}
    defaultValue={this.state.projectInformationDetails.projectName}
    onChange={this.setComponentState('projectName')}
/>