← Home

A thought about state in React

React is an amazing lib (some would call it a framework, I won’t digress about it though), and allow to use javascript in a way that will help to keep code simple and maintainable.

So, when writing a programs, we should keep some data around to maintain the state of our application. And React allow this with state.

In React, when we wanted to keep those states, we should always opt-in for class components and keep functional components for attachable functionality that wouldn’t keep states.

That changed though. In React 16.8, hooks were introduced to allow functional components to be able to hold state. It’s use is simple enough to allow it to be used right away:

import React, { useState } from 'react';

function MyFunctionalStatefullComponent() {

  // the useState will return a pair, 
  // the first element is the data holder
  // the second is the function to update this data holder
  const [count, setCount] = useState(0);

  return (
    ...
  );
}

Please, be aware that when using React Hooks, you should have a matching React Dom version, otherwise you will get the following error:

Invariant Violation
Hooks can only be called inside the body of a function component. (https://fb.me/react-invalid-hook-call)

Lets take a look in the following codes, starting with class component:

import React, { useState } from 'react'

class App extends React.Component {
  state = {
    count: 0,
    nome: "John"
  }

  increaseHandle = () => {
    var newCount = this.state.count + 1;
    this.setState({count: newCount});
    console.log(this.state);
  }

  render() {
    return (
      <div>
      <span>{this.state.count}</span>
      <button onClick={this.increaseHandle}>Increase</button>
    </div>
    )
  }
}

export default App;

Next, we will take a look in a functional component:

import React, { useState } from 'react'

function App() {
  const [data, setData] = useState({
    count: 0,
    name: "John"
  });

  const increaseHandle = () => {
    var newCount = data.count + 1;
    setData({count: newCount});
    console.log(data);
  };

  return (
    <div>
      <span>{data.count}</span>
      <button onClick={increaseHandle}>Increase</button>
    </div>
  );
};

export default App;

So, you may ask. What is the difference? And I would say, on the class component, it will automatically merge the new state with the old state, and the execution will be something like this:

Click Increase Button => {count: 0, nome: "John"}
Click Increase Button => {count: 1, nome: "John"}
Click Increase Button => {count: 2, nome: "John"}
...

That’s not what happens on the functional component using hooks, they differ and every state update with hooks will replace the old state, and the execution will be something like this:

Click Increase Button => {count: 0, nome: "John"}
Click Increase Button => {count: 1}
Click Increase Button => {count: 2}
...

That’s a small difference in result that have a huge impact in the application if you don’t pay attention.

We can fix this behavior using the javascript spread (…) operator and update the state completely, instead a partial update. We could change (in the functional component) the following code:

...

  const increaseHandle = () => {
    var newData = {
      ...data,
      count: data.count + 1
    };
    setData(newData);
    console.log(data);
  };

...

Taka a look at the …data, that is the spread operator, it will copy and expand the said object into a new one, and it will be possible update the new attributes that we want update and in our example that would be count: data.count + 1

With this little change our class component and functional component will have the same behavior.

You can give it a try at codesandbox.