React Hooks: useState. Rules and tips for component state manipulation.

  • figure out the way useState works
  • logical conditions in render
  • ways to perform an action after changing the state
  • use prevState in a function to change the state

In this article we’ll figure out what the useState is and the rules to follow for its successful use.

How the useSate works?

useState is a React hook for managing components rendering. The hook can be used inside each component which needs to be updated and re-rendered according to its state changes.

useState hook is a method that accepts just one argument which is an initial state.

This argument could be:

  • A function that returns initial state (using for a more complex way of its defining).
  • Some initial state.

useState returns an array of two elements:

  • Current state.
  • Function for updating current state.

Using useState hook we're able to divide logic into several states and each of it will be applied for proper element inside the render

Keep in mind that hooks have some particular rules of their use:

  • Hooks are available for calling only inside the body of a function component. Once you would try to call it inside another function, condition or loop inside the component, you’ll definitely get an error. To avoid getting an invalid hook call, useState should be initialized at the top of your component. Moreover, this makes it convenient for the use.
  • State value must be updated with given method from useState hook but not by direct reassignment (mutating).

WRONG ourState = newValue.
RIGHT changeStateValue(newValue).

Let’s take a look at the basic use of state when performing conditional rendering:


  import React from 'react'

  const StateExample = () => {
    const [isElementVisible, changeElementVisibility] = React.useState(true)
  
    const handleHideElement = () => {
      changeElementVisibility(false)
    }
  
    return (
      <div>
        {isElementVisible && <div>Element that should be hidden on button click</div>}
  
        <button onClick={handleHideElement}>Hilde element above</button>
      </div>
    )
  }
  
  export default StateExample
  

Let's see what we’ve done: when doing destructuring assignment, the isElementVisible constant was assigned the initial state, and the changeElementVisibility constant got a function for updating the state.

How constants and functions should be properly named? The right name should briefly describe the purpose and the role that the constant or function will play in a particular component. It might seem too obvious at the first sight. But this recommendation is often neglected by the developers. First things first, by doing this you ensure a rapid navigation across your entire project. In case the whole team is engaged for the project, the given name defines how quickly other developers can navigate through your code and continue working with it. Think of other people and be one step ahead. To get the right idea, try to always visualize yourself being completely new to this project or working in the pair.

To update component state it is vital to know that:

You SHOULD AVOID to use anonymous function inside onClick and make changes directly out of there. The anonymous function will be initialized anew each time your page will render. Its affect the entire application performance.

You SHOULD USE a predefined function to describe the state change, in our case handleHideElement. For example, if an element would not be hidden only by clicking on one button, but possibly somewhere else - we can easily reuse the handleHideElement function, reducing the amount of code and improving readability.

In our example, by clicking on the button, handleHideElement is called and the value is passed to changeElementVisibility. Thus, the state changes and the component is being re-rendered, hiding therefore the element we need.


  import React from 'react'

  const StateExample = () => {
    const [isElementVisible, changeElementVisibility] = React.useState(true)
    
    // Correct usage
    const handleHideElement = () => {
      changeElementVisibility(false)
    }
  
    return (
      <div>
        {isElementVisible && <div>Element that should be hidden on button click</div>}
        {/* Don't do that */}
        <button onClick={() => changeElementVisibility(false)}>Hilde element above</button>
        {/* Correct usage */}
        <button onClick={handleHideElement}>Hilde element above</button>
      </div>
    )
  }
  
  export default StateExample
  

By the way, when performing conditional rendering, it is correct to use a construction with a logical AND (&&). If the left side of the condition (isElementVisible) returns false, React will ignore this element in the render. In case the left side returns true - the construct returns the element that React will draw in the browser.

What if you want to perform an action after changing state?

Working with classes you’re able to pass a callback function as the second argument to the setState method and this callback function would be fired as soon as state has been changed. However, using the useState hook we’re not able to do that. To reach the goal, you should apply the useEffectmethod by adding the dependency isElementVisible as the second argument. Thus, the function will be called each time the component state changes.

Be aware that useEffect method will be called at the very first component rendering and make sure you take this into account when building logic.


  import React from 'react'

  const StateExample = () => {
    const [isElementVisible, changeElementVisibility] = React.useState(true)
  
    React.useEffect(() => {
      // function will be called on each "isElementVisible" change
    }, [isElementVisible])
  
    const handleHideElement = () => {
      changeElementVisibility(false)
    }
  
    return (
      <div>
        {isElementVisible && <div>Element that should be hidden on button click</div>}
  
        <button onClick={handleHideElement}>Hilde element above</button>
      </div>
    )
  }
  
  export default StateExample
  

Using prevState in a function to change state

As you can see in the example above, the function that updates the state takes an updated value as an argument. Yet this is not all. It can also take a function that returns an updated value. The argument of this function is the current state until the next update takes place.

The example below shows the function used as an argument to update the state of input fields.


  import React from 'react'

  // local variables
  const FIELD_NAMES = {
    FIRST_NAME: 'firstName',
    LAST_NAME: 'lastName'
  }
  
  const StateExample = () => {
    const [formValues, changeFormValues] = React.useState({
      [FIELD_NAMES.FIRST_NAME]: '',
      [FIELD_NAMES.LAST_NAME]: ''
    })
  
    const handleInputChange = fieldName => e => {
      const fieldValue = e.target.value
  
      changeFormValues(prevState => ({
        ...prevState,
        [fieldName]: fieldValue
      }))
    }
  
    return (
      <div>
        <input
          type='text'
          onChange={handleInputChange(FIELD_NAMES.FIRST_NAME)}
          name={FIELD_NAMES.FIRST_NAME}
          value={formValues[FIELD_NAMES.FIRST_NAME]}
        />
        <input
          type='text'
          onChange={handleInputChange(FIELD_NAMES.LAST_NAME)}
          name={FIELD_NAMES.LAST_NAME}
          value={formValues[FIELD_NAMES.LAST_NAME]}
        />
      </div>
    )
  }
  
  export default StateExample
  

To update the state of a specific element by its name, use a higher order function and pass the names of the inputs across the closure before the event handler in the input is called. An anonymous function should be passed to changeFormValues method for changing input's state. This anonymous function returns a NEW state object based on the PREVIOUS one, but with an updated field.

You might have noticed that I’ve created the FIELD_NAMES variable and defined an object for it in which I described the names of the inputs to use them inside the component. Try to remember and make use of this approach. If you’ll have to change the name of the input, this could be easily done it once and in one place. I guess you can admit that this is much more convenient than changing the name in each input separately. This approach can be defined as ENUM.

Now having a full set of tools and using the state of the components correctly, you can easily implement complex logic for your application.