Search code examples
react-routernested-routesoutlet

React router 6 pass properties from parent to children


I am not a frontend expert, so excuse any stupidity in my question. So, the question is, how to organise router config so that props from parent component FieldFormats, which is just a list of available FieldFormat instances, to a child component FieldFormat using Outlet. This is what I have at the moment (just an extract of full routerConfig):

{
            path: '/fieldformats',
            handle: {
                crumb: () => <Link to="/fieldformats">Field Formats</Link>
            },
            children: [
                {
                    index: true,
                    element: <FieldFormats />,
                    loader: async () => {
                        return getFieldFormats();
                    },
                },
                {
                    path: '/fieldformats/:id',
                    element: <FieldFormat />,
                    loader: async ({params}) => {
                        return params;
                    },
                    handle: {
                        crumb: (params) => <span>{params['id']}</span>
                    }
                }
            ]
          }

So, when going to /fieldformats the app renders FieldFormats and uses the data from getFieldFormats API call function executed in loader prop. When going to /fieldformats/138, it renders FieldFormat. So how would I arrange it differently to be able to provide data to FieldFormat with Outlet in parent FieldFormats ?

Tried creating FieldFormatsWrapper and putting it at the top and then pass on data to children FieldFormats and FieldFormat, but that way FieldFormat never got rendered.


Solution

  • If you would like the getFieldFormats loader function to load data and be available on both the "/fieldformats" index and "/fieldformats/:id" routes then the loader should be lifted to the common ancestor route, e.g. the "/fieldformats" layout route.

    Using Layout Route and Outlet Context

    From here you could use the useLoaderData hook and pass the data down on the Outlet component's provided context where nested route components would then use the useOutletContext hook to access the data. An example implementation could look like the following:

    import {
      Outlet,
      useLoaderData,
    } from 'react-router-dom';
    
    const FieldFormatsLayout = () => {
      const data = useLoaderData();
    
      return <Outlet context={{ data }} />;
    };
    
    {
      path: '/fieldformats',
      loader: getFieldFormats,
      element: <FieldFormatsLayout />,
      children: [
        {
          index: true,
          element: <FieldFormats />,
          handle: {
            crumb: () => <Link to="/fieldformats">Field Formats</Link>
          },
        },
        {
          path: ':id',
          element: <FieldFormat />,
          loader: async ({ params }) => {
            return params;
          },
          handle: {
            crumb: (params) => <span>{params['id']}</span>
          }
        }
      ]
    }
    
    const FieldFormats = () => {
      const { data } = useOutletContext();
    
      ...
    };
    
    const FieldFormat = () => {
      const { data } = useOutletContext();
    
      ...
    };
    

    Using Layout Route Loader and useRouteLoaderData hook

    An alternative would be still move the getFieldFormats loader to the parent "/fieldformats" layout route, and just directly access the route data using the useRouteLoaderData hook in the nested route components. Example implementation:

    {
      id: 'fieldformats', // <-- route id
      path: '/fieldformats',
      loader: getFieldFormats,
      children: [
        {
          index: true,
          element: <FieldFormats />,
          handle: {
            crumb: () => <Link to="/fieldformats">Field Formats</Link>
          },
        },
        {
          path: ':id',
          element: <FieldFormat />,
          loader: async ({ params }) => {
            return params;
          },
          handle: {
            crumb: (params) => <span>{params['id']}</span>
          }
        }
      ]
    }
    
    import { useRouteLoaderData } from "react-router-dom";
    
    const FieldFormats = () => {
      const data = useRouteLoaderData("fieldformats"); // <-- route id
    
      ...
    };
    
    import { useRouteLoaderData } from "react-router-dom";
    
    const FieldFormat = () => {
      const data = useRouteLoaderData("fieldformats"); // <-- route id
    
      ...
    };