Function components within function components

Declaring a function component within another function component leads to all sorts of trouble. don’t do it. A few examples of the symptoms your component may be having are:

My input loses focus every time I type!

import { useState } from "react";

export default function App() {
  const [state, setState] = useState("");
  // don't do this.
  const Input = () => (
    <input value={state} onChange={(e) => setState(e.target.value)} />
  );
  return (
    <div>
      <Input />
    </div>
  );
}

My component loses state for no reason!

import { useState } from "react";

export default function App() {
  const [buttonState, buttonStateSet] = useState(0);
  // don't do this.
  const Component = () => {
    const [state, setState] = useState("");
    return <input value={state} onChange={(e) => setState(e.target.value)} />;
  };
  return (
    <div>
      {buttonState}
      <Component />
      <button onClick={() => buttonStateSet(buttonState + 1)}>
        State gets deleted
      </button>
    </div>
  );
}

So, why does this happen?

This has to do with the way react renders. Every time you change the state in a component, the component declaring the state will rerun their own function. Then, for all the returned component instances, it will compare their identity during the current render loop to the one of the the previous render loop. If they are different, the previous one will get dismounted; and the current one mounted.

If you declare functions within functions, the reference of the child functions will never remain stable across different runs of the parent function. This is because running the function will re-declare all variables that are declared within that function. Thus, they will have entirely new places in memory. In terms of code, this may look like this:

function parent() {
  return function child() {};
}
parent() === parent(); // returns false

What should you do instead?

Extract the component to the top level scope. This way the reference of the component will stay the same throughout its lifetime.

import { useState } from "react";

// Put the component in the top level of the file.
// Use props to pass state.
const Input = ({ state, setState }) => (
  <input value={state} onChange={(e) => setState(e.target.value)} />
);

export default function App() {
  const [state, setState] = useState("");
  return (
    <div>
      <Input state={state} setState={setState} />
    </div>
  );
}

import { useState } from "react";

// Put the component in the top level of the file.
const Component = () => {
  const [state, setState] = useState("");
  return <input value={state} onChange={(e) => setState(e.target.value)} />;
};

export default function App() {
  const [buttonState, buttonStateSet] = useState(0);
  return (
    <div>
      {buttonState}
      <Component />
      <button onClick={() => buttonStateSet(buttonState + 1)}>
        State gets deleted
      </button>
    </div>
  );
}

Lifting stateReact: Functional Components