Skip to content
Advertisement

How to pass a function to state in react router v6

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>&nbsp; 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>
              &nbsp; 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>
    )
}

Edit how-to-pass-a-function-to-state-in-react-router-v6

7 People found this is helpful
Advertisement