In React, state and props are two important concepts that control the behavior and data flow of components. If you want to build an interactive UI in React, then it’s crucial to understand these two concepts.
In this post, you’ll learn about the difference between state and props, when to use state vs props, how to use them together, and best practices.
Before we get started, don’t forget to subscribe to my newsletter!
Get the latest tips, tools, and resources to level up your web development skills delivered straight to your inbox. Subscribe here!
Now, let’s jump right into it!🚀
What are Props?
Props (short for Properties) are used to pass data from one component to another component.
Props are written as HTML attributes, and they pass data from the parent component to the child component.
Props are read-only, which means the child component can’t change the props. Props help you to create reusable and dynamic components.
Let’s understand this with an example.
function Welcome(props) {
return <h1>Hello, {props.name}!</h1>;
}
function App() {
return <Welcome name="John" />;
}
Here,
- The name="John" is a prop, which is passed from App (parent) to Welcome (child).
- The value of the props.name will be "John".
Output:
Hello, John!
Destructuring Props (More Readable Code)
Instead of accessing props with props.name
, you can destructure them for cleaner syntax.
For example:
// Instead of this:
function Welcome(props) {
return <h1>Hello, {props.name}!</h1>;
}
// You can write like this:
function Welcome({ name }) {
return <h1>Hello, {name}!</h1>;
}
This is better and more readable than writing props.name
.
What is State?
State is private data of a component, which is managed inside that component.
Whenever the state changes, the component gets re-rendered.
State is used inside functional components with the help of the useState() hook.
Let’s understand this with an example.
import { useState } from "react";
function Counter() {
const [count, setCount] = useState(0);
return (
<div>
<h2>Count: {count}</h2>
<button onClick={() => setCount(count + 1)}>Increment</button>
</div>
);
}
export default Counter;
Here,
-
useState(0)
initializes the count state to 0. -
setCount(count + 1)
updates the state when the button is clicked. - The component re-renders to reflect the new state.
Output:
Count: 0
[Increment Button] (The count gets increasing as you click the button)
Difference Between Props and State
Feature | Props | State |
---|---|---|
Data Flow | Parent ➝ Child | Inside the Component |
Mutability | Immutable | Mutable |
Who Updates? | Parent Component | Component itself |
Re-Render? | On props change | On state change |
How to use Props and State Together?
You can build more powerful and dynamic components by combining props and state.
For example: Taking the data from the parent component and managing the state
import { useState } from "react";
function Greeting({ initialName }) {
const [name, setName] = useState(initialName);
return (
<div>
<h2>Hello, {name}!</h2>
<button onClick={() => setName("React Developer")}>Change Name</button>
</div>
);
}
function App() {
return <Greeting initialName="John" />;
}
Here,
-
initialName
is passed as a prop from the App (parent) to the Greeting (child). -
name
is managed as a state, which changes on the user interaction and allows changes inside the component.
Output:
Hello, John!
[Change Name Button]
(On button click: Hello, React Developer!)
When to Convert Props to State?
You can convert props to state when:
- You need to modify props inside a component (e.g., form input values or user interactions).
- You want to maintain specific data inside a component.
You should not convert props to state when:
- You want data only for display purposes, without modifying them.
- The parent component should control the data.
Props Drilling and Its Solution
When props are passed at multiple levels inside a component, then this is called props drilling.
For example:
function Grandparent() {
return <Parent message="Hello from Grandparent" />;
}
function Parent({ message }) {
return <Child message={message} />;
}
function Child({ message }) {
return <h2>{message}</h2>;
}
Problem:
- The message prop is passed from Grandparent ➝ Parent ➝ Child.
- This makes components harder to manage.
Solution:
To avoid props drilling, use React Context API or useReducer. This will manage shared data efficiently.
Best Practices for Using Props
1. Use Default Props to Prevent Undefined Values
You can set the default values to prevent errors when a prop isn’t passed.
For example:
function Welcome({ name = "User" }) {
return <h1>Hello, {name}!</h1>;
}
Now, if the name isn’t passed, you will get "User" instead of undefined.
2. Validate Props with PropTypes
You can use PropTypes to ensure components receive the correct data types.
import PropTypes from "prop-types";
function Profile({ name, age }) {
return <h1>{name} is {age} years old.</h1>;
}
Profile.propTypes = {
name: PropTypes.string.isRequired, // Must be a string and required
age: PropTypes.number, // Must be a number (optional)
};
- Helps catch errors early when passing props.
- Ensures proper data handling.
3. Avoid Unnecessary Props Drilling
If props are passed through multiple components, use Context API instead.
Bad Practice (Props Drilling):
function Grandparent() {
return <Parent message="Hello from Grandparent" />;
}
function Parent({ message }) {
return <Child message={message} />;
}
function Child({ message }) {
return <h2>{message}</h2>;
}
Better: Use Context API
import { createContext, useContext } from "react";
const MessageContext = createContext();
function Grandparent() {
return (
<MessageContext.Provider value="Hello from Grandparent">
<Parent />
</MessageContext.Provider>
);
}
function Parent() {
return <Child />;
}
function Child() {
const message = useContext(MessageContext);
return <h2>{message}</h2>;
}
- No need to manually pass
message
at every level. - Easier to manage shared data.
Best Practices for Using State
1. Keep State Local When Possible
Define the state in the component that needs it instead of lifting it unnecessarily.
Bad Practice: Unnecessary State Lifting
function Parent() {
const [count, setCount] = useState(0);
return <Child count={count} setCount={setCount} />;
}
function Child({ count, setCount }) {
return <button onClick={() => setCount(count + 1)}>Click Me</button>;
}
Better: Keep State in Child if Parent Doesn’t Need It
function Child() {
const [count, setCount] = useState(0);
return <button onClick={() => setCount(count + 1)}>Click Me</button>;
}
2. Avoid Re-rendering by Structuring State Properly
Keep the state minimal to prevent unnecessary re-renders.
Bad Practice: Storing Derived Data in State
function Counter() {
const [count, setCount] = useState(0);
const [doubleCount, setDoubleCount] = useState(count * 2);
return <h2>{doubleCount}</h2>;
}
Here, doubleCount
can be derived from count
, so you don’t need to store it separately.
Better: Derive Data from State Instead
function Counter() {
const [count, setCount] = useState(0);
return <h2>{count * 2}</h2>;
}
3. Use Functional Updates When Depending on the Previous State
When you need to update the state based on the previous state, then always use a function.
Bad Practice: Directly Using Current State Value
setCount(count + 1); // Won't increment twice
Better: Use Functional Update to Ensure Correct State
setCount(prevCount => prevCount + 1); // Will increment twice
This ensures that the correct state is updated when multiple updates happen in one render cycle.
4. Optimize Performance with useMemo and useCallback
Whenever the state changes, it causes unnecessary re-renders; you can optimize this with useMemo and useCallback.
Use useMemo to Cache Expensive Computations:
import { useState, useMemo } from "react";
function ExpensiveCalculation({ number }) {
const squared = useMemo(() => {
console.log("Calculating...");
return number * number;
}, [number]);
return <h2>Squared: {squared}</h2>;
}
Use useCallback to Prevent Unnecessary Re-renders:
import { useState, useCallback } from "react";
function Button({ onClick }) {
return <button onClick={onClick}>Click Me</button>;
}
function App() {
const [count, setCount] = useState(0);
const handleClick = useCallback(() => {
setCount(prev => prev + 1);
}, []);
return <Button onClick={handleClick} />;
}
- useMemo: Prevents expensive calculations from running unnecessarily.
- useCallback: Prevents function recreation on every render.
🎯Wrapping Up
That’s all for today!
For paid collaboration connect with me at : connect@shefali.dev
I hope this list helps you. 🚀
If you found this post helpful, here’s how you can support my work:
☕ Buy me a coffee – Every little contribution keeps me motivated!
📩 Subscribe to my newsletter – Get the latest tech tips, tools & resources.
𝕏 Follow me on X (Twitter) – I share daily web development tips & insights.
Keep coding & happy learning!
Top comments (0)