React JS - All about States

Essential State Concepts for Creating Powerful React Apps

React JS - All about States

State

State is an object that represents the current situation or data of a component at a given time. It holds dynamic values that can change over time (as opposed to props, which are passed from parent components and are generally static or immutable). When state changes, the component re-renders to reflect the new state.

import React, { useState } from 'react';

function Counter() {
  // Declare a state variable called count, initialized to 0
  const [count, setCount] = useState(0);

  return (
    <div>
      <p>You clicked {count} times</p>
      <button onClick={() => setCount(count + 1)}>Click me</button>
    </div>
  );
}

In the above example, count is the state variable, and setCount is the function used to update its value.

1. How State Works: Key Concepts

a. Initialization of State

State is typically initialized using the useState hook (in function components).

const [value, setValue] = useState(initialValue);

b. Updating State

State should never be updated directly (i.e., mutating the object). Instead, you use state setter function (in function components).

setCount(count + 1);

React uses a shallow merge when you call setState in class components. Only the properties specified in the setState call will be updated, and others will remain unchanged.

c. Asynchronous Nature of State Updates

State updates in React are asynchronous. React batches multiple state updates for performance reasons, especially in event handlers.

Function Components: You can also pass a function to the state setter function:

setCount((prevCount) => prevCount + 1);

d. State and Re-rendering

When the state of a component changes, React triggers a re-render of that component. This allows the UI to update based on the latest state. If parent renders then children's also renders in React, Parent of the parent will not be rendered.

2. Managing Complex State

a. State as an Object

In more complex scenarios, you might have a state object with multiple properties. In such cases, it’s important to merge the new state with the existing state rather than replacing the entire object.

Function Components: When using the useState hook with objects, you need to spread the existing state to avoid losing properties:

setState((prevState) => ({ ...prevState, name: 'Vitthal' }));

b. State as an Array

You can also manage arrays in state. Use array methods like map, filter, concat, and spread to update arrays immutably.

const [items, setItems] = useState([]);

setItems([...items, newItem]);  // Adding a new item

3. Lifting State Up

When multiple components need to share or synchronize state, the state should be "lifted up" to the nearest common ancestor component. This allows the parent component to manage the state and pass it down to the child components as props.

"lifting the state up" is a design pattern that involves moving the state from a child component to a parent component. This is done to make the state accessible to multiple child components, or to make it easier to manage the state from a single location.

Example:

function ParentComponent() {
  const [sharedState, setSharedState] = useState('Shared State');

  return (
    <div>
      <ChildComponent1 sharedState={sharedState} />
      <ChildComponent2 setSharedState={setSharedState} />
    </div>
  );
}

Here, sharedState is passed down as a prop to both child components.

4. Common State Patterns

a. Derived State

Avoid directly storing derived values in the state. If a value can be computed from existing state or props, it’s better to derive it in the render method rather than store it in state.

function Component({ items }) {
  const itemCount = items.length;  // Derived from props
  return <p>Total items: {itemCount}</p>;
}

b. Conditional State Updates

Sometimes, you need to conditionally update state based on certain criteria.

if (count < 10) {
  setCount(count + 1);
}

5. Best Practices for State

  • Keep state minimal: Only store data that’s necessary for rendering. Avoid storing derived or redundant values in the state.

  • Use state immutably: Always return new objects or arrays when updating the state to avoid side effects.

  • Avoid deep state nesting: Flatten state as much as possible to simplify updates and avoid performance issues.

  • Batch state updates: If you need to make multiple state changes at once, batch them to minimize renders.

6. State and Performance

While state is a powerful tool for managing UI, it can also lead to performance bottlenecks if misused. Here are some tips:

  • Memoization: Use useMemo or useCallback to memoize heavy computations or functions that depend on state to avoid unnecessary re-renders.

  • Avoid redundant re-renders: Overuse of state or complex state updates can cause unnecessary re-renders, affecting performance.

7. Example : Input Field

Let's create an example where we manage the state of an input field.

import { useState } from 'react';

