react.dev icon indicating copy to clipboard operation
react.dev copied to clipboard

Clarify React’s definition of purity

Open gmoniava opened this issue 4 months ago • 2 comments

I have some questions about purity in React that I couldn’t find answers to in the docs. It would be helpful if the docs clarified these points and/or someone answered them here.

My first question is, sometimes you may see such code:

import { useState } from "react";

export default function Demo() {
  const [value, setValue] = useState(() => localStorage.getItem("someKey") || "");
  return <div>{value}</div>;
}

I have seen similar code in popular libraries. But react requires the initializer function to be pure.

So my first question is: is this case allowed?

Clarifying idempotence

The docs about purity say:

One of the key concepts that makes React, React is purity. A pure component or hook is one that is:

Idempotent – You always get the same result every time you run it with the same inputs – props, state, context for component inputs; and arguments for hook inputs.

Well, imho above example of initializer could be considered idempotent by above definition, if that definition assumes single mount. Because for single mount, above initializer function will return the same value for each re-render. But across multiple mounts, the returned value by the initializer function may be different. So does this definition apply for single mount or multiple?

Actually, there is even code example in the docs which suggests following approach to fix idempotency issue:

function useTime() {
  // 1. Keep track of the current date's state. `useState` receives an initializer function as its
  //    initial state. It only runs once when the hook is called, so only the current date at the
  //    time the hook is called is set first.
  const [time, setTime] = useState(() => new Date());
  //...

But again, across multiple mounts, this would produce different output. So is the idempotency about single mount?

No side effects in render: does this example violate it?

Finally, even if we assume for a moment that initial example with localStorage is acceptable under the idempotence rule, is there some other rule that would make it impure? For example, that same section from docs says there should be no side effects in render:

Has no side effects in render – Code with side effects should run separately from rendering. For example as an event handler – where the user interacts with the UI and causes it to update; or as an Effect – which runs after render.

Someone could say my first code example from above is idempotent for single mount, but it violates another rule ("Has no side effects in render" rule) by reading localStorage in useState initializer. Would they be right? Or we could respond that in this case since the initializer runs once, it is ok if it not strictly pure (assuming it is idempotent for some definition of idempotency).

gmoniava avatar Sep 15 '25 18:09 gmoniava

Thanks for bringing this up!

I also did an edit to the react-hooks/purity ESLint rule docs Troubleshooting code example in a PR, which brings up how hydration and impure functions interact:

  • https://github.com/reactjs/react.dev/pull/8029

karlhorky avatar Oct 01 '25 17:10 karlhorky

Thanks for your comment @karlhorky

I could not go through your pull request in details yet, but indeed the example you mention there (and which I also mention in issue):

const [time, setTime] = useState(() => Date.now());

Could cause hydration issue and this is related to the question in my gh issue about idempotence (see part about "Clarifying idempotence"). This code seems to be idempotent for a single mount. And that's what I was asking to clarify whether it should be idempotent under single or multiple mounts. Current docs do not mention this aspect.

Separately, there’s the question of side effects (as discussed in the last part of my gh issue). Even if we assume the initializer is idempotent for a single mount, reading from localStorage might count as a side effect in render. I found the docs say:

“As long as calling a component multiple times is safe and doesn’t affect the rendering of other components, React doesn’t care if it’s 100% pure in the strict functional programming sense of the word. It is more important that components must be idempotent.”

I’m unclear what “safe” means here. Is reading localStorage in a useState initializer considered safe?

It would be helpful if someone clarified all this.

gmoniava avatar Oct 02 '25 17:10 gmoniava