DEV Community

Chris
Chris

Posted on • Updated on

An Overview on how to use State in React

In React, a virtual DOM is created to allow more efficient browser DOM manipulation and re-renderings. To allow React to know which components to update when a change in information occurs, it utilizes "state". State of a component is an object that holds information that may change over time and / or due to interactions by the user. This allows React to know when to re-render the component. And within a recent update to React, hooks are introduced to allow you to use state and other React features easier without writing a class (https://legacy.reactjs.org/docs/hooks-intro.html).

Before we begin, there are some import rules for hooks (like useState and others)

  • Only call hooks at the top level of a function component. Think of it similarly to the import statement
  • You can't call hooks inside loops or conditions.
  • Hooks can only be called from React Functions.

Let's say you have a button, and every time you click it, it increases a counter. In vanilla JS you would have to

  • create an event listener and attached to the button element
  • go into the DOM and access the element that contains the counter value
  • increment it
  • and then append the element

Let's do this instead using React. Let's write out our basic Component.

import React from 'react'

function CounterExample(){
    let counter = 0

    return(
        <div>
            <h1>You have clicked the button {counter} times so far!</h1>
            <button>Click me</button>
        </div>
    )
}

export default CounterExample
Enter fullscreen mode Exit fullscreen mode

First, we need to import useState from the react library.

import React, { useState } from 'react'
Enter fullscreen mode Exit fullscreen mode

Next, we need to call on useState, which the argument it accepts will be the initial value of your state variable.

const [counter, setCounter] = useState(0)
Enter fullscreen mode Exit fullscreen mode

There are a few things going on with the above line of code.

  • First, useState returns a 2-element array. The first element is the state variable with the initial argument you passed into the useState function. The second element is the setter function that sets the first element's value. You cannot assign a new value directly to the state variable. Instead, you use the setter function to set the value. This is how React knows when to re-render the component
  • Second, the above line uses JS destructuring. You can separate the elements of an array or properties of an object into separate variables all within 1 line. In the above case, the first element is the state variable and the second is the setter function

Important note, it is a common programming pattern to add the word "set" in front of the state variable name when assigning it as the setter function. So "formData", "counter", "color" will be "setFormData", "setCounter", "setColor", etc.

Next, we need to create our React event listener and increment our state variable

import React from 'react'

function CounterExample(){
    const [counter, setCounter] = useState(0)

    return(
        <div>
            <h1>You have clicked the button {counter} times so far!</h1>
            <button onClick = { () => setCounter(counter+1)} > Click me</button>
        </div>
    )
}

export default CounterExample
Enter fullscreen mode Exit fullscreen mode

We're passing a callback function that increases the value of counter by 1. As mentioned before, you can't just assign a new value to counter. Instead, you need to pass a new value to setCounter. React will update the state variable and know to re-ender that component.

Now every time you press the button, the counter value increases and gets updated within the DOM.

When using forms, you need to capture the input values using state. However, if you have multiple inputs, using so many state variables can become convoluted. Besides passing single values, you can pass arrays or objects into state and state setter functions

For example:

import React from 'react'

function CounterExample(){
    const [counter, setCounter] = useState(0)

    return(
        <form>
            <input name = "firstName" type = "text" placeholder="First Name Here" />
            <input name = "lastName" type = "text" placeholder="Last Name Here" />
            <input name = "age" type = "number" placeholder="age" />
            <button>Submit Values </button>
        </form>
    )
}

export default CounterExample
Enter fullscreen mode Exit fullscreen mode

instead of creating multiple state's, you can create 1 state to capture all three inputs

const [form, setFormData] = useState( {"firstName": "", "lastName" : "", "age" : "" })
Enter fullscreen mode Exit fullscreen mode

So, when you create your event listener for each input, you can create 1 event listener that accesses the key value pair within the object and update it.

function handleOnChange(e){
    const key = e.target.name
    const value = e.target.value

    setFormData({...formData, [key]: value})
}

Enter fullscreen mode Exit fullscreen mode

Please note, if you are using objects or arrays with state, you need to pass a COPY of it. You cannot use mutable array methods. However, you can "spread" the array or object into the setter function.

The final set of code will be:

import React from 'react'

function CounterExample(){
    const [form, setFormData] = useState( {"firstName": "", "lastName" : "", "age" : "" })

    return(
        <form onSubmit = {submitCallBackFunction}>
            <input onChange = {handleOnChange} name = "firstName" type = "text" placeholder="First Name Here" value={formData["firstName"]}/>
            <input onChange = {handleOnChange} name = "lastName" type = "text" placeholder="Last Name Here" value={formData["lastName"]}/>
            <input onChange = {handleOnChange} name = "age" type = "number" placeholder="age" value={formData["age"]}/>
            <button>Submit Values </button>
        </form>
    )
}

export default CounterExample
Enter fullscreen mode Exit fullscreen mode

Lastly, you should declare your state variables within the component that will use them. However, if another component needs to use this variable, then the closet parent to both is where state needs to reside, and then brought down via props.

The best way to brainstorm this is to create a component map.

index.js
|--- App
    |--- Header
    |--- DisplayData
    |--- AddData
Enter fullscreen mode Exit fullscreen mode

DisplayData needs a state variable to display some data. However, AddData contains data that will eventually be added to the state variable within DisplayData. So, App will contain the state data variable and then passed that down to DisplayData while passing down the setter function to the AddData component.

Also, AddData contains forms which need to be controlled. A separate state variable will be needed to keep tract of the input values. However, based on how our app is configured, it can stay within this component. When the user presses submit, a callback function or the setter function can be passed down to add the new data to the data state variable.

Our new map will look like this. (Please note you can design your map however you like but make it so that it shows a logical map of where your state variables are established!)

index.js
|--- App (data, setData)
    |--- Header
    |--- DisplayData <- data |
    |--- AddData <- setData | (formData, setFormData)
Enter fullscreen mode Exit fullscreen mode

Lastly, when react re-renders the component where the state variable is assigned, it will render every child component underneath it as well but not parent or sibling components.

I hope my above guide helped you on your programming journey.

Lastly, happy coding!

Top comments (0)