javascriptreactjsreact-router-domformik-material-ui

How to send useFormik submit data through React-router-dom's Link


I am trying to send the form data generated using useFormik and yup to other component through react router dom's 'Link' but I am encountering 2 problems here.

Problem no. 1: I am trying to pass the useFormik form data "values" into a state but the first time I click the submit button it is sending empty state and on next click it is passing the data. So I am not able to get hold of the data into a state and pass the state into the Link.

This is My Home page which is a form:

    import React, { useState } from "react";
import { Stack, Paper, TextField, Button, Typography } from "@mui/material";
import { useFormik } from "formik";
import * as yup from "yup";
import { Link } from "react-router-dom";

const validationSchema = yup.object({
  location: yup.string("Enter Location").required("Location is required"),
  adults: yup.number("Enter number of Adults").required("Adult is required"),
  currency: yup.string("Enter Currency").required("Currency is required"),
});

const Home = () => {

const [data, setData] = useState({});

  const formik = useFormik({
    initialValues: {
      location: "",
      adults: "",
      children: "",
      infants: "",
      pets: "",
      currency: "",
    },
    validationSchema: validationSchema,
    onSubmit: (values) => {
            setData(values)
      console.log(JSON.stringify(values, null, 2));
    
    
    },
  });
  return (
    <Stack>
      <Paper elevation={3} sx={{ maxWidth: 700, height: 500 }}>
        <form onSubmit={formik.handleSubmit}>
          <TextField
            name="location"
            value={formik.values.location}
            onChange={formik.handleChange}
            id="outlined-basic"
            label="Location"
            variant="outlined"
            error={formik.touched.location && Boolean(formik.errors.location)}
            helperText={formik.touched.location && formik.errors.location}
          />
          <TextField
            name="adults"
            value={formik.values.adults}
            onChange={formik.handleChange}
            type="number"
            id="outlined-basic"
            label="Adults"
            variant="outlined"
            error={formik.touched.adults && Boolean(formik.errors.adults)}
            helperText={formik.touched.adults && formik.errors.adults}
          />
          <TextField
            name="children"
            value={formik.values.children}
            onChange={formik.handleChange}
            type="number"
            id="outlined-basic"
            label="Children"
            variant="outlined"
          />
          <TextField
            name="infants"
            value={formik.values.infants}
            onChange={formik.handleChange}
            type="number"
            id="outlined-basic"
            label="Infants"
            variant="outlined"
          />
          <TextField
            name="pets"
            value={formik.values.pets}
            onChange={formik.handleChange}
            type="number"
            id="outlined-basic"
            label="Pets"
            variant="outlined"
          />
          <TextField
            name="currency"
            value={formik.values.currency}
            onChange={formik.handleChange}
            id="outlined-basic"
            label="Currency"
            variant="outlined"
            error={formik.touched.currency && Boolean(formik.errors.currency)}
            helperText={formik.touched.currency && formik.errors.currency}
          />

          <Button type="submit"><Link to='/result' state={{data:data}}>Submit</Link></Button>
        </form>
      </Paper>
    </Stack>
  );
};

export default Home;

This is my Result page where I want to show the Data:

    import React from 'react';
import { useLocation } from 'react-router-dom';

const Result = (props) => {
const location = useLocation();

console.log(props, "Props");
console.log("Location",location);

  return (
    <div>
        <h1>Hello from Result</h1>

    </div>
  )
}

export default Result

Problem No.2: When I click on the submit button where I have implemented Link to navigate to the result page. It is straight away redirecting me to the result page without the form validation. What I am expecting is, when i click on the submit button first it validates the form and then redirect me to the '/result' page.

Note: I am trying to learn React-router-dom, Material UI and useFormik(for handling form), requesting to provide the solution within this parameters of libraries only. Thank You.


Solution

  • There are at least a couple issues working against you here. First, when the link is clicked, the navigation action is immediately effected, meaning, it doesn't wait for any extraneous activities the form may be doing to navigate to the target route. Secondly, in the submit handler you are enqueueing a state update, and React state updates are not immediately processed. It takes at least another render cycle for the component to be rendered and have the updated state.

    What you can do here is to prevent the default link action from occurring so the navigation action is blocked, and issue an imperative navigation action, i.e. navigate, from the submit handler. You don't need the data state at all, navigate and pass the current form values in route state.

    Example:

    import React, { useState } from "react";
    import { Stack, Paper, TextField, Button, Typography } from "@mui/material";
    import { useFormik } from "formik";
    import * as yup from "yup";
    import { Link, useNavigate } from "react-router-dom";
    
    ...
    
    const Home = () => {
      const navigate = useNavigate();
    
      const formik = useFormik({
        initialValues: {
          ....
        },
        validationSchema: validationSchema,
        onSubmit: (values) => {
          // navigate on submission, pass form values in route state
          navigate("/result", { state: { values }});
        },
      });
    
      return (
        <Stack>
          <Paper elevation={3} sx={{ maxWidth: 700, height: 500 }}>
            <form onSubmit={formik.handleSubmit}>
              ....
    
              <Button type="submit">
                <Link
                  to="/result"
                  onClick={e => e.preventDefault()} // <-- prevent navigation
                >
                  Submit
                </Link>
              </Button>
            </form>
          </Paper>
        </Stack>
      );
    };
    
    export default Home;
    

    Since the Link here really isn't being used at all, you could/should probably just remove it.