Giorgi Moniava
Giorgi Moniava

Reputation: 28654

Can I put react component inside state?

Can't find any recent official info if any of the three options below is allowed?

  constructor(props) {
    this.state = {
      item: <SomeItem />,
      item1: () => <SomeItem />,
      item2: SomeItem,
    };
  }

I found this answer but it references an old link from web archive which says:

What Shouldn’t Go in State? ... React components: Build them in render() based on underlying props and state.

But that link doesn't say why that is a bad idea, if it will introduce bugs, etc.

Upvotes: 4

Views: 627

Answers (4)

davnicwil
davnicwil

Reputation: 30957

This is a really good question.

The reason that putting components in state is advised against is just that it goes fundamentally against the React model, which is that a component provides a render method (which is a pure function) that the React engine uses to automatically update the DOM to reflect the values of the component's props and state.

The output of that render, i.e. the React Element, is supposed to be used directly by the React engine. The contract is that your app, and all its components, generate a bunch of Elements in a pure way for the React engine to manage.

By doing things like introducing side effects in render, or putting the Elements in state, you're essentially breaking the 'pure' contract and it may give unpredictable results, which may or may not be considered bugs in your application. The specifics of the bugs may even change with different versions of React, with different engine implementations. The point is that you're breaking the React contract, so whilst it may work in some cases, it also may not in others or even the same cases as React itself changes. The behaviour is not guaranteed.

React has built-in ways to cache renders based on prop values, like React.memo, that the engine provides and understands, and are part of the contract. If you want to cache render output for performance reasons, this is the way to do it.

Indeed, this is exactly why such functions are provided by the React API rather than just letting you do it yourself.

Upvotes: 3

T.J. Crowder
T.J. Crowder

Reputation: 1074335

At the end of the day, React component instances are just objects, and you can store objects in state, so it shouldn't cause any trouble if you avoid pitfalls. One such pitfall is that if you're creating handlers to put on their props, those handlers will close over the context in which they're created, which may lead to some unexpected outcomes. Here's an example of that:

const {useState, Fragment} = React;

function Thingy({onClick}) {
    return <div onClick={onClick}>A</div>;
}

// STALE CLOSURE
function Example() {
    const [value, setValue] = useState(0);
    const [comp, setComp] = useState(
        <Thingy onClick={() => { console.log("A: value = " + value); }} />
    );
    
    const handler = () => {
        setValue(v => {
            ++v;
            console.log("B: value = " + v);
            return v;
        });
    };
    
    return <Fragment>
        {comp}
        <div onClick={handler}>B</div>
    </Fragment>;
}

ReactDOM.render(<Example />, document.getElementById("root"));
<div id="root"></div>

<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.13.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.13.0/umd/react-dom.production.min.js"></script>

This is the classic stale closure thing. It's probably a bit easier to do accidentally using functional components and hooks (as I did there) rather than class components, but it's definitely possible to do with class components as well.

But if you're not doing that (either not creating functions for the component you're storing, or creating ones that don't use anything they close over that may change), it should be fine.

But look at React.memo, which may be a better answer depending on what your reason for wanting to put component instances in state is.

Upvotes: 1

jnnnnn
jnnnnn

Reputation: 4353

You can do it, but it's a bit strange. A React element is an object like any other. In this case, it will be the result of a method call:

// `<SomeItem/>` compiles to 
React.createElement(SomeItem, null);
// A similar `React.createElement("div", null)` becomes
const el = {
  $$typeof: Symbol(react.element),
  key: null,
  props: {},
  ref: null,
  type: "div",
  _owner: null,
}

It's strange because it's unnecessary (and a little confusing). You can just generate the element (including any state or props updates) whenever you need it.

There's also a risk that you break one of the core guarantees of React: elements are immutable. Storing the element like this gives you a chance to mutate it and thus confuse React.

If you need many copies of the same element then it may be slightly more performant to keep it like this, especially if it is expensive to generate.

Upvotes: 0

Vagan M.
Vagan M.

Reputation: 463

You can do something like this, if I understand you right

const Title = () => {
  return <h1>Hello CodeSandbox</h1>;
};

class App extends React.Component {
  state = {}
  constructor(props) {
    super(props)
    this.state = {
      item: function() {
        return <Title />;
      }
    };
  }
  render() {
    return (
      <div className="App">
        {this.state.item()}
        <h2>Start editing to see some magic happen!</h2>
      </div>
    );
  }
}

export default App;

Upvotes: 0

Related Questions