React hooks introduction - useState

Classes can be a large barrier to learn React, according to the React team. They don’t minify very well and they make hot reloading unreliable. With React hooks, you don’t need to convert a function component into a class component. You can use state and lifecycle methods in the function component.

Okay, but what are hooks really?

They are functions that let you “hook” into the React internal state and lifecycle features from function components.

Great! How do I do that?

First, update your React and React DOM dependency in your project. Hooks were released on React 16.8.0.

npm install --save react@^16.8.0 react-dom@^16.8.0

Now, let’s take a look at the useState hook. Here is the demo:

import React from 'react';

require('./style.css');

import ReactDOM from 'react-dom';
import SimpleForm from './app.js';


const rootElement = document.getElementById("root");
ReactDOM.render(
  <SimpleForm firstName="JOHN" lastName="Edward" age={30} />,
  rootElement
);

useState hook

We need to have a simple class component with state for comparison. The easiest example I can imagine is of an input form:

class NameForm extends React.Component {
  constructor(props) {
    super(props);
    this.state = { value: '' };
  }

  handleChange = event => {
    this.setState({ value: event.target.value });
  };

  render() {
    return (
      <form>
        <label>
          Name:
          <input
            type="text"
            value={this.state.value}
            onChange={this.handleChange}
          />
        </label>
        <input type="submit" value="Submit" />
      </form>
    );
  }
}

Now, let’s rewrite it using the useState hook. We’ll import it from the react package so that we don’t have to write React.useState all the time.

import React, { useState } from 'react';

Now, let’s change our NameForm into a function component:

function NameForm(props) {}

The useState hook takes one argument, which is the initial state, and returns two values: the current state and a function that can be used to update the state. You can replace the state initialization in the constructor:

this.state = { value: '' };

into this:

function NameForm(props) {
  const [value, setValue] = useState('');
}

Notice the use of square brackets when a state variable is declared. This is the ES6 “array destructuring” syntax, and it means we’re assigning the first value returned by useState to value and the second value to setValue.

This means we have a state named value , which we can update by calling on setValue function. Let’s use it on our render method:

function NameForm(props) {
  const [value, setValue] = useState('');

  return (
    <form>
      <label>
        Name:
        <input
          type="text"
          value={value}
          onChange={event => setValue(event.target.value)}
        />
      </label>
      <input type="submit" value="Submit" />
    </form>
  );
}

onChange props no longer call a handleChange method. Instead, we have an arrow function that will call the setValue function, which updates our state. Oh, and unlike this.setState in the class component, when updating a state variable using hooks, always replace it instead of merging it.

Having multiple states?

Then, call on useState as many times as you need.

function SimpleForm(props) {
  const [firstName, setFirstName] = useState('');
  const [lastName, setLastName] = useState('');
  const [age, setAge] = useState('');

  return (
    <form>
      <label>
        First Name:
        <input
          type="text"
          value={firstName}
          onChange={event => setFirstName(event.target.value)}
        />
      </label>
      <label>
        Last Name:
        <input
          type="text"
          value={lastName}
          onChange={event => setLastName(event.target.value)}
        />
      </label>
      <label>
        Age:
        <input
          type="number"
          value={age}
          onChange={event => setAge(event.target.value)}
        />
      </label>
      <input type="submit" value="Submit" />
    </form>
  );
}

And that’s it for the useState hook, really!

Setting initial state from props

Oh, it’s so easy!

function SimpleForm(props) {
  const [firstName, setFirstName] = useState(props.firstName);
  const [lastName, setLastName] = useState(props.lastName);
  const [age, setAge] = useState(props.age);

  //...
}

ReactDOM.render(
  <SimpleForm firstName="JOHN" lastName="Edward" age={30} />,
  document.getElementById('root')
);

Can I use the object data type in a state?

Sure! Just as class state can accept an object or array, useState can have them as well. But to make it work, let’s add name props to your input elements. We also use spread properties to update our state.

function SimpleForm(props) {
  //create object state
  const [form, setForm] = useState({
    FirstName: '',
    LastName: '',
    age: '',
  });

  const handleChange = event => {
    // use spread operator
    setForm({
      ...form,
      [event.target.name]: event.target.value,
    });
  };

  return (
    <form>
      <label>
        First Name:
        <input
          type="text"
          name="firstName"
          value={form.firstName}
          onChange={handleChange}
        />
      </label>
      <label>
        Last Name:
        <input
          type="text"
          name="lastName"
          value={form.lastName}
          onChange={handleChange}
        />
      </label>
      <label>
        Age:
        <input
          type="number"
          name="age"
          value={form.age}
          onChange={handleChange}
        />
      </label>
      <input type="submit" value="Submit" />
    </form>
  );
}

What about arrays?

This one’s a bit unusual, but yes, just like in class state, we’ll use concat for updating the array.

function SampleArrayState(props) {
  //create array state
  const [nameArray, setNameArray] = useState(['Carl']);

  const updateArray = () => {
    setNameArray(nameArray.concat('John'));
  };

  return (
    <React.Fragment>
      <button onClick={updateArray}>Click me!</button>
      <div>{nameArray.toString()}</div>
    </React.Fragment>
  );
}

And Boolean?

Got you covered here:

function SampleBooleanState(props) {
  const [show, setShow] = useState(true);
  const visibility = show ? 'visible' : 'hidden';

  return (
    <React.Fragment>
      <h1 visibility }}>useState Hook is awesome!</h1>
      <button
        onClick={() => { setShow(!show) }}
      >{`${show ? 'Hide' : 'Show'} the Header!`}</button>
    </React.Fragment>
  );
}

The rules of hooks

The important thing to remember is that hooks don’t work in a class component; they are made for function components.

  1. Don’t call hooks from inside nested function, loops or conditionals.
  2. Don’t call hooks from a regular JavaScript function.
  3. This:

Conclusion

The useState hook enables function components to access React’s internal state and update it. The state can be any data type: string, number, boolean, array, or object. useState accepts one argument (the initial data) and it returns an array of two values: the current state value and the function/method that can be used to update the state.

There are more hooks than just useState, but let’s cover each hook in its own single post.

Here’s a list of all built-in hooks (yes, you can write a custom hook!) available as of this writing. I’ll be covering them in the next posts!

Also, I’m writing a book on learning React properly without the stress. You might wanna check it out here.

Until next time!

Free Resources

Attributions:
  1. undefined by undefined