DEV Community

Cover image for Intro to State in React - Example Walkthrough
Kyra HP
Kyra HP

Posted on • Edited on

Intro to State in React - Example Walkthrough

Table of Contents:


I've spent the last few weeks getting a handle on React and can already see how much easier my life as a developer will be because of it. While there are myriad topics I could delve into regarding React, today I'll focus on one of the most important - state. If you already have a general understanding of React components (read here if not), this post will provide an introduction on how to take advantage of the power of state in your application. I will also be using destructuring throughout my code snippets.

React's data flow

React's data flow runs one way down the component hierarchy, from Parent to Child component. Props are pieces of data that are passed from Parent to Child component. For example, say you have an App that contains two child components- SongTitles and SongArtists. App might have a songs array of objects representing each song, where keys are the song titles and values are the corresponding artists. If we want to render the titles and artists in their respective components, we might want to pass that information as props to SongTitles and SongArtists. This might not be the most elegant design, but here's a snippet of what our components could look like:

//App.js
function App() {
  const songs = [
    { "Blank Space": "Taylor Swift" },
    { "Tiffany Blews": "Fall Out Boy" },
    { "Knee Socks": "Arctic Monkeys" },
  ];

  const titles = songs.map((song) => Object.keys(song)[0]);
  const artists = songs.map((song) => Object.values(song)[0]);

  return (
    <div className="App">
      <h1>React State Demo</h1>
      <SongTitles titles={titles} />
      <SongArtists artists={artists} />
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode
//SongTitles.js
function SongTitles({ titles }) {
  const songList = titles.map((title) => (
    <p key={title}>
      <button>X</button> {title}
    </p>
  ));
  return (
    <div className="titles">
      <h2>Song Titles</h2>
      {songList}
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode
//SongArtists.js
function SongArtists({ artists }) {
  const artistList = artists.map((artist) => <p key={artist}>{artist}</p>);
  return (
    <div className="artists">
      <h2>Song Artists</h2>
      {artistList}
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

Here's what the App looks like:

Image description

Definition of state

This looks good, but what happens if we want to change the songs object dynamically and remove a song from the screen when we click a delete button? Here's where state comes in. State data can change throughout a component's lifetime as the user interacts with the app, unlike props. If we want to be able to delete a song (and its corresponding artist) from inside the SongTitles component, we can easily do this by setting state. If we make the songs array stateful rather than static, it will update dynamically and we won't have to refresh the page each time we want to render a change.

Deciding where to hold state

Before we create our state variables, we should figure out where to hold songs's state. To figure out where state should live, it's helpful to decide which components need access to that state. In this case, SongTitles and SongArtists both need access to songs to render information. Sibling components can't pass data to each other (remember the data flow) so we should store songs's state in a common parent component (App in this case). This means that songs can continue living in App, but you can imagine that without the SongArtists component, we could just store this state directly in SongTitles.

Setting up state

Now that we've decided where the songs state will live, let's import React's useState hook in App.js: import {useState} from "react"

The useState function takes in the initial value of our state variable as a parameter and returns two things:
1) the state's current value
2) a function that can be used to set (or change) that state

To use state with our songs array, we can replace our old array with the following lines inside of our App function:

// App.js
function App() {
  const [songs, setSongs] = useState([
    { "Blank Space": "Taylor Swift" },
    { "Tiffany Blews": "Fall Out Boy" },
    { "Knee Socks": "Arctic Monkeys" },
  ]);
//...
}
Enter fullscreen mode Exit fullscreen mode

Here, songs is initially set to the the same 3 songs from our initial array, but we also now have a function to dynamically change songs called setSongs.

NOTE: This is the naming convention for setter functions, but you could name it something other than setSongs if you prefer.

Updating state

Our next step is to create a function in App that will remove a song from songs. Given a song title that we want to delete, our function could look like this:

// App.js
function App() {
//...
  function handleDeleteSong(title){
    const updatedSongs = songs.filter(song=>!(title in song));
  }
//...
}
Enter fullscreen mode Exit fullscreen mode

Right now this function is creating a new array of songs. It contains all of our original songs except for the one with the title we want to delete. Your first thought might be to just set songs equal to updatedSongs. However, because we want to modify an internal state living in App, this is where our setter function comes in handy.

// App.js
function App() {
//...
  function handleDeleteSong(title){
    const updatedSongs = songs.filter(song=>!(title in song));
    setSongs(updatedSongs); //Updating songs state using our new songs array
  }
//...
}
Enter fullscreen mode Exit fullscreen mode

We are using setSongs to update our internal state to a new array of songs, with our deleted song removed.

We're not quite done yet! We haven't actually called this function anywhere. We want handleDeleteSong to run when we click the delete button next to a song's title. We'll need to access this function from within SongTitles and use it as a callback in a click event listener. We can pass a reference to handleDeleteSong as a prop down to SongTitles. I'm going to call this prop onDeleteSong.

// App.js
function App() {
//...
  return (
    <div className="App">
      <h1>React State Demo</h1>
      <SongTitles titles={titles} onDeleteSong={handleDeleteSong}/>
      <SongArtists artists={artists} />
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

Now within SongTitles, we can call onDeleteSong every time we click a delete button.

//SongTitles.js
function SongTitles({ titles, onDeleteSong }) {
  //Added onDeleteSong prop
  const songList = titles.map((title) => (
    <p key={title}>
      {/* Adding click event listener below, passing in title we want to delete */}
      <button onClick={(e) => onDeleteSong(title)}>X</button> {title}
    </p>
  ));
  return (
    <div className="titles">
      <h2>Song Titles</h2>
      {songList}
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

This inverses the data flow and triggers a state change in App from its child component SongTitles. This is generally a good strategy to use when you want to update a parent's state from a child.

One great thing about React is that it automatically re-renders a component when its state changes. This means that when we click a delete button, App and its children, SongTitles and SongArtists, will re-render with the updated state.

NOTE: This works because updatedSongs from our handleDeleteSong function is an entirely new array. Because we used the filter method, updatedSongs points to a different place in memory than songs. React does not re-render a component if you attempt to set state with a shallowly unchanged object. If we tried to set state by directly modifying songs (e.g. by using splice() to remove a song from songs), setSongs(songs) would be unsuccessful. Even if we successfully removed an element from songs this way, state would not update and therefore our screen would not be updated to reflect our changes.

Here's a quick demo of our working app:

If you'd like to see the source code for our 3 components, you can check out this github repository.

You can read more about state here.

Top comments (0)