I have an app that has a 3 JSX files:
one for controlling states
import React from "react";
import CreateRisk from "./CreateRisk";
import TablePage from "./TablePage";
import { Link } from "react-router";
const CommonAncestor = () => {
// Lazy initialize state from localStorage
const [allRisks, setAllRisks] = React.useState(() => {
return JSON.parse(localStorage.getItem("allrisks")) || [];
});
// Side-effect to persist state to localStorage
React.useEffect(() => {
localStorage.setItem("allrisks", JSON.stringify(allRisks));
}, [allRisks]);
// Handler to update state
const addRisk = (risk) => {
setAllRisks((allRisks) => allRisks.concat(risk));
};
return (
<>
...
<CreateRisk addRisk={addRisk} />
...
<TablePage rows={allRisks} />
...
</>
);
};
export default CommonAncestor;
one for creating items
import { nanoid } from "nanoid";
import { Link } from "react-router";
function CreateRisk({ addRisk }) {
function handleSubmission() {
addRisk({
id: nanoid(),
riskname: document.getElementById("rname").value,
riskdesc: document.getElementById("rdesc").value,
riskimpact: document.getElementById("rimpact").value,
test: "test",
});
}
return (
<div>
<input type="text" id="rname" placeholder="Risk Name" />
<input type="text" id="rdesc" placeholder="Risk Description" />
<input type="text" id="rimpact" placeholder="Risk Impact" />
<button onClick={handleSubmission}>Store my data</button>
</div>
);
}
export default CreateRisk;
one for viewing created items as a table.
import { Link } from "react-router";
function TablePage({ rows }) {
return (
<div>
<h1>TablePage</h1>
<div>{JSON.stringify(rows)}</div>
</div>
);
}
export default TablePage;
And the main App
and index files:
import "./styles.css";
import { Link } from "react-router";
import CommonAncestor from "./CommonAncestor";
export default function App() {
return (
<div className="App">
<h1>Home Page</h1>
<Link to="/">Home -</Link>
<Link to="/CreateRisk">Create Risk -</Link>
<Link to="/TablePage">Table Page -</Link>
<CommonAncestor />
</div>
);
}
import { StrictMode } from "react";
import { createRoot } from "react-dom/client";
import { createBrowserRouter, RouterProvider } from "react-router";
import CreateRisk from "./CreateRisk.jsx";
import TablePage from "./TablePage.jsx";
import CommonAncestor from "./CommonAncestor.jsx";
import App from "./App";
const router = createBrowserRouter([
{
path: "/",
element: <App />,
},
{
path: "/CommonAncestor",
element: <CommonAncestor />,
},
{
path: "/CreateRisk",
element: <CreateRisk />,
},
{
path: "/TablePage",
element: <TablePage />,
},
]);
createRoot(document.getElementById("root")).render(
<StrictMode>
<RouterProvider router={router} />
</StrictMode>
);
Here is a code sandbox: https://codesandbox.io/p/sandbox/flamboyant-roman-forked-8hw8x2
When viewing the controlling files component(1), the functionality of adding items and viewing the table works. When I go directly to the pages for creating items / viewing the table the functionality does not. Why is this?
You've defined all the state and handlers in CommonAncestor
and pass them down as props to CreateRisk
and TablePage
directly, and this works so long as you are on the route that renders CommonAncestor
, e.g. the "/"
home page route. The problem then is that when you navigate to one of the other routes rendering CreateRisk
and TablePage
the props are no longer passed.
Colocate the state and handlers again to a "common ancestor" to be passed down to the descendent components. In this case treat App
as the common ancestor and convert it to a layout route component where the state and handlers can be passed via a React context, the Outlet
component's context in this case.
Example:
App
import React from "react";
import { Link, Outlet } from "react-router";
export default function App() {
const [allRisks, setAllRisks] = React.useState(() => {
return JSON.parse(localStorage.getItem("allrisks")) || [];
});
// Side-effect to persist state to localStorage
React.useEffect(() => {
localStorage.setItem("allrisks", JSON.stringify(allRisks));
}, [allRisks]);
// Handler to update state
const addRisk = (risk) => {
setAllRisks((allRisks) => allRisks.concat(risk));
};
return (
<div className="App">
<h1>Home Page</h1>
<Link to="/">Home -</Link>
<Link to="/CreateRisk">Create Risk -</Link>
<Link to="/TablePage">Table Page -</Link>
<Outlet context={{ allRisks, addRisk }} />
</div>
);
}
CreateRisk
import { nanoid } from "nanoid";
import { useOutletContext } from "react-router";
import { Link } from "react-router";
function CreateRisk() {
const { addRisk } = useOutletContext();
function handleSubmission() {
addRisk({
id: nanoid(),
riskname: document.getElementById("rname").value,
riskdesc: document.getElementById("rdesc").value,
riskimpact: document.getElementById("rimpact").value,
test: "test",
});
}
return (
<div>
<input type="text" id="rname" placeholder="Risk Name" />
<input type="text" id="rdesc" placeholder="Risk Description" />
<input type="text" id="rimpact" placeholder="Risk Impact" />
<button onClick={handleSubmission}>Store my data</button>
</div>
);
}
export default CreateRisk;
TablePage
import { useOutletContext } from "react-router";
function TablePage() {
const { allRisks } = useOutletContext();
return (
<div>
<h1>TablePage</h1>
<div>{JSON.stringify(allRisks)}</div>
</div>
);
}
export default TablePage;
Update the router to nest the other routes as children routes under "/"
.
const router = createBrowserRouter([
{
path: "/",
element: <App />,
children: [
{
index: true,
element: <CommonAncestor />,
},
{
path: "/CreateRisk",
element: <CreateRisk />,
},
{
path: "/TablePage",
element: <TablePage />,
},
],
},
]);
createRoot(document.getElementById("root")).render(
<StrictMode>
<RouterProvider router={router} />
</StrictMode>
);
App
contains the state and passes it down to nested children routes via the Outlet
context, and the nested route components access the values via the useOutletContext
hook.