function TextInput() {
  // Initialize state with an empty string
  const [text, setText] = useState('');

  return (
    <div>
      <input
        type="text"
        value={text}
        onChange={(e) => setText(e.target.value)}
      />
      <p>You typed: {text}</p>
    </div>
  );
}

export default TextInput;
  • We initialize the text state with an empty string.

  • The input field's value is controlled by the text state.

  • The onChange event updates the text state with the input field's current value.

  • We display the current input value in a <p> element.

8. Example : Toggle

Let's create a component to toggle between two states.

import { useState } from 'react';

function Toggle() {
  // Initialize state with true
  const [isOn, setIsOn] = useState(true);

  return (
    <div>
      <p>{isOn ? 'ON' : 'OFF'}</p>
      <button onClick={() => setIsOn(!isOn)}>
        {isOn ? 'Turn Off' : 'Turn On'}
      </button>
    </div>
  );
}

export default Toggle;
  • We initialize the isOn state with true.

  • We display "ON" or "OFF" based on the isOn state.

  • We provide a button to toggle the state. When clicked, the button calls setIsOn with the opposite of the current state.

//hooks
//useState
//useEffect

//useState is a hook which you can call as a function. \
//it takes default value as a argument

//useState(defaultValue) -> it'll return an array
//[stateValue, functionToUpdateThatStateValue];

//In react we cannot use normal variables
//to change the UI

//state == data
//When state change -> our component will re-render -> UI

import { useState } from "react";

function StateSimple() {
  const [name, setName] = useState("vitthal");
  //console.log(name, setName);
  function updateName() {
    if (name === "vitthal") {
      setName("korvan");
    } else {
      setName("vitthal");
    }
  }

  return (
    <>
      <h1>{name}</h1>
      <button onClick={updateName}>Change Name</button>
    </>
  );
}

export default StateSimple;

9. Counter App using Callback function

import { useState } from "react";
import "./counter.css";
function Counter() {
  let [count, setCount] = useState(0);
  return (
    <>
      <h1 className="count">{count}</h1>
      <button
        onClick={() => {
            setCount((prev)=>prev+1)
        }}
        className="btn"
      >
        Increase
      </button>
      <button
        onClick={() => {
           setCount((prev) => prev - 1);
        }}
        className="btn"
      >
        Decrease
      </button>
      <button
        onClick={() => {
          setCount(0);
        }}
        className="btn"
      >
        Reset
      </button>
    </>
  );
}

export default Counter;

10. Update State in Array

  • for unique ID of an array elements we use command: npm i uuild

  • Why creating keys on the fly is a problem: When you create keys on the fly, you're essentially generating a new key every time the component is rendered. This can lead to:

    1. Performance issues: React has to re-render the entire list every time the keys change, which can be slow and inefficient.

    2. Unexpected behavior: If the keys change, React may not be able to correctly identify which components to update or remove, leading to unexpected behavior.

