Understanding State in React JS - Part 4

Understanding State in React JS - Part 4

ยท

5 min read

Hello everyone ๐Ÿ‘‹,

In the previous article of Learn React JS Series, we learned about,

  1. What is a Component?
  2. When to use a Component?
  3. How to create a Component?
  4. How to separate a big component into smaller components?

In this Part 4 of Learn React JS Series, we will learn about what is State, how to use it, and when to use it.

What is State?

State is similar to props, but it is private and fully controlled by the component.

When to use State?

In the previous part of this series, we created a SearchResults functional component that accepts props as an argument and renders the URL, title, and description.

function SearchResult(props) {
  return (
    <div>
      <div className="search-url">{props.url}</div>
      <h2 className="search-title">{props.title}</h2>
      <div className="search-description">{props.description}</div>
    </div>
  );
}
export default SearchResult;

Assume that you wanted to update the URL to localhost on the click event of a button.

function SearchResult(props) {
  // newly added - handler for button click
  function updateURL() {
    props.url = "localhost";
  }

  return (
    <div>
      <div className="search-url">{props.url}</div>
      <h2 className="search-title">{props.title}</h2>
      <div className="search-description">{props.description}</div>
       // newly added
      <button onClick={updateURL}>Update URL</button>
    </div>
  );
}
export default SearchResult;

When the click event happens on the button, updateURL function is triggered to update the URL in props. But, when it tries to update the URL, the following error will be displayed.

Screenshot 2021-07-06 at 5.36.06 PM.png

This is because props are the read-only property and the props values are obtained from the parent component.

Similarly, if we have a Counter component, the counter value should be controlled by the Counter component. In this case, we should use State instead of Props to maintain the state for each component on its own.

How to use State?

  • Create a class Component with for Counter and extend the React.Component.
import React from "react";

export class Counter extends React.Component {

}
  • Override the constructor and pass props to the base class.
import React from "react";

export class Counter extends React.Component {

constructor(props) {
    super(props);
  }

}
  • Define the state with its initial values. In our case, count is initialized to 0 and isStarted as false. isStarted flag is used to toggle the label. (start/stop)
export class Counter extends React.Component {

constructor(props) {
    super(props);
    this.state = { count: 0, isStarted: false };
  }

}
  • Override the render method. Render() method should return a value JSX. In the render() method, we have a button that displays either Stop/Start based on isStarted flag from the state object & span tag to show counter value.
export class Counter extends React.Component {

constructor(props) {
    super(props);
    this.state = { count: 0, isStarted: false };
  }

  render() {
    return (
      <div className="counter">
        <button className="btn">
          {this.state.isStarted ? "Stop" : "Start"}
        </button>

        <span>Count is {this.state.count}</span>
      </div>
    );
  }

}
  • To start the counter on the button click, listen to the onClick event on the button with the handler function.
export class Counter extends React.Component {

constructor(props) {
    super(props);
    this.state = { count: 0, isStarted: false };
    // This binding is necessary to make `this` work in the callback. eg (toggleButton)
    this.toggleButton = this.toggleButton.bind(this);
  }

 toggleButton() {
    if (!this.state.isStarted) {
     // clicked Start button, so start the timer
    } else {
    // clicked stopped button, so clear the timer
    } 
  }ร

  render() {
    return (
      <div className="counter">
        <button className="btn" onClick={this.toggleButton}>
          {this.state.isStarted ? "Stop" : "Start"}
        </button>

        <span>Count is {this.state.count}</span>
      </div>
    );
  }

}

To increment the counter, we should use this.setState instead of directly changing the counter by this.state.counter = this.state.counter + 1.
Read more on why States should not be directly modified

There are 2 ways to update the State.

  1. this.setState({}) accepts an object to update the state of the component with key-value pair. Eg: this.setState({count: this.state.count})

  2. this.setState() also accepts a function rather than an object with the previous state as the first argument, and the props at the time the update is applied as the second argument.

As state update are asynchronous, it is better to use this way whenever the previous state is used to calculate new values. Syntax : this.setState((state, props) => { } )

In our case, we can update the count state by,

 this.setState((state) => ({
       count: state.count + 1,
 }));

Our final code for the Counter component,

import React from "react";

export class Counter extends React.Component {
  constructor(props) {
    super(props);
     // local state
    this.state = { count: 0, isStarted: false };
    // This binding is necessary to make `this` work in the callback. eg (toggleButton)
    this.toggleButton = this.toggleButton.bind(this);
  }

  toggleButton() {
    if (!this.state.isStarted) {
       // clicked Start button, so start the timer
      this.counterInterval = setInterval(() => {
        // Update the counter state
        this.setState((state) => ({
          count: state.count + 1,
        }));
      }, 1000);
    } else {
       // clicked stopped button, so clear the timer
      clearInterval(this.counterInterval);
    }
    // update the isStarted state
    this.setState({
      isStarted: !this.state.isStarted,
    });
  }

  render() {
    return (
      <div className="counter">
        <button className="btn" onClick={this.toggleButton}>
          {this.state.isStarted ? "Stop" : "Start"}
        </button>

        <span>Count is {this.state.count}</span>
      </div>
    );
  }
}

Use the counter component in the App.js

import React from "react";
import { Counter } from "./Counter";

function App(props) {
  return (
    <div className="container">
      <h1>Understanding State</h1>
      <Counter></Counter>
      <Counter></Counter>
    </div>
  );
}

export default App;

Output

We have used the Counter component 2 times. You can clearly see the counter state is maintained by its own Counter component instance. The count value is different as we started the 2nd one after some seconds.

counter.gif

Resource

Here's my Github repo where you can find all the files in the part-4 branch. You can clone and try it out!

Thanks for reading the article. I hope you like it!

You can connect with me on Twitter & Github :)

Did you find this article valuable?

Support Yuvaraj by becoming a sponsor. Any amount is appreciated!

ย