Search code examples
javascriptcssreactjsemotionreach-router

Assign style and props to each nested children


I want to selectively pass some props to each children element and style each of them.

const Burger = ({children, ...rest}) => (
  <>
   <p>I'm a burger...</p>
   {children}
  </>
)
const Meat = ({type}) => <span>with {type?type:'no'} meat</span>
const Sauce = ({flavor}) => <span>with {flavor?flavor:'no'} sauce</span>
const Veggie = ({green}) => <span>with {green?green:'no'} greens</span>

const App = () => (
  // reach-router nesting
  <Router>
    <Burger path='burger' {...props}>
      <Sauce path='sauce' />
      <Veggie path='veggie' />
      <Meat path='meat' />
    </Burger>
  </Router>
)

I noticed I cannot simply iterate through and pass the props to each child

const Burger = ({children, ...rest}) => (
   <p>I'm a burger... </p>
   {children.map(Child => <Child {...rest} />) } // error
)

Using a render function doesn't work inside <router>. It breaks nested routing and I don't enjoy having to manually type in the props for each children if the numbers of children are indefinite.

...
  <Router>
    <Burger path='burger' {...props}>
    {someStyle => (
      <>
        <Sauce path='sauce' style={someStyle}/>
        <Veggie path='veggie' style={someStyle}/>
        <Meat path='meat' style={someStyle}/>
      </>
    )}
    </Burger>
  </Router>
...

next, I went ahead and tried using css-in-js solution (emotion) to select and style each direct descendant of <Burger/> but this also doesn't seem to work in this case.

const styleAllChild = css`
  & > * : {
    margin-bottom: 10px;
  }
`
...
  <Router>
    <Burger path='burger' css={styleAllChild} {...props}>
      <Sauce path='sauce' />
      <Veggie path='veggie' />
      <Meat path='meat' />
    </Burger>
  </Router>
...

Lastly, I find the solution of using children.map in conjuction with React.cloneElement unsatisfying since there should be some performance implications if each children is too big or too nested.

Is there another way to implement what I want? Thank you.


Solution

  • Your first solution should work. You have to use only one component in the return statement. So I added the <> (shorthand for Fragments). And you should use double curly braces for destructuring (the first for using js code in the jsx the second for object destructuring syntax).

    const Burger = ({children, ...rest}) => (
      <>
       <p>I'm a burger... </p>
       {children.map(Child => <Child {{...rest}} />) } // error
      </>
    )