skip to Main Content

I want to store React component in react useState but i think it is technically is wrong.
What you think about that.

Example

 const LayoutContext = createContext(null as unknown as {
  setSidebarContent: React.Dispatch<React.SetStateAction<null | React.ReactNode>>;
})

const Layout = () => {
  const [sidebarContent, setSidebarContent] = useState<null | React.ReactNode>(
    null,
  );

  return (
    <LayoutContext.Provider value={{
      setSidebarContent
    }}>

  <div>
    <Sidebar>
      {sidebarContent}
    </Sidebar>
   <div className='page-container'>
      <Suspense fallback={<div>Loading...</div>}>
          <Outlet
      </Suspense>
    </div>
     
  </div>
  </LayoutContext.Provider>)

};

in this example with LayoutContext i provide setter for sidebar content, want to know if will some problem with that approach, and are there any other apporach to set content from child components

2

Answers


  1. An important distinction needs to be made here: a component is a function of props, state and context which returns an element. What you are storing there in that context is an element. An element is an object of shape specified by React (you can try to console.log it to see it for yourself).

    Since you can store both functions and objects in state and context, you can totally store components and elements there too. Also, you can pass them as props.

    Given a state:

    const [sidebarContent, setSidebarContent] = useState<null | React.ReactNode>(
        null,
      );
    

    And a component

    function MyComponent(props) {
      console.log('MyComponent props', props)
      return (
        <div>MyComponent</div>
      );
    }
    

    Storing a component in state:

    setSidebarContent(MyComponent);
    
    //usage:
    <Sidebar>
      <MyComponent prop1={someProp1} prop2={someProp2}/>
    </Sidebar>
    

    Storing an element in state (as you did):

    //somewhere in a handler or in an effect
    setSidebarContent(<MyComponent prop1={someProp1} prop2={someProp2}/>); 
    
    //usage:
    <Sidebar>
      {sidebarContent}
    </Sidebar>
    

    Passing as props:

    <Sidebar
      contentAsComponent={MyComponent}
      contentAsElement={<MyComponent prop1={someProp1} prop2={someProp2}/>}
    />
    

    The major difference is about where you set the props.

    Login or Signup to reply.
  2. @lorweth333 is technically correct, but there are serious pitfalls to watch out for –

    function App() {
      const [state, setState] = React.useState(<Counter />)
      
      return <div>
        {state} click the counter ✅
        <hr />
        <button onClick={() => setState(<Counter />)} children="A" />
        <button onClick={() => setState(<Counter init={10} />)} children="B" />
        <button onClick={() => setState(<Counter init={100} />)} children="C" />
        change the counter ❌
      </div>
    }
    
    function Counter(props) {
      const [state, setState] = React.useState(props.init || 0)
      return <button onClick={() => setState(_ => _ + 1)} children={state} />
    }
    
    ReactDOM.createRoot(document.querySelector("#app")).render(<App />)
    <script crossorigin src="https://unpkg.com/react@18/umd/react.development.js"></script>
    <script crossorigin src="https://unpkg.com/react-dom@18/umd/react-dom.development.js"></script>
    <div id="app"></div>

    The program runs, but changing from one component state to another does not work as we might expect. React detects that state in App changed, but when {state} is rendered, React doesn’t know that is a new Counter component, as distinct from any other. So the same counter renders and the state stays the same.

    If you re-key the element containing the component stored in state, you can prompt a fresh Counter to be initialized. This is obviously a hack and should be avoided. This demo is only here to give better insight on how React "sees" things –

    function App() {
      const [state, setState] = React.useState(<Counter />)
      
      return <div key={Math.random() /* hack ❌ */ }>
        {state} click the counter ✅
        <hr />
        <button onClick={() => setState(<Counter />)} children="A" />
        <button onClick={() => setState(<Counter init={10} />)} children="B" />
        <button onClick={() => setState(<Counter init={100} />)} children="C" />
        change the counter ✅
      </div>
    }
    
    function Counter(props) {
      const [state, setState] = React.useState(props.init || 0)
      return <button onClick={() => setState(_ => _ + 1)} children={state} />
    }
    
    ReactDOM.createRoot(document.querySelector("#app")).render(<App />)
    <script crossorigin src="https://unpkg.com/react@18/umd/react.development.js"></script>
    <script crossorigin src="https://unpkg.com/react-dom@18/umd/react-dom.development.js"></script>
    <div id="app"></div>

    Now that we can see with React Vision, we can understand why adding a unique key to each Counter component would also "fix" the problem –

    function App() {
      const [state, setState] = React.useState(<Counter key={0} />)
      
      return <div>
        {state} click the counter ✅
        <hr />
        <button onClick={() => setState(<Counter key={0} />)} children="A" />
        <button onClick={() => setState(<Counter key={1} init={10} />)} children="B" />
        <button onClick={() => setState(<Counter key={2} init={100} />)} children="C" />
        change the counter ✅
      </div>
    }
    
    function Counter(props) {
      const [state, setState] = React.useState(props.init || 0)
      return <button onClick={() => setState(_ => _ + 1)} children={state} />
    }
    
    ReactDOM.createRoot(document.querySelector("#app")).render(<App />)
    <script crossorigin src="https://unpkg.com/react@18/umd/react.development.js"></script>
    <script crossorigin src="https://unpkg.com/react-dom@18/umd/react-dom.development.js"></script>
    <div id="app"></div>

    While I haven’t seen this explicitly called out as an anti-pattern in the React docs, as seen above, it has potential to hide some bad bugs in your program. For this reason, I think you should avoid storing components as state.

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