'react-router-dom v6 StaticRouter context is not working

I'm trying to make SSR React web application. Everything works fine except staticContext.

My server code is

// IMPORTS

const renderer = (req, store, context) => {
    const content = renderToString(
        <Provider store={store}>
            <ThemeProvider theme={responsiveFontSizes(theme)}>
                <CssBaseline />
                <StaticRouter location={req.path} context={context}>
                    <Navigation />
                    <main>
                        {renderRoutes(MainRouter)}
                    </main>
                    <Footer />
                </StaticRouter>
            </ThemeProvider>
        </Provider>
    );

    const helmet = Helmet.renderStatic();

    return `
        <!DOCTYPE html>
        <html>
            <head>
                <meta charset="utf-8" />
                <link rel="icon" href="${process.env.REACT_APP_URL}/favicon.ico" />
                <meta name="viewport" content="width=device-width, initial-scale=1" />
                <meta name="theme-color" content="${process.env.REACT_APP_THEME_COLOR}" />
                ${helmet.title.toString()}
                ${helmet.meta.toString()}
                <link rel="stylesheet" href="main.css">
                <link rel="preconnect" href="https://fonts.googleapis.com">
                <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
                <link href="https://fonts.googleapis.com/css2?family=Roboto:wght@300;400;500;700&display=swap" rel="stylesheet">
            </head>
            <body>
                <noscript>Pre dokonalé fungovanie tejto webovej aplikácie povoľte JavaScript.</noscript>
                <div id="root">${content}</div>
                <script>
                    window.INITIAL_STATE = ${serialize(store.getState())}
                </script>
                <script src="bundle.js"></script>
            </body>
        </html>
    `;
};

app.get('*', (req, res) => {

    const store = createStore();

    var promises = matchRoutes(MainRouter, req.path).map(route => {
    return route?.loadData ? route.loadData(store) : [];
    });

    promises.push(loadData(store, req.cookies));

    Promise.all(promises).then(() => {
        const context = {};
        const content = renderer(req, store, context);

        if(context.url) {
            return res.redirect(302, context.url);
        }

        if(context.notFound) {
            console.log('404');
            res.status(404);
        };

        res.send(content);
    });
});

And my client code of 404 component is

const NotFound = ({ staticContext = {} }) => {
    staticContext.notFound = true;

    const head = () => (
        <Helmet>
            <title>{process.env.REACT_APP_NAME} – Stránka sa nenašla</title>
        </Helmet>
    );

    return (
        <React.Fragment>
            {head()}
            <section>
                {/* Some content */}
            </section>
        </React.Fragment>
    )
};

export default {
    Component: NotFound
};

This line should pass notFound = true argument into the request:

staticContext.notFound = true;


And so this part of code should set request status to 404 and console log '404' string:

if(context.notFound) {
    console.log('404');
    res.status(404);
};

But it doesn't work. Is it possible that my code is deprecated? I'm using currently latest stable versions of all dependencies.

My package.json dependencies

"@emotion/react": "^11.7.1",
"@emotion/styled": "^11.6.0",
"@mui/icons-material": "^5.2.5",
"@mui/material": "^5.2.5",
"axios": "0.24.0",
"babel-cli": "6.26.0",
"babel-core": "6.26.3",
"babel-loader": "8.2.3",
"compression": "1.7.4",
"concurrently": "6.5.1",
"cookie-parser": "^1.4.6",
"cors": "^2.8.5",
"date-fns": "^2.27.0",
"express": "4.17.2",
"lodash": "4.17.21",
"nodemon": "2.0.15",
"npm-run-all": "4.1.5",
"react": "17.0.2",
"react-dom": "17.0.2",
"react-helmet": "6.1.0",
"react-redux": "^7.2.6",
"react-router-dom": "6.2.1",
"react-spinners": "^0.11.0",
"redux": "^4.1.2",
"serialize-javascript": "6.0.0",
"universal-cookie": "^4.0.4",
"webpack": "5.65.0",
"webpack-dev-server": "4.7.1",
"webpack-merge": "5.8.0",
"webpack-node-externals": "3.0.0"


Solution 1:[1]

Is it possible that my code is deprecated?

I'm afraid it is.

Here is one of the major changes of v6.0.0-alpha.4:

Removed the <StaticRouter context> API. We don't support navigation on the initial render in v6, so this API is unnecessary.

I ended up storing the status code and the redirect URL in the Redux store and checking them on the render server after rendering.

For the status code I dispatch the setStatus(404) action in the loadData function of the NotFound page component.

And this is my solution for the redirect URL:

// router.js

import {useRoutes} from 'react-router-dom';

import getRoutes from '@client/router/routes';
import {useAuthSelector} from '@client/store/slices/auth';

export default ({ssr = false}) => {
  const {currentUser} = useAuthSelector();

  return useRoutes(getRoutes(currentUser, ssr));
};
// routes.js

import requireAuth from './requireAuth';
import App from '@client/App';
import Home from '@client/pages/Home';
import Users from '@client/pages/Users';
import Admins from '@client/pages/Admins';
import NotFound from '@client/pages/NotFound';

export default (currentUser = false, ssr = false) => {
  const protectedRoute = requireAuth(currentUser, ssr);

  return [
    {
      ...App,
      children: [
        {
          path: '/',
          ...Home,
        },
        {
          path: '/users',
          ...Users,
        },
        {
          path: '/admins',
          ...protectedRoute(Admins),
        },
        {path: '*', ...NotFound},
      ],
    },
  ];
};
// requireAuth.js

import React from 'react';
import {Navigate} from 'react-router-dom';

import {setRedirectUrl} from '@client/store/slices/http';

export default (currentUser, ssr = false) => (component) => {
  switch (currentUser) {
    case false:
      if (ssr) {
        // If the user is not logged in and the SSR is happening
        const loadData = component.loadData;

        component.loadData = (store) => {
          // Call the original loadData if component has one
          loadData && loadData(store);
          store.dispatch(setRedirectUrl('/'));
        };
      } else {
        // If the user is not logged in
        component.element = <Navigate to='/' replace />;
      }
      break;

    case null:
      component.element = <div>Loading...</div>;
      break;

    default:
      return component;
  }

  return component;
};

When I use my router on the render server, I just set the ssr prop to true.

Another approach that comes to mind is to use a React context instead of the Redux store.

Solution 2:[2]

I had a generic import error with StaticRouter for SSR and landed on this page from Google (when pasted the error). In case anyone else has a similar issue, it appears that in react-router-dom v6 the StaticRouter should now be imported from react-router-dom/server.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1
Solution 2 M.Calugaru