The most straightforward way would be to move the title
prop to the MainContent
layout wrapper and wrap each route individually, but you'll lose the nested routing.
An alternative could be to create a React context to hold a title
state and use a wrapper component to set the title.
const TitleContext = createContext({
title: "",
setTitle: () => {}
});
const useTitle = () => useContext(TitleContext);
const TitleProvider = ({ children }) => {
const [title, setTitle] = useState("");
return (
<TitleContext.Provider value={{ title, setTitle }}>
{children}
</TitleContext.Provider>
);
};
Wrap the app (or any ancestor component higher than the Routes
component) with the provider.
<TitleProvider>
<App />
</TitleProvider>
Update MainContent
to access the useTitle
hook to get the current title
value and render it.
function MainContent() {
const { title } = useTitle();
return (
<div>
<h1>{title}</h1>
<div>
<Outlet />
</div>
</div>
);
}
The TitleWrapper
component.
const TitleWrapper = ({ children, title }) => {
const { setTitle } = useTitle();
useEffect(() => {
setTitle(title);
}, [setTitle, title]);
return children;
};
And update the routed components to be wrapped in a TitleWrapper
component, passing the title
prop here.
<Route path="/projects" element={<MainContent />}>
<Route
index
element={
<TitleWrapper title="Projects">
<ProjectList />
</TitleWrapper>
}
/>
<Route
path="create"
element={
<TitleWrapper title="Create Project">
<CreateProject />
</TitleWrapper>
}
/>
</Route>
In this way, MainContent
can be thought of as UI common to a set of routes whereas TitleWrapper
(you can choose a more fitting name) can be thought of as UI specific to a route.
Update
I had forgotten about the Outlet
component providing its own React Context. This becomes a little more trivial. Thanks @LIIT.
Example:
import { useOutletContext } from 'react-router-dom';
const useTitle = (title) => {
const { setTitle } = useOutletContext();
useEffect(() => {
setTitle(title);
}, [setTitle, title]);
};
...
function MainContent() {
const [title, setTitle] = useState("");
return (
<div>
<h1>{title}</h1>
<div>
<Outlet context={{ title, setTitle }} />
</div>
</div>
);
}
...
const CreateProject = ({ title }) => {
useTitle(title);
return ...;
};
...
<Router>
<Routes>
<Route path="/projects" element={<MainContent />}>
<Route index element={<ProjectList title="Projects" />} />
<Route
path="create"
element={<CreateProject title="Create Project" />}
/>
</Route>
</Routes>
</Router>
<Routes>
instance in each URL-dependent section instead of an<Outlet>
– Brindabrindell