javascriptreactjsfluentui-react

How to preserve input values when navigating to another page with React Router


I have a form that I want to submit, and the address fields can be populated by selecting an Address that will navigate you back to the form and fill in the form's input values.

I use useNavigate() to navigate to the Address Look-up page and pass in the form data.

const navigate = useNavigate();
navigate("address-lookup", {
  state: {
    postcode: form.postcode,
    isIndividual: form.isIndividual,
    firstName: form.firstName,
    surname: form.surname,
  },
});

Then, I use react-router's useLocation() hook to get the form data:

const { state } = useLocation();
<SearchBar
  value={state?.postcode ?? input}
  handleOnInput={(e) => setInput(e.target.value)}
  handleFind={handleFind}
/>

When I click an Address result, it navigates back to the form and passes the result's data.

  const handleOnClick = (postcode, street, town, country) => {
    navigate("form", {
      state: {
        postcode: postcode,
        street: street,
        town: town,
        country: country,
      },
    });
  };

Back in the Form, I set the value of the inputs by first checking if useLocation() contains any data. If not, it gets set as its form statue value.

<Input
  value={state?.postcode ?? form.postcode}
  onChange={(e) => setForm({ ...form, postcode: e.target.value })}
/>

An issue I am having is when I try to type in the Address Look-up input, it does set the value that was passed from the Form, however I can no longer change the input. Is it the way I am setting the input state at this point?

Form:

const Form = () => {
  const navigate = useNavigate();
  const { state } = useLocation();
  const [form, setForm] = useState({
    isIndividual: state?.isIndividual ?? false,
    firstName: state?.firstName ?? "",
    surname: state?.surname ?? "",
    postcode: state?.postcode ?? "",
    street: state?.street ?? "",
    town: state?.town ?? "",
    country: state?.country ?? 0,
  });

  const countries = [
    {
      id: 1,
      name: "UK",
    },
    {
      id: 2,
      name: "USA",
    },
    {
      id: 3,
      name: "France",
    },
  ];

  return (
    <div>
      <Checkbox
        label="Individual"
        onChange={(e, data) => {
          setForm({ ...form, isIndividual: Boolean(data.checked) });
        }}
      />
      <div>
        <Field
          label="First Name"
          validationState={form.firstName == "" ? "error" : "none"}
          validationMessage={
            form.firstName == "" ? "First Name is required." : ""
          }
        >
          <Input
            value={state?.firstName ?? form.firstName}
            onChange={(e) => setForm({ ...form, firstName: e.target.value })}
          />
        </Field>
      </div>
      <div>
        <Field
          label="Surname"
          validationState={form.surname == "" ? "error" : "none"}
          validationMessage={form.surname == "" ? "Surname is required." : ""}
        >
          <Input
            value={state?.surname ?? form.surname}
            onChange={(e) => setForm({ ...form, surname: e.target.value })}
          />
        </Field>
      </div>
      <div className={styles.field}>
        <Field
          label="Postcode"
          validationState={form.postcode == "" ? "error" : "none"}
          validationMessage={form.postcode == "" ? "Postcode is required." : ""}
        >
          <Input
            value={state?.postcode ?? form.postcode}
            onChange={(e) => setForm({ ...form, postcode: e.target.value })}
          />
        </Field>
        <Button
          appearance="outline"
          onClick={() => {
            navigate("address-lookup", {
              state: {
                postcode: form.postcode,
                isIndividual: form.isIndividual,
                firstName: form.firstName,
                surname: form.surname,
              },
            });
          }}
        >
          Look-up
        </Button>
      </div>
      <div>
        <Field label="Street">
          <Input
            value={state?.street ?? form.street}
            onChange={(e) => setForm({ ...form, street: e.target.value })}
          />
        </Field>
      </div>
      <div>
        <Field
          label="Town"
          validationState={form.town == "" ? "error" : "none"}
          validationMessage={form.town == "Town is required." ? "" : ""}
        >
          <Input
            value={state?.field ?? form.town}
            onChange={(e) => setForm({ ...form, town: e.target.value })}
          />
        </Field>
      </div>
      <div>
        <Label>Country</Label>
        <Dropdown
          aria-labelledby={dropdownId}
          // defaultValue={state?.country ?? countries[0].id}
          // defaultSelectedOptions={[state?.country ?? countries[0].id]}
          onOptionSelect={(e, data) => {
            setForm({ ...form, district: data.optionValue });
          }}
        >
          {countries.map((country) => (
            <Option key={country.id} value={country.id}>
              {country.name}
            </Option>
          ))}
        </Dropdown>
      </div>
      <Button appearance="primary" onClick={handleSubmit}>
        Submit
      </Button>
    </div>
  );
};

Address Look-up (this page also contains a SearchBar component that is reused for other Look-up pages):

const AddressLookup = () => {
  const navigate = useNavigate();
  const { state } = useLocation();
  const [input, setInput] = useState("");
  const [results, setResults] = useState([
    {
      postcode: "",
      street: "",
      town: "",
      country: 0,
    },
  ]);

  const handleOnClick = (postcode, street, town, country) => {
    navigate("form", {
      state: {
        postcode: postcode,
        street: street,
        town: town,
        country: country,
      },
    });
  };

  const handleFind = () => {
    //api call here
    //setResults(...);
  };

  return (
    <div>
      <SearchBar
        type="Address Look-up"
        placeholderText="by Postcode"
        value={state?.postcode ?? input}
        handleOnInput={(e) => setInput(e.target.value)}
        handleFind={handleFind}
      />
      {results.map((result, index) => {
        return (
          <div
            onClick={() =>
              handleOnClick(
                result.postcode,
                result.street,
                result.town,
                result.country.id
              )
            }
            key={index}
          >
            <div>{result.postcode}</div>
            <div>{result.street}</div>
            <div>{result.town}</div>
            <div>{result.country.name}</div>
          </div>
        );
      })}
    </div>
  );
};

const SearchBar = ({ value = "", handleOnInput, handleFind }) => {
  return (
    <div>
      <Field>
        {value != "" ? (
          <Input value={value} onInput={handleOnInput} />
        ) : (
          <Input onInput={handleOnInput} />
        )}
      </Field>
      <div>
        <Button appearance="primary" onClick={handleFind}>
          Find
        </Button>
      </div>
    </div>
  );
};

Solution

  • This line:

    <Input
      value={state?.firstName ?? form.firstName}
      onChange={(e) => setForm({ ...form, firstName: e.target.value })}
    />
    

    notice your value is using the state object. I don't think you want that. But you're already setting the form object to match the state object when you call useState.

    So I think just change to this:

    <Input
      value={form.firstName}
      onChange={(e) => setForm({ ...form, firstName: e.target.value })}
    />