Search code examples
reactjstypescriptjsxreact-functional-component

Nested component as argument in React FC / JSX


I have this structure in React FC typescript:

import { BaseLayout } from "./...."
import { MainContent } from "./...."
import { SplitContent } from "./...."
import { VideoFeed } from "./...."
...

<Route path="/foo" element={<BaseLayout ContentComponent={MainContent}/>}

That works fine.

However, one of the child component is supposed to be another nested component, and I can't seem to make it work.

<Route path="/foo" element={<BaseLayout ContentComponent={MainContent}/>}

MainContent takes no props/arguments, but SplitContent expects something like:

export function SplitContent({TopContent}: {TopContent: FC}) { ... }

(I'm just trying to make it work, obv it'll later take a 2nd bottom component as argument but let's just make it work with one first).

So if I try to just pass the SplitContent as component, not a string, TS complains:

<Route path="/foo" element={<BaseLayout ContentComponent={<SplitContent/>}/>}

"Type Element isn not assignable to type FC<{}>"

(On top of SplitContent not receiving an expected TopContent argument).

If I suppress it with ts-ignore, then in the console it doesn't render and I get:

" React.jsx: type is invalid -- expected a string (for built-in component) or a class/function (for composite component) but got: <SplitContent/> "

Solution

  • There is a difference between a component (a function/class which might have state, and is likely to return some elements) and rendered elements (<Foo />). In this case, what you need to provide is a component. Not a rendered element:

    <Route
      path="/foo"
      element={
        <BaseLayout
          ContentComponent={() => (
            <WrapperComponent WrappedComponent={SomeConcreteComponent} />
          )}
        />
      }
    />
    

    notice that

    () => <WrapperComponent WrappedComponent={SomeConcreteComponent}/>
    

    is a component which in turn, renders something. It's not the rendered thing.

    I'd suggest not making it in render though, if possible. Create it outside of your main component as a new component. And use it here. Like:

    const MyComponent = () => <WrapperComponent WrappedComponent={SomeConcreteComponent}/>;
    
    // And inside your main component
    <Route
      path="/foo"
      element={
        <BaseLayout
          ContentComponent={MyComponent}
        />
      }
    />