import {v4 as uuid} from "uuid";
fruits.map((fruit)=>(<li key={uuid()}>{fruit}</li>
import { useState } from "react";
import {v4 as uuid} from "uuid";

function ArrayState() {
  const [fruits, setFruits]=useState(['Mango','Apple','Banana','Chikoo']);
  const AddFruit=()=>{
    //setFruits([...fruits,'Orange'])
    // setFruits((prevFruits)=>{
    //   return [...prevFruits, 'Orange']
    // })

    setFruits((prevfruits)=>[...prevfruits, 'Orange'])
  }
  return <>
     <ul>
      {
      fruits.map((fruit)=>(
            <li key={uuid()}>{fruit}</li>
      ))
      }
      </ul> 
      <button onClick={AddFruit}>Add fruit</button>
  </>;
}
export default ArrayState;

11. Update State in Objects

import { useState } from "react";

function ObjectState() {
  const [person, setPerson] = useState({
      id:1,
      fName:'Vitthal',
      lName:'Korvan',
      email:'vitthalkorvan@gmail.com',
      mob:'9876543210',
      age:24
  });

  const IncreaseAge =()=>{
      // setPerson((prevPerson)=>{
      //       return {...prevPerson, age:prevPerson.age+1}
      // })
      setPerson((prevPerson)=> ({...prevPerson, age:prevPerson.age+1}))
  }
  return (
    <>
      <div >
        <p>FirstName : {person.fName}</p>
        <p>LastName : {person.lName}</p>
        <p>Email : {person.email}</p>
        <p>Mobile : {person.mob}</p>
        <p>Age : {person.age}</p>
        <button onClick={IncreaseAge}>Increase Age</button>
      </div>
    </>
  );
}
export default ObjectState;

Some Applications

Example 1: Create and Update object

//App.jsx
import { useState } from "react";
import "./App.css";
import Users from "./Users";

function App() {
  const [users, setUser] = useState([
    { id: 1, fname: "vitthal", lname: "korvan", age: 24 },
    { id: 2, fname: "Mahesh", lname: "boga", age: 24 },
    { id: 3, fname: "jagannath", lname: "patta", age: 24 },
    { id: 4, fname: "arpit", lname: "gupta", age: 24 }
  ]);
  return <>
  <Users users={users}/>
  </>;
}

export default App;
//Users.jsx
import React from "react";
import "./App.css";
import User from "./User";


function Users({users}) {
  return (
    <>
      {
      users.map((user)=>{
            return <User {...user} key={user.id}/>
      })
      }
    </>
  );
}
export default Users
//User.jsx
import "./App.css"

function User({firstname, lastname, age}) {
  return (
    <>
      <div  className="user">
        <p>firstName: {firstname}</p>
        <p>lastName: {lastname}</p>
        <p>age: {age}</p>
      </div>
    </>
  );
}

export default User

Example 2: Increase age and Delete user Using Object

// App.jsx
import { useState } from "react";
import "./App.css";
import Users from "./Users";

function App() {
  const [users, setUser] = useState([
    { id: 1, fname: "vitthal", lname: "korvan", age: 24 },
    { id: 2, fname: "Mahesh", lname: "boga", age: 24 },
    { id: 3, fname: "jagannath", lname: "patta", age: 24 },
    { id: 4, fname: "arpit", lname: "gupta", age: 24 },
  ]);

  const IncreaseAge = (id) => {
    setUser((prevState) => {
      return prevState.map((user) => {
        if (user.id === id) {
          return { ...user, age: user.age + 1 };
        } else {
          return user;
        }
      });
    });
  };

  // const DeleteUser=(id)=>{
  //   setUser((prevState)=>{
  //     return prevState.filter(user=>{
  //       return user.id !== id 
  //     })
  //   })
  // }

  const DeleteUser =(id)=> setUser(prevState => 
        prevState.filter(user => user.id !==id))

  return (
    <>
      <Users users={users} IncreaseAge={IncreaseAge} DeleteUser = 
       {DeleteUser}/>
    </>
  );
}

export default App;
//Users.jsx
import React from "react";
import "./App.css";
import User from "./User";

//Prop Drilling
function Users({users, IncreaseAge, DeleteUser}) {
  return (
    <>
      {/* {users.map((user) => (
        <div key={uuid()} className="user">
          <p>firstName: {user.fname}</p>
          <p>lastName: {user.lname}</p>
          <p>age: {user.age}</p>
        </div>
      ))} */}

      {
      users.map((user)=>{
            return <User {...user} key={user.id} 
            IncreaseAge={IncreaseAge} DeleteUser = {DeleteUser}/>
      })
      }
    </>
  );
}
export default Users
//User.jsx
import "./App.css";

function User({ firstname, lastname, age, id, IncreaseAge, DeleteUse}) 
{
  return (
    <>
      {/* <div key={uuid()} className="user">
        <p>firstName: {userDetail.fname}</p>
        <p>lastName: {userDetail.lname}</p>
        <p>age: {userDetail.age}</p>
      </div> */}
      <div className="user">
        <p>firstName: {firstname}</p>
        <p>lastName: {lastname}</p>
        <p>age: {age}</p>
        <button onClick={() => IncreaseAge(id)}>Increase Age</button>
        <button onClick={() => DeleteUser(id)}>Delete User</button>
      </div>
    </>
  );
}

export default User;

ToDo List App - Todo List

GitHub Link - All About States