I want to share state between two routes when I click on the link for one of the routes (NewUser
). The state that I want to share and the logic modifying it are both held in the Users
route. I want to pass the logic to change the state to the NewUsers
route.
When I pass a string to the state object in router Link
, I am able to access it in the NewUsers
component. However, I get null
when I pass a function.
I know that I can use context/redux, but I would prefer if I can do it this way.
Users
route:
function Users() { const [users, setUsers] = useState([]); return ( <Card sx={{ padding: "2rem", mt: "2rem" }}> <MDBox display="flex" flexDirection="row" justifyContent="space-between" > <MDTypography variant="body2">{`You currently have ${users.length} users`}</MDTypography> <MDButton variant="gradient" color="info" size="small"> <Link to="/settings/users/new-user" state={setUsers: setUsers}> //this is how I want to pass the state <MDBox display="flex" alignItems="center" color="white" fontWeight="normal" > <Icon>add</Icon> Add New User </MDBox> </Link> </MDButton> </MDBox> </Card>
NewUsers
route:
function NewUser({history}) { const location = useLocation(); const saveChanges = (e) => { location.state.setUsers({ fname: values.firstName, lname: values.lname, email: values.email, }); navigate("/settings/users"); }; return( <MDBox py={3} mb={20} height="62vh"> <Grid container justifyContent="center" alignItems="center" sx={{ height: "100%", mt: 0 }} > <Grid item xs={12} lg={12}> <Formik initialValues={initialValues} validationSchema={currentValidation} onSubmit={(values) => { setValues(values); }} > {({ values, errors, touched, isSubmitting }) => ( <Form id={formId} autoComplete="off"> <Card sx={{ height: "100%", width: "100%" }}> <MDBox px={3} py={4}> <MDBox display="flex"> <ButtonWrapper fullWidth={false} handleClick={saveChanges} > Save Changes </ButtonWrapper> </MDBox> <MDBox> {getStepsContent({ values, touched, formField, errors, })} </MDBox> </MDBox> </Card> </Form> )} </Formik> </Grid> </Grid> </MDBox> ) }
Routing code:
{ type: "collapse", name: "Settings", key: "settings", icon: <Icon fontSize="small">settings</Icon>, collapse: [ { name: "Users", key: "users", route: "/settings/users", // icon: <Icon fontSize="small">users</Icon>, component: <Users />, }, { name: "Companies", key: "companies", route: "/settings/companies", component: <Companies />, }, { name: "Billing", key: "billing", route: "/settings/billing", component: <Billing />, }, { name: "Integrations", key: "integrations", route: "/settings/integrations", component: <Integrations />, }, ], }, { name: "New User", key: "new user", route: "/settings/users/new-user", noCollapse: true, component: <NewUser />, }, { type: "collapse", name: "Sign Out", key: "signout", route: "/sign-out", icon: <Icon fontSize="small">logout</Icon>, component: <SignOut />, noCollapse: true, }, ];
function that renders the routes:
const getRoutes = (allRoutes) => allRoutes.map((route) => { if (route.collapse) { return getRoutes(route.collapse); } if (route.route) { return <Route exact path={route.route} element={route.component} key={route.key} />; } return null; }); <Routes> {getRoutes(routes)} {/* <Route path="*" element={<Navigate to="/dashboard" />} /> */} <Route path="*" element={<Console />} /> </Routes>
Advertisement
Answer
The state value sent via the Link
component needs to be JSON serializable. Javascript functions are not serializable. Instead of trying to pass a function through to a target component I recommend lifting the state up to a common ancestor so the state and callback function is accessible to both components.
I would suggest using a React context to hold the users
state and provide out the state value and an updater function to add a user object. react-router-dom
has a “built-in” way to do this via a layout route component that renders an Outlet
component that wraps nested routes.
Example:
import { Outlet } from 'react-router-dom'; const UsersProvider = () => { const [users, setUsers] = useState([]); const addUser = (user) => { setUsers((users) => users.concat(user)); }; return <Outlet context={{ users, addUser }} />; };
…
<Routes> ... <Route path="/settings/users" element={<UsersProvider />}> <Route index element={<Users />} /> <Route path="new-user" element={<NewUser />} /> </Route> ... </Routes>
Users
const Users = () => { const { users } = useOutletContext(); return ( <Card sx={{ padding: "2rem", mt: "2rem" }}> <Box display="flex" flexDirection="row" justifyContent="space-between"> <Typography variant="body2"> You currently have {users.length} users </Typography> <Button variant="gradient" color="info" size="small"> <Link to="/settings/users/new-user"> <Box display="flex" alignItems="center" color="white" fontWeight="normal" > <Icon>add</Icon> Add New User </Box> </Link> </Button> </Box> </Card> ); };
NewUser
function NewUser({history}) { const navigate = useNavigate(); const { addUser } = useOutletContext(); const saveChanges = (e) => { addUser({ fname: values.firstName, lname: values.lname, email: values.email, }); navigate("/settings/users"); }; return( <MDBox py={3} mb={20} height="62vh"> <Grid container justifyContent="center" alignItems="center" sx={{ height: "100%", mt: 0 }} > <Grid item xs={12} lg={12}> <Formik initialValues={initialValues} validationSchema={currentValidation} onSubmit={(values) => { setValues(values); }} > {({ values, errors, touched, isSubmitting }) => ( <Form id={formId} autoComplete="off"> <Card sx={{ height: "100%", width: "100%" }}> <MDBox px={3} py={4}> <MDBox display="flex"> <ButtonWrapper fullWidth={false} handleClick={saveChanges} > Save Changes </ButtonWrapper> </MDBox> <MDBox> {getStepsContent({ values, touched, formField, errors, })} </MDBox> </MDBox> </Card> </Form> )} </Formik> </Grid> </Grid> </MDBox> ) }