skip to Main Content

Now, change the value of m (TheDemo‘s state), the structure of component tree changed, and the v (TheChild‘s state) will reset to init value 3.

What I want is: while change the value of m, keep the value of v unchanged.

https://codesandbox.io/s/cool-waterfall-if1v0z?file=/index.js

import React, { StrictMode, useState } from "react";
import { createRoot } from "react-dom/client";

function TheDemo() {
  const [m, setM] = useState(true);
  const child = <TheChild />;
  return (
    <div style={{ fontFamily: "monospace" }}>
      <button onClick={() => setM(!m)}>m = {+m}</button>
      {m ? <b>{child}</b> : <span>{child}</span>}
    </div>
  );
}

function TheChild() {
  const [v, setV] = useState(3);
  return <button onClick={() => setV(v + 1)}>v = {v}</button>;
}

const root = createRoot(document.getElementById("root"));
root.render(
  <StrictMode>
    <TheDemo />
  </StrictMode>
);

Maybe this a X-Y problem? The origin problem is I want to allow user to change page layout , move a child_0 from parent_1 to parent_2.

The demo is simplified, I have a lot of nested children components and lift up all state is nearly impossible.

Tao’s comment solved this question, and the react docs link here.

2

Answers


  1. To keep the value of v unchanged while changing the value of m, you can lift the state of TheChild component up to the TheDemo component and pass it down as props. Here’s the modified code:

    import React, { StrictMode, useState } from "react";
    import { createRoot } from "react-dom/client";
    
    function TheDemo() {
      const [m, setM] = useState(true);
      const [v, setV] = useState(3); // Lift the state up
      const child = <TheChild v={v} setV={setV} />; // Pass the state down as props
      return (
        <div style={{ fontFamily: "monospace" }}>
          <button onClick={() => setM(!m)}>m = {+m}</button>
          {m ? <b>{child}</b> : <span>{child}</span>}
        </div>
      );
    }
    
    function TheChild({ v, setV }) { // Receive the state from props
      return <button onClick={() => setV(v + 1)}>v = {v}</button>;
    }
    
    const root = createRoot(document.getElementById("root"));
    root.render(
      <StrictMode>
        <TheDemo />
      </StrictMode>
    );
    

    Now, when you change m, the value of v will remain unchanged because it’s managed by the parent component TheDemo. This way, you can also change the page layout while keeping the state of child components unchanged.

    Login or Signup to reply.
  2. As pointed out in Preserving and Resetting State, React associates a component’s state with its position in DOM and its HTML tag (or, in more general terms, with the DOM tree structure). If one of those changes, the state is reset in the changed branches 1. In your case, the position is kept, but the HTML tag is changed.

    The simplest way to fix it (in this case) would be to keep the HTML <span> tag and change its style from {} (or null) to { fontWeight: 'bold' } based on m‘s truthiness:

    <span style={ m ? {fontWeight: 'bold'} : null }>{child}</span>
    

    If you’re looking for a more generic mechanism (and this was just a simplified example), you have several other options to persist state in React:

    • lift the state into a parent component which doesn’t re-render when m changes and pass the state down through props
    • use React.context. Same principle as lifting state, except you can access the state using a hook no matter how many levels deep, without having to do prop drilling (adding the prop to each intermediary parent)
    • use a state management solution. Jotai, Recoil, XState, Redux, Rematch, Zustand (the list is not comprehensive). I’ve placed Jotai first because you literally just need to replace React.useState() with Jotai.useAtom() and you’re good to go 2.
      All state management solutions have a way to set a boundary on the context using that state (typically a Provider), so you can have multiple instances of the same state (e.g: in a list rendering example, where you’d want each list item and all of its children to use their own context).

    1 – there is an exception: you can change the position in DOM without losing state by using :key (and keeping the same tag/component)
    2 – at least in the case you presented; more complex data sharing might require more complex solutions; I’d say Jotai is good at hiding its own complexity, but does have a few gotchas.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search