This is my index.tsx
import ReactDOM from 'react-dom/client';
import App from './App';
import { PublicClientApplication } from '@azure/msal-browser';
import { MsalProvider } from '@azure/msal-react';
import { msalConfig } from './azureAuthConfig';
const msalInstance = new PublicClientApplication(msalConfig);
const root = ReactDOM.createRoot(document.getElementById('root') as HTMLElement);
root.render(
<MsalProvider instance={msalInstance}>
<App />
</MsalProvider>
);
This is my app.tsx
import { BrowserRouter, Routes, Route } from "react-router-dom"
import './App.css';
import Layout from './components/Layout';
import Home from './components/Home';
import AuthRequired from './components/AuthRequired';
import Usage from "./components/Usage";
import Login from "./components/Login";
export default function App() {
return (
<BrowserRouter>
<Routes>
<Route path="/" element={<Layout />}>
<Route index element={<Home />} />
<Route element={<AuthRequired />} >
<Route path="usage" element={<Usage />} />
</Route>
<Route path="/login" element={<Login />} />
</Route>
</Routes>
</BrowserRouter >
);
}
And this is my Layout
component where I have defined some outlet context.
import React from 'react';
import Header from "./Header"
import Footer from "./Footer"
import { Outlet } from 'react-router-dom';
import { ReportingPeriod, SelectedProjects } from '../types';
import { DefaultReportingPeriod } from '../utils';
export default function Layout() {
// Set Context for the Pollination key
const [pollinationKey, setPollinationKey] = React.useState<string>("")
// Set reporting period to be used on the rest of the app
const [reportingPeriod, setReportingPeriod] = React.useState<ReportingPeriod>({
start: DefaultReportingPeriod().start,
end: DefaultReportingPeriod().end
});
// Set Project names to be used on the rest of the app
const [selectedProjects, setSelectedProjects] = React.useState<SelectedProjects>({});
return (
<>
<Header />
<Outlet
context={{
pollinationKey, setPollinationKey,
reportingPeriod, setReportingPeriod,
selectedProjects, setSelectedProjects
}}
/>
<Footer />
</>
)
}
I see that I can access this context successfully on Login
route but not on the Usage
route which is a protected route. Why would that be?
Here's the error I get when I try to access one of the context values
Uncaught TypeError: Cannot destructure property 'reportingPeriod' of '(0 , react_router_dom__WEBPACK_IMPORTED_MODULE_5__.useOutletContext)(...)' as it is undefined.
I tried the following:
As suggested by documentation, I did create a hook on the Layout component to consume in the Usage component. Didn't work. Same result.
I tried taking the Usage
route out of the protected route just to test. It worked. I was able to access the value.
If I use React's Context API and wrap the BrowserRouter
with a context provider like shown below. It works.
export default function App() {
return (
<AppProvider>
<BrowserRouter>
<Routes>
<Route path="/" element={<Layout />}>
<Route index element={<Home />} />
<Route element={<AuthRequired />} >
<Route path="usage" element={<Usage />} />
</Route>
<Route path="/login" element={<Login />} />
</Route>
</Routes>
</BrowserRouter >
</AppProvider>
);
}
As mentioned above, I would really like to achieve this functionality using the React Router V6's outlet context.
Both Layout
and AuthRequired
should be rendering Outlets
components if implemented normally. The issue would appear to be that you are destructuring reportingPeriod
via useOutletContext
directly in the leaf route component, e.g. Usage
. The useOutletContext
hook accesses the context value of the closest ancestor Outlet
component, e.g. the one provided by AuthRequired
.
You can update AuthRequired
to access any ancestor Outlet
context and forward on down the ReactTree.
Example:
export default function Layout() {
...
return (
<>
<Header />
<Outlet
context={{
pollinationKey,
setPollinationKey,
reportingPeriod,
setReportingPeriod,
selectedProjects,
setSelectedProjects
}}
/>
<Footer />
</>
)
}
const AuthRequired = () => {
const context = useOutletContext(); // <-- any ancestor context value
... AuthRequired business logic ...
return <someCondition>
? (
<Outlet
context={{
...context, // <-- shallow copy ancestor context value
// add any new context value
}}
/>
) : <Navigate to="/login" replace />;
};
export default function App() {
return (
<BrowserRouter>
<Routes>
<Route path="/" element={<Layout />}>
<Route index element={<Home />} />
<Route
element={<AuthRequired />} // <-- consumes Layout outlet context
>
<Route
path="usage"
element={<Usage />} // <-- consumes AuthRequired outlet context
/>
</Route>
<Route path="/login" element={<Login />} />
</Route>
</Routes>
</BrowserRouter >
);
}
const Usage = () => {
const { reportingPeriod } = useOutletContext();
...
};