Search code examples
javascriptreactjsreduxreact-routerreact-router-dom

How to pass props to children class components in react router v6 without using <Outlet />?


I've been migrating React Router from v3 to v6 in a project and things got messy since I have some React function components and some React class component which I want to keep them as classes for performance reasons. I would like to inform that my project is built with React Redux. I have migrated the routes and it has been working very well. With the last version of routing in my app component which I use selectors with mapStateToProps and this way I am able to make those props available to all the children as you can see below.

const childrenWrapper = React.Children.map(props.children, child => {
  return React.cloneElement(child, {
    currentOrganization: currentOrganization,
    organizationSlug: organizationSlug,
    eventSlug: eventSlug,
    isPreview: isPreview,
    user: user,
  });
});

const mapStateToProps = createStructuredSelector({
  user: selectUser(),
  currentOrganization: selectCurrentOrganization(),
  isLoaded: selectLoaded(),
  organizationCount: selectOrganizationCount()
});

This was working like a charm but with the router v6 I am only able to use <Outlet /> to render the children and if I want to pass data to children I use it by defining props like <Outlet context={{ currentOrganization: currentOrganization }} and in the component if want to use this I simply use useOutletContext and get the data passed through <Outlet />. If everything was a function component I wouldn't need to ask this here since it is quite straight forward. I am wondering how can I tackle this problem of passing props to children with class components without using Outlet (anyway it is not working in the class components). I tried to do it with context api but by creating a separate component to and wrap the <Outlet /> with that context.provider but children still didn't get any of those props defined in AppContext. So my question is how could I pass the props to the class components while using router v6. I changed my App.js component to function component already therefore I am able to use React Router v6.


Solution

  • If I'm understanding correctly, your question isn't about passing props to a routed component, e.g. <Route path="..." element={<SomeComponent thisIsAPassedProp={....} />} /> but rather you are using a layout route and passing a context value down via an Outlet and need to access that value from a React class-based component.

    A trivial solution would be to create a custom Higher Order Component that can use the useOutletContext hook and pass the value as props.

    Example:

    import { useOutletContext } from 'react-router-dom';
    
    export const withOutletContext = Component => props => {
      const contextValue = useOutletContext();
    
      return (
        <Component
          {...props}                  // <-- all other passed props
          contextValue={contextValue} // <-- the context value
        />
      );
    };
    

    Decorate the exported React class components that should be able to access this context value.

    import { Component } from 'react';
    import { compose } from 'redux';
    import { connect } from 'react-redux';
    import { withOutletContext } from '../path/to/withOutletContext';
    
    class MyComponent extends Component {
      ...
    
      someFunction = () => {
        const { contextValue } = this.props;
        console.log(contextValue.currentOrganization);
        ...
      };
    
      ...
    }
    
    const mapStateToProps = state => ({
      ...
    });
    
    export default compose(
      connect(mapStateToProps),
      withOutletContext,
    )(MyComponent);
    

    Now the decorated components will be able to access their nearest Outlet's provided context value.

    <Routes>
      ...
      <Route element={<LayoutRoute />}> {/* Provides Outlet context value */}
        ...
        <Route path="..." element={<MyComponent />} /> {/* Receives Outlet context value as props */}
        ...
      </Route>
    </Routes>