问题
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