DEV Community

Cover image for React, State, and You
Darren
Darren

Posted on

React, State, and You

React can be daunting to start off with, but once you learn some basic concepts even the most complicated of interactions can be simple. The thing to remember is you can only really influence three basic things

  1. Children
  2. Props
  3. State

So let's learn them.

Children

All React apps start from a single component, which is sometimes called the 'entry point' for your application:

ReactDOM.render(<App />, rootElement);
Enter fullscreen mode Exit fullscreen mode

Here, <App /> is our 'entry point'. It's the component from which any other component in our application needs to be a child of. We can then define <App /> to be something like this:

export default function App() {
  return (
    <Child>
      <GrandChild />
    </Child>
  );
}
Enter fullscreen mode Exit fullscreen mode

The structure is <App> -> <Child> -> <GrandChild> with each being a 'child' of it's parent.

Props

The second fundamental concept is the properties that you provide to your components. Properties, are variables that the parent wants to share with a child. The child can then make use of these properties - shortened to props. Properties are defined to a child, and subsequently consumed by a child as follows:

const Child = (props) => (
  <p>Hi, my name is {props.name} </p>
);

export default function App() {
  return (
    <div>
      <Child name="Billy" />
      <Child name="Mary" />
      <Child name="Colin" />
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

Here, each child is provided a different value for 'name' and the Child component itself uses the name provided in the props object. When using functional components like this, props is always the first argument to your function, if you were using class Child extends React.Component you would need to use this.props but otherwise it works the same. Props can contain anything you want, the only preset prop is children which are 'children' provided by the parent, for example:

const Child = (props) => (
  <div>
    <p>Hey I am a child</p>
    {props.children && <div>And I have my own children {props.children}</div>}
  </div>
);

const GrandChild = () => <p>Hey I am a grandchild!</p>;

export default function App() {
  return (
    <Child>
      <GrandChild />
    </Child>
  );
}
Enter fullscreen mode Exit fullscreen mode

A <GrandChild> is being provided to the <Child> by the <App>. This will be accessible to the <Child> using props.children. You can see in the Child function that we are checking if props.children is set and if it is we are rendering them in the <div> component.

State

So we have seen what a parent can provide to a child through props, but what if the child itself wants to maintain some data of it's own. This is where 'state' comes in, and effectively state is a variable that lives inside of a component and exists during the lifetime of that component. There are some differences between 'functional' and 'class' based components, here I will be talking exclusively about the 'functional' format for state management. Let's look at a really simple example:

const names = ['Mary', 'Bill', 'Fred', 'Juan']

export default function App() {
  return (
    <div>
      <h1>Today is who's birthday?</h1>
      <ul>
        {names.map((name) => <li>{name}</li>)}
      </ul>
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

Here we have a simple array of names, which our component is then rendering into a list. We want to maintain 'state' of who's birthday it actually is. One really simple way would be to include a variable as follows:

const names = ["Mary", "Bill", "Fred", "Juan"];

export default function App() {
  const birthdayPerson = "Bill";
  return (
    <div>
      <h1>Today is who's birthday?</h1>
      <ul>
        {names.map((name) => (
          <li>{`${name}${
            name === birthdayPerson ? " HAPPY BIRTHDAY!!!" : ""
          }`}</li>
        ))}
      </ul>
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

We can change the birthdayPerson to equal anyone from the list, but currently it's hardcoded. What if we want the user to be able to click one of the names in the list, thereby setting the birthdayPerson to that person. Sadly we can't just create our own variable and update it because React works by re-rendering changes when it detects a change - so it needs help detecting those changes. So the following won't work:

const names = ["Mary", "Bill", "Fred", "Juan"];

export default function App() {
  let birthdayPerson = "Bill";
  return (
    <div>
      <h1>Today is who's birthday?</h1>
      <ul>
        {names.map((name) => (
          // this won't work!
          <li onClick={() => (birthdayPerson = name)}>{`${name}${
            name === birthdayPerson ? " HAPPY BIRTHDAY!!!" : ""
          }`}</li>
        ))}
      </ul>
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

Instead, we need to use the useState hook. useState is a 'hook' function built into React that allows us to declare a variable and get a function that allows us to change this variable. This way React knows when the variable has changed so can compute the new render and decide what needs to update efficiently.

import React, {useState} from 'react';
const names = ["Mary", "Bill", "Fred", "Juan"];

export default function App() {
  const [birthdayPerson, setBirthdayPerson] = useState("Fred");
  return (
    <div>
      <h1>Today is who's birthday?</h1>
      <ul>
        {names.map((name) => (
          <li onClick={() => setBirthdayPerson(name)}>{`${name}${
            name === birthdayPerson ? " HAPPY BIRTHDAY!!!" : ""
          }`}</li>
        ))}
      </ul>
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

Always remember to import useState when you want to use it. useState is a function that will provide your component with an array. The first thing in the array is the current value for the state, with the argument passed into useState(arg) being the initial state (in the above case birthdayPerson === "Fred"). The second thing in the array is the function to call that will update the value, and will take care of Reacts re-rendering for you. In the above example the onClick of each list item is using it to setBirthdayPerson(name) where name is the name for that particular item in the names array.

Bringing it all together.

So now you have children and then parents are providing props to it. Each component can now also have their own state for managing things, but now we want to tie these things together. Well there isn't much else to cover, just that state and the functions to update it can be fed into props... and this is really where the basic building blocks open into a lot of possibilities.

The Spec

We want to make an address book, names on the left that we can selected, and on the right we see more information for the selected name. We will have a data source for our address book which is just an array of objects like this:

{
  _id: "5f90374ad2e52f3fbe46d149",
  name: {
    first: "Bentley",
    last: "Rosales"
  },
  company: "ACUSAGE",
  phone: "+1 (961) 423-2258",
  address: "930 Eckford Street, Elfrida, Vermont, 1570",
  photoUrl:
      "https://avatars.dicebear.com/api/avataaars/5f90374ad2e52f3fbe46d149.svg"
}
Enter fullscreen mode Exit fullscreen mode

We want the list to show just a first name, but on selected we want to see their address, phone number, company, and of course their picture!

Component Structure

So like everything we have a single point of entry, this will be our <App>. Our <App> will then have two child components <List> - which shows our selectable list of people, and <View> - which shows the currently selected person.

This is one of the simplest components so it makes sense to build this one first. All it needs is the right structure to render what the information we want, and a single prop selectedPerson.

const View = (props) => {
  const { selectedPerson } = props;
  return (
    <div className="view">
      {selectedPerson ? (
        <Fragment>
          <div className="view-heading">
            <img src={selectedPerson.photoUrl} />
            <h2>
              {selectedPerson.name.first} {selectedPerson.name.last}
            </h2>
          </div>
          <p>
            <b>{selectedPerson.company}</b>
          </p>
          <p>{selectedPerson.address}</p>
        </Fragment>
      ) : (
        <p>No one selected</p>
      )}
    </div>
  );
};
Enter fullscreen mode Exit fullscreen mode

This is working just using props and a single selectedPerson prop is expected. If this isn't set we show <p>No one selected</p> otherwise we show the data of the person.

Next up is the list component, this has to take a few different sources of information from props. First it needs the people which is the array of names to display. Second, it needs to know if there is a selectedPerson so that it can show that that person is selected in the list. Finally it needs to know how to update or setSelectedPerson so when a name is clicked it can set the selected person to whoever was clicked. All of this will be provided as props:

const List = (props) => {
  const { people, selectedPerson, setSelectedPerson } = props;
  return (
    <div className="list">
      {people.map((person) => (
        <div
          onClick={() => setSelectedPerson(person)}
          className={
            person === selectedPerson ? "list-item selected" : "list-item"
          }
          key={`person_${person._id}`}
        >
          {person.name.first}
        </div>
      ))}
    </div>
  );
};
Enter fullscreen mode Exit fullscreen mode

So you can see we have a list of people which we then map and turn into <div> elements with the persons first name rendered. We also check if the person that we are iterating over is equal to selectedPerson and if so we set a different CSS className. The <div> also gets given an onClick function which will invoke the setSelectedPerson prop with the respective person.

So now we have to tie the two things together, and really the only place to do this is in the parent <App> component. This can feed the people, selectedPerson and setSelectedPerson properties to the <List> and can provide the <View> with the selectedPerson property.

import peopleData from "./data";
export default function App() {
  const [selectedPerson, setSelectedPerson] = useState();
  return (
    <div className="address_book">
      <List
        selectedPerson={selectedPerson}
        setSelectedPerson={setSelectedPerson}
        people={peopleData}
      />
      <View selectedPerson={selectedPerson} />
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

The only things we need here are to import the peopleData from our file that has the array of people for the address book, and to create a state variable that holds the selectedPerson. We don't provide an initial value for selectedPerson in the useState() call - because we have ensured that the <View> can deal with this being empty.

You can find all of this in a neat sandbox below. Enjoy!

Latest comments (0)