Understanding React State: Managing Dynamic Data

·

7 min read

React components have a built-in state object that determines how they behave. This object can be used to store the component’s dynamic information or data, allowing changes over time. Let’s explore what this means in practice!

Triggering Changes with Events

Typically, when we’re working with events in our components we want to trigger some change in our application based on the user’s interaction.

Here’s an example of a React component that uses a variable to display “Logged In” or “Logged Out” based on a button click:

function App() {
    let isLoggedIn = true

    function handleClick(){
        isLoggedIn = !isLoggedIn
        console.log("button clicked")
        console.log(isLoggedIn)
    }

    return (
        <div>
            <button onClick={handleClick}>{isLoggedIn ? "Logged In" : "Logged Out"}</button>
        </div>
    )
}

export default App

In this example component, we have a variable isLoggedIn which is set to true. We’re using conditional rendering to show “Logged In” or “Logged Out” as the text for the button based on the value of isLoggedIn being true or false.

When this code runs, the button displays “Logged In” initially.

If we manually change the value of isLoggedIn to false, the button displays "Logged Out":

// App.js
function App() {

   let isLoggedIn = false
    // manually changed value to false

// ... export statements

Now let’s see what happens when we change the value of isLoggedIn back to true and try clicking the button to initiate the change rather than manually assigning the value each time.

Every time we click the button, we can see in the console the variable gets assigned to a new value. We have an issue though… the variable is showing a value of false which means the button should say “Logged Out”, but the text doesn’t appear to change despite multiple clicks of the button.

We want to figure out a way that when the button is clicked we update our isLoggedIn variable, and by updating the variable we also want to update what is displayed in the DOM.

function App() {
    let isLoggedIn = true

    function handleClick(event){
        // now passing event into the function
        isLoggedIn = !isLoggedIn
        console.log("button clicked")
        console.log(isLoggedIn)

        event.target.textContent = isLoggedIn ? "Logged In" : "Logged Out"
        // manually grabbing the target and conditionally assigning the correct text content
    }

    return (
        <div>
            <button onClick={handleClick}>{isLoggedIn ? "Logged In" : "Logged Out"}</button>
        </div>
    )
}

export default App

In order to do this, we could go into the event, manually grab the target, and set the text content to something based on whether we are “Logged In” or “…Out”.

Look at that! Our button is working now. We can toggle the value and also change the text of the element with a click. However, if you ever find yourself doing this in React.. you are doing things the wrong way!

With this approach, we are doing the manual hard work of manipulating the DOM ourselves, which is not the most efficient. What we want is React to update the DOM for us; doing the hard work of updating the DOM without us having to manually update those elements.

We need to figure out how to write some code that will automatically update the DOM and re-render the component whenever the variable is changed. This is where one of React’s handy tools comes into play.

Introducing State

State in React enables automatic updates to the DOM when the state changes. In order to use state in our component we need to use something called React Hook; it is a special function from React which will let us hook into React’s internal state. To get access to this hook, we need to import it:

// App.js
import { useState } from "react"

// ...App component

Now, in order to use it we must call it from inside of our component and pass in some initial value. Let’s modify our example to use state with React Hooks:

import { useState } from "react"
// import statement

function App() {
    const [isLoggedIn, setIsLoggedIn] = useState(true)
    // calls useState and passes in an initial value^
    // useState returns an array with our state variable, and setter function
    console.log({ isLoggedIn, setIsLoggedIn })

    function handleClick(){
        isLoggedIn = !isLoggedIn
        console.log("button clicked")
        // console.log(isLoggedIn)
        // no longer needed, already logging isLoggedIn above ^
    }

    return (
        <div>
            <button onClick={handleClick}>{isLoggedIn ? "Logged In" : "Logged Out"}</button>
        </div>
    )
}

export default App

In our case, we are trying to use this for our isLoggedIn variable, and we want to set the initial value to true.

Now what we get from calling useState is an array that has two things inside of it. This array has our state variable, and we will also get a setter function from our variable - setIsLoggedIn.

function App() {  
  const [isLoggedIn, setIsLoggedIn] = useState(true)
    console.log({ isLoggedIn, setIsLoggedIn })

// ... rest of code

After saving our application if we do a quick console.log to see our isLoggedIn and setIsLoggedIn variables we will see…

... setIsLoggedIn is something we have access to inside of this function.

function App() {
    const [isLoggedIn, setIsLoggedIn] = useState(false)
    console.log({ isLoggedIn, setIsLoggedIn })

// ... rest of code

We can also change the initial value to false, and see our button text changes to “Logged Out”…

So far, it appears our component still works the same - we still have a variable that determines what’s displayed inside of the button.

However, when we click the “Logged In” button we get an error…

The reason we get this error is that isLoggedIn = !isLoggedIn is no longer valid syntax, since isLoggedIn is now saved as a const.

We’ll need to change our code inside of the click event handler function. Instead of just updating the variable, we want to tell React that we are trying to change our state variable, isLoggedIn, by calling our setter function, setIsLoggedIn.

How do we do this?

import { useState } from "react"

function App() {
    const [isLoggedIn, setIsLoggedIn] = useState(true)
    console.log({ isLoggedIn, setIsLoggedIn })

    // let isLoggedIn = true

    function handleClick(){
        setIsLoggedIn(!isLoggedIn)
        // isLoggedIn = !isLoggedIn
        // no longer valid syntax

        console.log("button clicked")
    }

    return (
        <div>
            <button onClick={handleClick}>{isLoggedIn ? "Logged In" : "Logged Out"}</button>
        </div>
    )
}

export default App

Instead of just assigning the variable to a new value, isLoggedIn = !isLoggedIn, we want to call our setter function and pass in whatever new value we want our state to be.

After updating, we will see the value of isLoggedIn is changing when we click our button. We can also see the component re-renders with the new value!

This is a crucial part of working with state in react. It’s important to note that anytime we call the setter function two things happen:

1. It updates the state variable.

2. It triggers a re-render of the component.

So as long as we use the setter function, React will automatically update the DOM for us! This approach ensures the UI stays in sync with the data.

Passing State to Child Components

Another important thing about state, anytime we have a component that has child components, all children will also re-render when the parent component re-renders. Let’s look at an example:

import { useState } from "react"

function ChildComponent({ isLoggedIn }) {
    console.log("ChildComponent render")
    // logs every time component renders

    return <h1>{isLoggedIn ? "You're logged in!" : "You're logged out"}</h1>
    // conditionally renders text based on the value of isLoggedIn
}

function App() {
    const [isLoggedIn, setIsLoggedIn] = useState(true)
    console.log({ isLoggedIn, setIsLoggedIn })

    function handleClick(){
        setIsLoggedIn(!isLoggedIn)

        console.log("button clicked")
    }

    return (
        <div>
            <ChildComponent isLoggedIn={isLoggedIn}/>
            <button onClick={handleClick}>{isLoggedIn ? "Logged In": "Logged Out"}</button>
        </div>
    )
}

export default App

Say we have another component that is a child to App. Anytime we update state in the parent component, the child component will also re-render. Let’s save our file and click the button:

As we can see, the child component re-renders whenever the parent’s state changes!

In this example, you will also notice we are sharing the isLoggedIn variable by passing it down as a prop to ChildComponent. We are also able to pass our state variables between components just like any other values we pass as props.

Summary

State is a fundamental tool in React for managing dynamic data. It ensures that components automatically re-render to reflect changes, making it easier to create interactive applications. By using useState and passing state as props, you can build efficient and dynamic UIs. Mastering state is key to unlocking React’s full potential!