DEV Community

Cover image for Fast & easy... React states management in one function
Fabio Russo
Fabio Russo

Posted on

Fast & easy... React states management in one function

Don't repeat the code...

In React, like anywhere else in your code, you must never repeat yourself unless it is strictly necessary (almost never).

Probably what you're going to read, it's easy stuff, but reading some code online, I thought about dealing with the subject ... I "apologize" to the experts for the banality.

Example...

We have to manage a text input tag, and make sure to memorize the value entered by the user, and show It somewhere, till it's changed again!

How do we do it in React?

We insert an onChange that updates the state of the component to the last input value ... and then we pass the value of the state to the input tag, or to any other tag on the page, to show the last input value.

To the code!


//we've a class... with a state defined

class App extends Component {
  constructor(props){
    super(props)
    this.state={
      myFirstState:"Labrador"
    }

//we're going to manage the changes, and setState() equal to the user input

   valueChange = (e) => {
      this.setState({myFirstState : e.target.value});
    }

//rendering time...we've an Input tag, with the state as value 
//and the function, calling to onChange

render() {
    return (
        <div className="just a class">
          <input placeholder="insertValue" value={this.state.myFirstState} 
                 onChange= {this.valueChange(e)}/>
        </div>
       )
   }
}

Enter fullscreen mode Exit fullscreen mode

That's basic stuff.
And It's just a way to go for It.

But what If we've more than one <input> ?
Not two... not three... we've more than ten <input> doing the same stuff?
(it is an exaggeration useful for the post)

As you can see our handleChange function, changes a specific state in It's setState() ... if we use the same function for other <input> we will change the value of myFirstState everytime.
(And trust me... I have seen people use numerous functions doing the same stuff, like these.)

My idea is...


//we've a class... with a more states defined

class App extends Component {
  constructor(props){
    super(props)
    this.state={
      myFirstState:"Labrador",
      mySecondState:"Akita"
    }

//we're going to manage the changes, and setState() equal 
//to the user input... for all the possible "keys" in the state object

      valueChange = (key) => {
        return function (e) {
          var obj= {};
          state[key] : e.target.value;
          this.setState(obj);
        }.bind(this);
       }

//rendering time...we've more Input tags, with the states as values and one
//function, calling onChange... we're passing the "key" as argument.

render() {
    return (
        <div className="just a class">
          <input placeholder="insertValue" value={this.state.myFirstState} 
                 onChange= {this.valueChange("myFirstState")}/>
          <input placeholder="insertValue" value={this.state.mySecondState} 
                 onChange= {this.valueChange("mySecondState")}/>
        </div>
       )
   }
}

Enter fullscreen mode Exit fullscreen mode

With that, we're calling the method setState() passing an object where the key is the state we want to change and the value is the user input!.
We're also binding this or we're going to receive an error.

(Keep in mind... setState() triggers the render)

I hope I've been useful ... I'm looking for suggestions to manage this kind of case in more efficient ways!

hug
Cya adventurers

Latest comments (19)

Collapse
 
connect2amc profile image
Amit Chauhan
valueChange = (e) => {
    this.setState({
    [e.target.name]: e.target.value
    })
}

const {myFirstState, mySecondState} =this.state;

<input 
placeholder="insertValue" 
name="myFirstState" 
value={myFirstState}
onChange= {this.valueChange}/>

<input 
placeholder="insertValue" 
name="mySecondState" 
value={mySecondState}
onChange= {this.valueChange}/>
Collapse
 
mgtitimoli profile image
Mauro Gabriel Titimoli

You can try this alternative instead that doesn't require you to add any instance method (methods declared using arrow fns).

import React from "react";

// you can move this fn to an independent module and import it here
const setState = (component, updateState) => params =>
  component.setState(updateState(params));

const setName = event => prevState => ({
  ...prevState,
  name: e.target.value
}); 

export default class App extends Component {
  state = {
    name: ""
  };

