4

I'm trying to make a system where my App component's state stores another component, which it will render. Any component should be able to update this state, forcing the App component to re-render.

function Page1() {
    return <p>Hello world!</p>
}

export default function App() {

    let [Page, setPage] = useState(Page1);

    return (
        <div id="app">
            <Page/>
        </div>
    );
}

I know how to make the state accessible by any component, but I'm having some issues with rendering the component in App's state. When I try running the code above, I get the following error:

Error: Element type is invalid: expected a string (for built-in components) or a class/function (for composite components) but got: object.

Strangely enough, making the state a JSON object seems to fix the issue:

let [Page, setPage] = useState({Page: Page1});

    return (
        <div id="app">
            <Page.Page/>
        </div>
    );

But I was wondering if there was a way to do this without the JSON. It seems to work fine with numbers and strings; what's different about components?

  • Why would you store a component in the state? Honestly, looks like an antipattern to avoid. If you need navigation between pages, use react-router – Terminat Jan 21 '21 at 12:24

2 Answers2

9

This is because the argument of useState can be one of two types: either a direct value, or a function that returns a value. The function that returns a value is called Lazy initial state.

For example:

useState('my value');
useState(() => 'my value');

In this case of passing a React component directly, it attempts to set the initial state to the return value of the component and not the component itself. This is what's causing it to throw the error.

Essentially, it's trying to do this:

useState(() => <p>Hello world!</p>);

The solution? Easy, just return your component from inside this function:

useState(() => Page1);
Andrew Hughes
  • 91
  • 1
  • 3
  • 1
    This is actually the correct answer. This is perfectly fine to do, you just shouldn't do it all over the place. You can store components _and even functions_ in useState, simply wrap it with an arrow function. – Scott Cornwell Dec 01 '22 at 20:44
7

Actually, you shouldn't put any react component inside state variable. About this point, you can check this site or this similar question to read more details.

Now, to make this work you can just use an object to map the components you need:

function Page1() {
    return <p>Hello world!</p>
}

function Page2() {
    return <p>Hello world!</p>
}

const components = {
 'page1': Page1,
 'page2': Page2,
}

export default function App() {

    let [page, setPage] = useState('page1');

    return (
        <div id="app">
            <components.page />
        </div>
    );
}

In this way, you just have to change the object key string in the state and it should reload you printed component.

Mauricio Lima
  • 171
  • 1
  • 5
  • What if one is retrieving the component asynchronously on the first place? so Page1 comes via an async call, and it's not available otherwise.. – trainoasis Nov 17 '22 at 13:17
  • 2
    what about adding props to the component? Page1, and Page2 may have different props passed down – jeffci Feb 02 '23 at 15:33
  • The first link is broken. Could you elaborate on why is it a bad idea to store a component in the state? What if component is getting loaded dynamically and you need to wait for it to load before rendering? – Slava Fomin II Jun 22 '23 at 17:33