React JS - All about States
Essential State Concepts for Creating Powerful React Apps
Table of contents
- State
- Some Applications
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
oruseCallback
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 thetext
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 withtrue
.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:
Performance issues: React has to re-render the entire list every time the keys change, which can be slow and inefficient.
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