Router `<Set>`s in Redwood 0.28.0

One (of the many!) new things included in Redwood v0.28 is a new component you can use in your Routes.js file: <Set>

<Set> allows you to group a set (get it? :laughing:) of routes and wrap them, using the wrap prop, in a layout, or a context, or whatever you need.

Taking an example from the Redwood tutorial, if you previously had a HomePage and a AboutPage that both did something like this

import BlogLayout from 'src/layouts/BlogLayout'

// ...

const HomePage = () => {
  return (
    <BlogLayout>
      <p>This is the page content</p>
    </BlogLayout>
  )
}

you would move the import statement and the BlogLayout to the routes file instead

import { Router, Route, Set, Private } from '@redwoodjs/router'
import BlogLayout from 'src/layouts/BlogLayout'

const Routes = () => {
  return (
    <Router>
      <Set wrap={BlogLayout}>
        <Route path="/about" page={AboutPage} name="about" />
        <Route path="/" page={HomePage} name="home" />
      </Set>
      <Route notfound page={NotFoundPage} />
    </Router>
  )
}

export default Routes

This fits conceptually very well with how we think about layouts as something that’s wrapping a page and contains any content that’s outside of the page itself.

And as an added bonus your layouts won’t be re-rendered as soon as a page is re-rendered, or even when going from one page to another that’s in the same <Set>.

Wrapping with multiple components

In the example above the wrap prop took a single layout as its value. If you want to wrap more components around your routes, it will also accept an array: wrap={[BlogLayout, PageContext]}

4 Likes

That’s cool.

So if I follow the logic, for a Layout that receives props, the only option is to use a context?

Are the components generated in the array’s initial natural order?

There are a few options

  • Context is one of them.
  • If you want something from the url, you can use useParams() inside your layout
  • If it’s something static you can inline a function <Set wrap={({children}) => <MyLayout propOne="une">{children}</MyLayout>}>

Yes, <Set wrap={[CustomWrapper, GlobalLayout]}><Route ... /></Set> will be

<CustomWrapper>
  <GlobalLayout>
    <Route ... />
  </GlobalLayout>
</CustomWrapper>

Thanks for your question, let me know if there’s anything else you want to know :slight_smile:

1 Like

Well, thanks for your answer! :slight_smile:

I can migrate some of my pages to this new feature, but not all straight away: my layout has a breadcrumbs prop and an actions placeholder. It’s very cool to have those options though, and for my specific case I think I have no choice but to look for Context - which is cool.

Thanks a lot \o/

This sounds like a perfect use-case for a context with some custom hooks. Something like

const { pushBreadcrumb } = useBreadcrumbs()

on pages/in cells where you want to add stuff to your breadcrumb. And then in your layout you can have

const { breadcrumbs } = useBreadcrumbs()

return (
  <ul>
    {breadcrumbs.map((breadcrumb) => <li>{breadcrumb} »</li>)}
  </ul>
)

(with some special handling for the last one, to not show »)

What’s your action placeholder? How do you use that? What is it?

My thoughts as well :stuck_out_tongue:

I’ll try hook before context, great idea, I’m reluctant to play with contexts x).

My actions placeholder is a spot at the same level as the breadcrumbs.
We’re building an application for school management, on the pages where we edit a Teacher, a Student, a Customer, a Session, we systematically have a button at the top to let us delete the entity we’re currently editing.
On some of those pages we have extra buttons, so we expect these to be dynamic :).

( there’s a preprod coming as soon as I manage to fix my netlify deploy, you’d see in context how we use the actions placeholder :slight_smile: )

No, you misunderstood. You’ll need a context to back that hook :slight_smile:

Erf x) allright :), I’ll check it out once v0.28.0 is shipped

There’s a PR on the way that adds another option - any prop you add to <Set> is passed to the layout (and all other components specified in wrap)

2 Likes

ah great! interesting!

@Tobbe
Eventually did it and migrated all appropriate routes to <Set />, also did what we discussed with a hook and context to handle my breadcrumbs and actions :), it :rocket: high!

4 Likes

Great that it worked out for you, and thanks for reporting back :slight_smile:

1 Like

What ‘kind’ of context can be used in the set attribute? (I don’t think it’s the React hook useContext as I don’t see any mention of Providers/Consumers.)

I’ve searched through the docs and forums, but besides GQL related results, I didn’t find much about context and I don’t understand how to integrate the context needed for the breadcrumbs example above.

I think that could be extremely useful, but I actually ended up here after trying to display the current page’s title in the Layout component. Is there a way to add additional properties to a Route element in web/src/Routes.js that could be consumed in the parent layout? (I tried: useParams, useLocations, useRouterState.)

1 Like

There are quite a few contexts to keep track of, but this one is React context. The Provider/Consumer is kind of implied, but to make things more explicit, in @Tobbe’s breadcrumbs example, the Set component would have the BreadcrumbsProvider:

const BreadcrumbsContext = React.createContext()

const BreadcrumbsProvider = ({ children }) => {
  const [breadcrumbs, setBreadcrumbs] = React.useState()
  return (
    <BreadcrumbsContext.Provider value={{ breadcrumbs, setBreadcrumbs }}>
      {children}
    </BreadcrumbsContext.Provider>
  )
}

const Routes = () => {
  // Routes...
  <Set wrap={BreadcrumbsProvider}>
    // Routes...
  </Set>
}

You’ve got to be careful using context like this though. Context is better for dependency management—just passing something down the tree. Once you start mutating context, you get a lot of rerenders and might blow away your state further down the tree.

3 Likes

Passing information from the pages up to layouts is a very common thing to want to do. And I’ve been using context for that without issue in a lot of places. But if you do start noticing performance issues (I haven’t) Dom is right that contexts do often result in more re-renders than you might at first realize.

Thanks for asking. If more people start asking about this, maybe we should be thinking of a way to provide a built in solution for this.

2 Likes

Thank you both for the quick and informative replies. I have it wired up and I’m working on testing it out. I don’t think it’ll get very complicated for this addition, but I’ll definitely keep an eye on the re-renders.

3 Likes