DEV Community

Cover image for State and Props in React: The Core Building Blocks Explained
Shefali
Shefali

Posted on • Originally published at shefali.dev

7

State and Props in React: The Core Building Blocks Explained

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" />;
}
Enter fullscreen mode Exit fullscreen mode

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!
Enter fullscreen mode Exit fullscreen mode

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>;
}
Enter fullscreen mode Exit fullscreen mode

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;
Enter fullscreen mode Exit fullscreen mode

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)

Enter fullscreen mode Exit fullscreen mode

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" />;
}
Enter fullscreen mode Exit fullscreen mode

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!)
Enter fullscreen mode Exit fullscreen mode

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>;
}
Enter fullscreen mode Exit fullscreen mode

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>;
}
Enter fullscreen mode Exit fullscreen mode

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)
};
Enter fullscreen mode Exit fullscreen mode
  • 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>;
}
Enter fullscreen mode Exit fullscreen mode

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>;
}
Enter fullscreen mode Exit fullscreen mode
  • 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>;
}
Enter fullscreen mode Exit fullscreen mode

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>;
}
Enter fullscreen mode Exit fullscreen mode

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>;
}
Enter fullscreen mode Exit fullscreen mode

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>;
}
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

Better: Use Functional Update to Ensure Correct State

setCount(prevCount => prevCount + 1); // Will increment twice
Enter fullscreen mode Exit fullscreen mode

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>;
}
Enter fullscreen mode Exit fullscreen mode

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} />;
}
Enter fullscreen mode Exit fullscreen mode
  • 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)

👋 Kindness is contagious

Explore a trove of insights in this engaging article, celebrated within our welcoming DEV Community. Developers from every background are invited to join and enhance our shared wisdom.

A genuine "thank you" can truly uplift someone’s day. Feel free to express your gratitude in the comments below!

On DEV, our collective exchange of knowledge lightens the road ahead and strengthens our community bonds. Found something valuable here? A small thank you to the author can make a big difference.

Okay