Is it ok to pass setState as a prop in React?

Is it ok to pass setState as a prop in React?

No, it's not

It's possible to pass setState as a prop in React. However, it's not the recommended way to deal with updating state triggered by child components.

  • Passing down setState makes it hard to reason about the state of the component.

  • State management should be encapsulated within the component that owns the state.

To show an example of bad code:

function ParentComponent() {
  const [count, setCount] = useState(0);

  return <ChildComponent setCount={setCount} />;
}

function ChildComponent({ setCount }) {
  return (
    <button onClick={() => setCount((prevCount) => prevCount + 1)}>
      Increment
    </button>
  );
}

The callback approach

A more common and recommended approach is to pass a callback to the child component and let it call the callback when the action happens.

It's a much cleaner approach:

Abstraction: You decide what should happen. The component only knows about the callback, not how it's implemented.

Flexibility: If you need more logic than just setState, you can include it in the callback that you pass down to the child component.

Reusability: The component can be reused in different contexts. It's not tied to the parent component.

Let's take a look at the good example:

function ParentComponent() {
  const [count, setCount] = useState(0);

  const incrementCount = () => {
    setCount((prevCount) => prevCount + 1);
  };

  return <ChildComponent onIncrement={incrementCount} />;
}

function ChildComponent({ onIncrement }) {
  return <button onClick={onIncrement}>Increment</button>;
}

Here we don't pass down setCount as prop. Instead, the child component accepts onIncrement as prop and calls it when the button is clicked. We have control over what happens, and we can add more logic to the callback if we want.

What about state in Context?

When it comes to dealing with React's Context API, it's recommended that the Context.Provider should contain the useState hooks. This ensures it's a single source of truth for the state.

This is much better than passing down state and setState as props to the Provider from a parent component.

Example of how good code looks like:

import React, { useState, useContext, createContext } from "react";

const CountContext = createContext();

function CountProvider({ children }) {
  const [count, setCount] = useState(0);

  const increment = () => {
    setCount((prevCount) => prevCount + 1);
  };

  return (
    <CountContext.Provider value={{ count, increment }}>
      {children}
    </CountContext.Provider>
  );
}

// Consumer component
function Counter() {
  const { count, increment } = useContext(CountContext);
  return (
    <div>
      <p>{count}</p>
      <button onClick={increment}>Increment</button>
    </div>
  );
}

// App component
function App() {
  return (
    <CountProvider>
      <Counter />
    </CountProvider>
  );
}