  render() {
    return (
      <div>
        <input
          placeholder="insert your name" value={this.state.name} 
          onChange= {setState(this, setName)} />
      </div>
    );
  }
};
Collapse
 
acostalima profile image
André Costa Lima • Edited

The approach demonstrated in this post to manage form data is known as controlled components, which is traditional "reactish" way to handle input value changes. Since the post is intended for beginners, maybe you could complement it adding a note to mention this for those who'd like to read further on the subject. 🙂

More details can be found here: reactjs.org/docs/uncontrolled-comp...

Collapse
 
patroza profile image
Patrick Roza • Edited

You're creating a new function for every input you manage for every render pass. This is an anti pattern especially on big state trees. Especially because it will cause re-renders of child components unnecessarily (as the onChange prop for each element changes every render pass. You may want to cache the created functions, or take a different approach

Collapse
 
genta profile image
Fabio Russo

But.. have you seen all the post? _^

Collapse
 
patroza profile image
Patrick Roza

I think so, what specifically did I overlook? Supplying a better alternative?

Thread Thread
 
genta profile image
Fabio Russo • Edited

There’s a warning about rerendering... and me asking for suggestions ✌🏻

Sorry if it’s not perfect, I’m here for learning too.

Thread Thread
 
patroza profile image
Patrick Roza • Edited

No worries, just wanted to chip in. Somehow dont see a note about rerender, I'm on mobile, perhaps some caching going on.

I like the alternative via name prop for input elements, or a custom data prop, as I wrote in the other comment thread.

Alternatively, I would still create a function property per onChange handler, but create and use a reusable function to limit the repetitiveness to the bare minimum.

Thread Thread
 
acostalima profile image
André Costa Lima • Edited

You can create a curried function and then memoize it. 😉 It should work! Have a look at Lodash's curry and memoize functions.

Collapse
 
maciejsimka profile image
Maciej Simka • Edited

using curried function is a good idea! it's very useful for handling multiple similar state changes.
another approach, if you're dealing with form elements like inputs etc. would be to use name attribute, something like this:

state = {
  firstName: '',
  message: '',
}

handleValueChange = e => {
  this.setState(() => ({ [e.target.name]: e.target.value }));
}

render() {
  return (
    <form>
      <input
        type="text"
        name="firstName"
        value={this.state.firstName}
        onChange={this.handleValueChange}
      />
      <input
        type="text"
        name="message"
        value={this.state.message}
        onChange={this.handleValueChange}
       />
    </form>
  )
}
Enter fullscreen mode Exit fullscreen mode

but of course it works only with certain html elements (<input>, <select>, <textarea> among them) and IIRC specification says name should be lowercase, although I don't think I've run into any problems with camelCase name so far.

Collapse
 
patroza profile image
Patrick Roza

I like this, how about leveraging a data- attribute instead of name?

Collapse
 
efleurine profile image
Emmanuel

In the valueChange function where did you find the state variable. May be it should be obj variable ?

Collapse
 
genta profile image
Fabio Russo

In the second example?

The state is the obj variable.
You can see I’m creating an obj and using key-value pairs in It.
Then I’m passing the object to setState().

Collapse
 
kayis profile image
K • Edited

you defining obj, but then you use state. in non-strict mode this leads to the creation of a global variable, in strict mode this leads to an error.

After that you use obj again, so the new value saved in state in state isn't even used.

Thread Thread
 
genta profile image
Fabio Russo

Understood!

Collapse
 
kayis profile image
K • Edited

Yeah, curried functions are perfect for this. The arrow syntax lends itself to this.

valueChange = (key) => {
  return function (e) {
    var obj= {};
    state[key] = e.target.value; //<-- this is a bug btw.
    this.setState(obj);
  }.bind(this);
}
Enter fullscreen mode Exit fullscreen mode

becomes

valueChange = key => e => this.setState(oldState => ({
  ...oldState,
  [key]: e.target.value
}))
Enter fullscreen mode Exit fullscreen mode
Collapse
 
patroza profile image
Patrick Roza

Or just valueChange = key => e => this.setState({[key]: e.target.value})

Collapse
 
alephnaught2tog profile image
Max Cerrina

Yesssssssssssssss

Collapse
 
genta profile image
Fabio Russo

Love It!