Check if input field is changed

我与影子孤独终老i 提交于 2021-01-01 06:45:06

问题


I am making a stepper form using React. And the form structure is almost done..

There are two steps,

-> Basic Details

-> Employment Details

Here a form context has been used to fill out the input field default value and also if any changes made then that gets stored via state.

form-context.js

import React, { useState } from 'react';

export const FormContext = React.createContext();

export function FormProvider({ children }) {
  const [formValue, setFormValue] = useState({
    basicDetails: {
      firstName: 'John',
      lastName: '',
    },
    companyDetails: {
      companyName: 'Some Company',
      designation: 'Some Designation',
    },
  });

  return (
    <FormContext.Provider value={[formValue, setFormValue]}>
      {children}
    </FormContext.Provider>
  );
}

Here I have next and previous button to move between steps,

index.js:

  const next = () => setCurrentPage((prev) => prev + 1);
  const prev = () => setCurrentPage((prev) => prev - 1);

Requirement:

-> On click over next/previous button, I am in the need to check whether any input is changed.

-> Through this I can call API to save the changes on click of next button in my real application.

-> Here if you help me to make some console log then that would be more than enough.

Eg:

-> If user modify the first name in basic detail section from John to Doe then on click of next button can console log that there is change in basic details.

-> If none of the fields are changed in basic details section then it can directly proceed to next step (as like now).

Note: Please don't hard code any input name because I have more than 30 input fields for each step..


回答1:


I think the main issue here is that your current implementation replaces existing state, on each change, so it is impossible to know if/what changed. Not even a "usePrevious" hook to hold the previous state value would work here since it would only hold the last previous field edit, not the original value, for comparison. Many form handling solutions handle this by keeping an initial state that isn't mutated, or tracking "dirty" fields, and/or more.

Here's a suggestion of mine to tweak your form context state to track fields that have been updated. The dirtyFields object holds the original value.

FormProvider

const [formValue, setFormValue] = useState({
  basicDetails: {
    fields: {
      firstName: 'John',
      lastName: '',
    },
    dirtyFields: {},
  },
  companyDetails: {
    fields: {
      companyName: 'Some Company',
      designation: 'Some Designation',
    },
    dirtyFields: {},
  },
});

BasicDetails

const BasicDetails = () => {
  const [value, setValue] = React.useContext(FormContext);
  const {
    basicDetails: { fields }, // <-- get field values
  } = value;

  const handleInputChange = (event) => {
    const { name, value } = event.target;

    setValue((prev) => ({
      ...prev,
      basicDetails: {
        ...prev.basicDetails,
        fields: {
          ...prev.basicDetails.fields,
          [name]: value,
        },
        ...(prev.basicDetails.dirtyFields[name] // <-- only mark dirty once with original value
          ? {}
          : {
              dirtyFields: {
                ...prev.basicDetails.dirtyFields,
                [name]: prev.basicDetails.fields[name],
              },
            }),
      },
    }));
  };

  return (
    <>
      <div className="form-group col-sm-6">
        <label htmlFor="firstName">First Name</label>
        <input
          ...
          value={fields.firstName} // <-- access fields object
          ...
        />
      </div>
      <div className="form-group col-sm-4">
        <label htmlFor="lastName">Last Name</label>
        <input
          ...
          value={fields.lastName}
          ...
        />
      </div>
    </>
  );
};

EmploymentDetails

const EmploymentDetails = () => {
  const [value, setValue] = React.useContext(FormContext);
  const {
    companyDetails: { fields },
  } = value;

  const handleInputChange = (event) => {
    const { name, value } = event.target;

    setValue((prev) => ({
      ...prev,
      companyDetails: {
        ...prev.companyDetails,
        fields: {
          ...prev.companyDetails.fields,
          [name]: value,
        },
        ...(prev.companyDetails.dirtyFields[name]
          ? {}
          : {
              dirtyFields: {
                ...prev.companyDetails.dirtyFields,
                [name]: prev.companyDetails.fields[name],
              },
            }),
      },
    }));
  };

  return (
    <>
      <div className="form-group col-sm-6">
        <label htmlFor="companyName">Company Name</label>
        <input
          ...
          value={fields.companyName}
          ...
        />
      </div>
      <div className="form-group col-sm-4">
        <label htmlFor="designation">Designation</label>
        <input
          ...
          value={fields.designation}
          ...
        />
      </div>
    </>
  );
};

Check dirty fields when incrementing/decrementing the step.

Give each section an id that matches the "form key" in the form context.

const sections = [
  {
    title: 'Basic Details',
    id: 'basicDetails',
    onClick: () => setCurrentPage(1),
  },
  {
    title: 'Employment Details',
    id: 'companyDetails',
    onClick: () => setCurrentPage(2),
  },
  { title: 'Review', id: 'review', onClick: () => setCurrentPage(3) },
];

Create a checkDirty utility. Here I simply log the dirty fields

const checkDirty = (page) => {
  console.log('check dirty', 'page', page);
  console.log(
    value[sections[page - 1].id] && value[sections[page - 1].id].dirtyFields,
  );
};

const next = () => {
  setCurrentPage((prev) => prev + 1);
  checkDirty(currentPage); // <-- check for dirty fields when updating page step
};

const prev = () => {
  setCurrentPage((prev) => prev - 1);
  checkDirty(currentPage);
};

Because of the extra nested state in the form context, here's a utility to reduce it back to just form data you want rendered on the review step.

const prettyReview = sections.reduce(
  (sections, section) => ({
    ...sections,
    ...(value[section.id]
      ? { [section.id]: { ...value[section.id].fields } }
      : {}),
  }),
  {},
);

...

<pre>{JSON.stringify(prettyReview, null, 2)}</pre>

Edit

You said your data comes from a backend API call. Here's a context state initialization function that maps the API data shape to the state shape I have.

Given data from API

const apiData = {
  basicDetails: {
    firstName: 'John',
    lastName: '',
  },
  companyDetails: {
    companyName: 'Some Company',
    designation: 'Some Designation',
  },
};

Initialization function

const initializeContext = (data) =>
  Object.entries(data).reduce(
    (sections, [section, fields]) => ({
      ...sections,
      [section]: {
        fields,
        dirtyFields: {},
      },
    }),
    {},
  );

Initialize the FormProvider context state

function FormProvider({ children }) {
  const [formValue, setFormValue] = useState(initializeContext(apiData));

  return (
    <FormContext.Provider value={[formValue, setFormValue]}>
      {children}
    </FormContext.Provider>
  );
}


来源:https://stackoverflow.com/questions/64857521/check-if-input-field-is-changed

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!