React Function Components with useState

React’s Function Components make writing components really easy. They eliminate a lot of boiler plate code and confusing state management. However without hooks function components are very limited. Let’s start with a very simple example and see why the useState hook is important. Then we’ll move on to the reason for the useEffect hook.

Example

Our example is very simple – just an input and a live rendering of that input. So what happens without the useState hook? Here’s a very simple function component:

const Comment = (props) => {
  return (
    <div>
      <div>{props.comment}</div>
      <textarea 
        value={props.comment} 
        name="comment"
        onChange={(e) => { props.comment = e.target.value }}
      />
    </div>
  )
};

which accepts props with a “comment” and displays it, along with an input which should allow you to edit the comment.

See the Pen React Component No Hooks by corporealshift (@corporealshift) on CodePen.

Except…it won’t work! This is because React doesn’t know that the comment might change. So we have to tell it, and we do that using useState. More specifically, we use useState hook when we want to retain a value between invocations of our component. In our case we want to hang on to the changes the user makes in the textarea field.

Example with useState

To use the useState hook is pretty straightforward: It accepts a default (or starting) value and returns two things: The current state variable (in our example comment) and a function for updating the state variable (setComment). It is common to destructure this result using an array. Next in our onChange function on the textarea we call the setComment function and give it the value from the textarea.

const Comment = (props) => {
  const [comment, setComment] = useState(props.comment);
  return (
    <div>
      <div>{comment}</div>
      <textarea 
        value={comment} 
        name="comment"
        onChange={(e) => { setComment(e.target.value) }}
      />
    </div>
  )
};

and here is the example working in a codepen:

See the Pen React Component Template by corporealshift (@corporealshift) on CodePen.

Great! Now when we enter our comment into the text area it is reflected in the div above it. The useState hook is telling React that we want to have the comment persisted through render calls, and giving us a way to update that value anytime we want.

But what if we want to have some state shared between two components? Or more? Let’s take a look at what happens if we split our comment component up by responsibilities: One for editing a comment and one for rendering one.

First, let’s split out our text area into its own component. We’ll also need to add a way to update the parent component’s state when the comment is edited, so we’ll add a new prop called onChange. This gives us a way to run code from the parent component when the child’s state changes.

const EditComment = (props) => {
  const [comment, setComment] = useState(props.comment);
  const handleTextareaChange = (e) => {
    const newComment = e.target.value;
    setComment(newComment);
    if (props.onChange) {
      props.onChange(newComment);
    }
  };
    
  return (
    <div>
      <textarea 
        value={comment} 
        name="comment"
        placeholder="Write a comment"
        onChange={handleTextareaChange}
      />
    </div>
  )
};

Next let’s move the comment display into its own component, and introduce a parent component that composes our two together:

// EditComment component omitted for brevity
const ViewComment = (props) => (
  <div>{props.comment}</div>
);

const Comment = (props) => {
  const [comment, setComment] = useState(props.comment);
  const onCommentChange = (newComment) => {setComment(newComment)};
  return (
    <div>
      <ViewComment comment={comment} />
      <EditComment comment={comment} onChange={onCommentChange} />
    </div>
  );
};

And here’s a codepen example with the updated components:

See the Pen React Component Separated by corporealshift (@corporealshift) on CodePen.

And it works! As long as you leverage the useState hook, state changes in a parent component will cause the children to update. As long as all of our code lives inside our React app everything is happy. But what if we need to interact with things outside of our app? Like browser APIs, network requests, or other non-react code? To cover side effects React has the useEffect hook, which will be covered in another post.

Takeaways

  • The useState hook saves information between component updates
  • Parent states will cause updates to child components
  • Child states will not cause updates to their parents
  • Changes made outside of a React app will not affect state

Leave a Reply

Your email address will not be published. Required fields are marked *