A curious case of lifting state in react

By akash_rawal, created: 2026-06-11, last modified: 2026-06-11

Every other web UI framework has embraced signals to precisely re-render only the components that need to re-render. React hasn't, but curiously, React has doubled down on lifting state with useMemo, and now React compiler. What are they thinking?

The problem

In React, whenever a parent component needs to control a child component, React documentation recommends lifting state into parent component and passing the state down into child component. However this causes the parent component to re-render whenever child component needs to change, even when there is no change in parent component itself. Whenever child component changes, the code of the parent component re-runs, along with the entire component subtree.

Other web UI frameworks, e.g Preact, Svelte, SolidJS, Leptos and even Angular, solve this problem using signals, which only trigger re-render on the component that needs to change.

React Docs

See react's own example of accordion here


                       <Accordion/>
                            |
                            |
               ----------------------------
               |                          |
               |                          |
               V                          V
            <Panel/>                   <Panel/>
        {isActive: true}           {isActive: false}

The problem being discussed is: If another panel is being opened, the already open panel must be closed... so that at any time only one panel is open, ever. React documentation proposal is to convert isActive state to a prop, add a state to parent component, and derive isActive for each <Panel/> from that state.


                         <Accordion/>
                          {open: 0}
                              |
                              |
               ------------------------------
               |                            |
               |                            |
               V                            V
    <Panel isActive={true}/>     <Panel isActive={false}/>     

If a panel needs to be opened and another panel needs to be closed, only the opening panel and the closing panel needs to re-render, but that's just me I guess. React's answer is that every single panel along with the Accordion container and all other child components must re-render.

Is it really that hard?

So I have been thinking, how do I keep the state in the child component, make it accessible in some ways to the parent component, and still keep the components that need to re-render to the absolute minimum?

It was easy. In and out. 20 minute adventure.

The trick involves creating a binding object in parent component, and re-assigning the functions within child component.

class PanelCloser {
	close() {}
	bind(fn: () => void) {
		this.close = fn;
	}
}

All I have to do is to pass this object to each panel, and it becomes a way to send a message to currently opened panel. Only two panels need to re-render: the opening one and the closing one.

function Panel({ title, closer, children }: {
	title: string, 
	closer: PanelCloser, 
	children: React.ReactNode
}) {
  const [isActive, setIsActive] = useState(false);

  function open() {
    //Close currently opened panel
	closer.close();

    //Open this panel
	setIsActive(true);

    //Re-assign the function so that this component gets called
    //whenever closer.close is called
	closer.bind(() => setIsActive(false));
  }

  return (
    <section className="panel">
      <h3>{title}</h3>
      {isActive ? (
        <p>{children}</p>
      ) : (
        <button onClick={open}>
          Show
        </button>
      )}
    </section>
  );
}

function Accordion() {
  const closer = new PanelCloser();
  //^ This is all the setup needed in the parent component. 
	
  return (
    <>
      <h2>Almaty, Kazakhstan</h2>
      <Panel title="About" closer={closer}>
        With a population of about 2 million, Almaty is Kazakhstan's largest 
        city. From 1929 to 1997, it was its capital city.
      </Panel>
      <Panel title="Etymology" closer={closer}>
        The name comes from <span lang="kk-KZ">алма</span>, the Kazakh word for
        "apple" and is often translated as "full of apples". In fact, the region
        surrounding Almaty is thought to be the ancestral home of the apple, and
        the wild <i lang="la">Malus sieversii</i> is considered a likely 
        candidate for the ancestor of the modern domestic apple.
      </Panel>
    </>
  );
}

Prior art

I looked around, and found that I am not the only one who ever thought this way. There is WebDevCody who tried to assign an object to a parent component's Ref in child component. There is another who used useImperativeHandle hook to expose an object via a Ref.

However React docs discourage usage of exposing functions from components.

If you can express something as a prop, you should not use a ref. For example, instead of exposing an imperative handle like { open, close } from a Modal component, it is better to take isOpen as a prop like <Modal isOpen={isOpen} />. Effects can help you expose imperative behaviors via props.