When to use react useState? [duplicate] - javascript

This question already has answers here:
The useState set method is not reflecting a change immediately
(15 answers)
Closed last year.
I have a simple registration form with 3 fields. I have stored the state in formValues with value & error associated with each field. Now when i submit the form without filling any or at least one field the form should be invalid but instead it shows validation messages with invalid fields but makes form valid. Even if i have added setTimeout the updated state is not available in the same handleSubmit. If i submit again the process works just fine. I understand that the state updation is async but if we see the logs in console the form's validation message is logged after formValues log in the render and those logs show that the state was updated correctly but the final validation message shows invalid state. If i change it to class component it works. Here's a link to codesandbox.
import React, { useState } from "react";
import { Button, Form, Col } from "react-bootstrap";
const sleep = timeout => new Promise(resolve => setTimeout(resolve, timeout));
const RegistrationForm = () => {
const [formValues, setFormValues] = useState({
name: { value: "", error: null },
email: { value: "", error: null },
password: { value: "", error: null }
});
const handleInputChange = (e, field) => {
const { value } = e.target;
setFormValues(prevValues => ({
...prevValues,
[field]: { value, error: null }
}));
};
const validateForm = () => {
let updatedFormValues = { ...formValues };
Object.keys(formValues).forEach(field => {
if (!formValues[field].value) {
updatedFormValues = {
...updatedFormValues,
[field]: { ...updatedFormValues[field], error: "required" }
};
}
});
setFormValues(updatedFormValues);
};
const isFormValid = () =>
Object.keys(formValues).every(field => formValues[field].error === null);
const handleSubmit = async e => {
e.preventDefault();
validateForm();
await sleep(100);
if (!isFormValid()) {
console.log("form is not valid", formValues);
return;
}
console.log("form is valid", formValues);
// make api call to complete registration
};
console.log({ formValues });
return (
<Form className="registration-form" onSubmit={handleSubmit}>
<Form.Row>
<Col>
<Form.Group controlId="name">
<Form.Label>Name</Form.Label>
<Form.Control
type="text"
placeholder="Enter name"
value={formValues.name.value}
onChange={e => handleInputChange(e, "name")}
/>
<Form.Control.Feedback type="invalid" className="d-block">
{formValues.name.error}
</Form.Control.Feedback>
</Form.Group>
</Col>
<Col>
<Form.Group controlId="email">
<Form.Label>Email</Form.Label>
<Form.Control
type="email"
placeholder="Enter email"
value={formValues.email.value}
onChange={e => handleInputChange(e, "email")}
/>
<Form.Control.Feedback type="invalid" className="d-block">
{formValues.email.error}
</Form.Control.Feedback>
</Form.Group>
</Col>
</Form.Row>
<Form.Row>
<Col>
<Form.Group controlId="password">
<Form.Label>Password</Form.Label>
<Form.Control
type="password"
placeholder="Enter password"
value={formValues.password.value}
onChange={e => handleInputChange(e, "password")}
/>
<Form.Control.Feedback type="invalid" className="d-block">
{formValues.password.error}
</Form.Control.Feedback>
</Form.Group>
</Col>
<Col />
</Form.Row>
<Button variant="primary" type="submit">
Submit
</Button>
</Form>
);
};
export default RegistrationForm;

State updates are not just async but are als affected by closures in functional components, so using a sleep or timeout isn't going to leave your with an updated value in the same render cycle
You can read more about it in this post:
useState set method not reflecting change immediately
However, one solution in your case is to maintain a ref and toggle is value to trigger a useEffect in which you will validate the form post handleSubmit handler validates it and sets the formValues
Relevant code:
const validateFormField = useRef(false);
const handleInputChange = (e, field) => {
const { value } = e.target;
setFormValues(prevValues => ({
...prevValues,
[field]: { value, error: null }
}));
};
const validateForm = () => {
let updatedFormValues = { ...formValues };
Object.keys(formValues).forEach(field => {
if (!formValues[field].value) {
updatedFormValues = {
...updatedFormValues,
[field]: { ...updatedFormValues[field], error: "required" }
};
}
});
setFormValues(updatedFormValues);
validateFormField.current = !validateFormField.current;
};
const isFormValid = () =>
Object.keys(formValues).every(field => formValues[field].error === null);
const handleSubmit = async e => {
e.preventDefault();
validateForm();
// make api call to complete registratin
};
useEffect(() => {
if (!isFormValid()) {
console.log("form is not valid", formValues);
} else {
console.log("form is valid", formValues);
}
}, [validateFormField.current]); // This is fine since we know setFormValues will trigger a re-render
Working demo

Related

BAD_USER_INPUT Error on Edit value on form user

Problem
I'm currently working on reactjs + apollo client projects. I'm developing edit user feature. for querying user by username, it works.
but when i'm change the data information. it throws error like this.
Also , i'm getting this error.
Code
Mutation
mutation($username: String!, $input: EditUser!) {
updateUser(username: $username, input: $input) {
full_name
email
group_id
phone
address
}
}
ModalEdit.js
import React, { useEffect, useState } from "react";
import { useQuery, useMutation } from "#apollo/client";
import { Button, Modal, Form } from "react-bootstrap";
import { GET_USER_BY_ID } from "../../../gql/query";
import { UPDATE_USER } from "../../../gql/mutation";
const ModalEdit = (props) => {
// state for check input component
const [isChecked, setIsChecked] = useState("ACTIVE");
// state for input values
const [value, setValue] = useState({
group_id: "",
full_name: "",
email: "",
phone: "",
address: "",
password: "",
});
useEffect(() => {
if (props.show) {
document.body.classList.add("modal-open");
}
return () => {
if (document.body.classList.contains("modal-open")) {
document.body.classList.remove("modal-open");
}
};
}, [props.show]);
const { data, loading, error } = useQuery(GET_USER_BY_ID, {
variables: { username: props.username },
});
const [updateUser, { error: updateError, loading: updateLoading, refetch }] =
useMutation(UPDATE_USER, {
onCompleted: (data) => {
refetch();
},
onError: (err) => {
console.error(JSON.stringify(err, null, 2));
},
});
const dataUser = data?.getUserByID;
useEffect(() => {
if (dataUser) {
setValue({
group_id: dataUser.group_id,
full_name: dataUser.full_name,
email: dataUser.email,
phone: dataUser.phone,
address: dataUser.address,
password: dataUser.password,
});
}
}, [dataUser]);
if (loading) return <p>Loading...</p>;
if (error) return <p>Error!</p>;
const handleChange = (event) => {
const { name, value } = event.target;
setValue({ ...value, [name]: value });
};
// handle mutation for edit user
const handleSubmit = (event) => {
event.preventDefault();
console.log(value.full_name);
updateUser({
variables: {
username: props.username,
input: {
group_id: value.group_id,
full_name: value.full_name,
email: value.email,
phone: value.phone,
address: value.address,
password: value.password,
},
},
});
};
return (
<Modal show={props.show}>
<Modal.Header>
<Modal.Title>
{" "}
<span>FORMULIR AKUN PENGGUNA</span>{" "}
</Modal.Title>
</Modal.Header>
<Modal.Body>
<Form onSubmit={handleSubmit}>
<Form.Group className="mb-3">
<Form.Label>Role Akun</Form.Label>
<Form.Select
aria-label="pilih user role"
name="group_id"
onChange={handleChange}
>
<option value={value.group_id}>{value.group_id}</option>
</Form.Select>
</Form.Group>
<Form.Group className="mb-3">
<Form.Label>Nama Lengkap</Form.Label>
<Form.Control
type="text"
name="full_name"
value={value.full_name}
onChange={handleChange}
/>
</Form.Group>
<Form.Group className="mb-3">
<Form.Label>Email</Form.Label>
<Form.Control
type="email"
name="email"
value={value.email}
onChange={handleChange}
/>
</Form.Group>
<Form.Group className="mb-3">
<Form.Label>Phone</Form.Label>
<Form.Control
type="text"
name="phone"
value={value.phone}
onChange={handleChange}
/>
</Form.Group>
<Form.Label>Aktifkan Akun</Form.Label>
{dataUser.status === "ACTIVE" ? (
<Form.Check
type="switch"
checked={isChecked}
onChange={(event) => setIsChecked(event.target.checked)}
id="custom-switch"
label="Aktifkan Akun"
/>
) : (
<Form.Check
type="switch"
id="custom-switch"
checked={isChecked}
onChange={(event) => setIsChecked(event.target.checked)}
label="Aktifkan Akun"
/>
)}
<Button variant="primary" type="submit">
Submit
</Button>
<Button variant="secondary" onClick={props.onClose}>
Close
</Button>
</Form>
</Modal.Body>
</Modal>
);
};
export default ModalEdit;
Question
How to fix BAD_USER_INPUT request?
How to check if email is controlled/uncontrolled components?
any help will be appreciated, thank you
It looks like the input type needs a full_name field as well. I think your problem lies with:
// state for input values
const [value, setValue] = useState({
group_id: "",
full_name: "",
email: "",
phone: "",
address: "",
password: "",
});
...
const handleChange = (event) => {
const { name, value } = event.target;
setValue({ ...value, [name]: value });
};
Because in the context of this function value is == event.target.value and not the value you defined in your setState:
Therefore setValue({...value, [name]: value}); will completely override the state.
Try aliasing the target's value:
const handleChange = (event) => {
const { name, value:tValue } = event.target;
setValue({ ...value, [name]: tValue });
};
I suspect this is a consequence of value.email suddenly becoming undefined based on (1).

React useState and onSubmit form doesn't update the state immediately - validation best practice

I'm developing a custom Form validation for my React project with Typescript.
I'm facing an issue with the useState that is not updating immediately the state containing the errors when I submit a form.
Let me provide you an example.
const initialFormState = {
email: '',
password: '',
}
const SignUpForm = () => {
const [formValues, setFormValues] = useState(initialFormState);
const [validationErrors, setValidationErrors] = useState<string>([]);
const handleChange = () => {
// handle the change implementation updating the formValues ...
}
const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
doValidationOnSubmit();
if (validationErrors.length > 0) {
console.log('validation errors!!');
return;
}
doLogin();
};
const doValidationOnSubmit = () => {
Object.entries(formValues).forEach(([inputName, value]) => {
if (formValues[inputName] === '') {
setValidationErrors((oldValidationErrors) => [...oldValidationErrors, `${inputName} is not valid`]);
}
});
}
const doLogin = () => {
// do login logic
}
return (
<>
<form onSubmit={handleSubmit}>
<input type="email" name="email" onChange={handleChange} />
<input type="password" name="email" onChange={handleChange} />
<button type="submit">Login</button>
</form>
</>
);
}
export default SignUpForm;
When I'm checking for the errors in the handleSubmit, there are no errors, even if errors should be present there:
if (validationErrors.length > 0) {
console.log('validation errors!!');
return;
}
In general, I'm wondering what is the best practice in order to avoid these kinds of issues with the react state not updating immediately the state?
I already tried with useEffect, listening on the validationErrors changes but nothing changes actually, the behavior is pretty the same.
I'm sure I'm missing something.
useState is asynchronous, so state changes (setValidationErrors) are not applied immediately. Therefore, you cannot get the latest state of validationErrors in the next line.
We can do validation and set state separately. In that case, you can leverage the latest value (not the latest state) to check values validity.
const initialFormState = {
email: '',
password: '',
}
const SignUpForm = () => {
const [formValues, setFormValues] = useState(initialFormState);
const [validationErrors, setValidationErrors] = useState<string>([]);
const handleChange = () => {
// handle the change implementation updating the formValues ...
}
const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
//get all invalid values
const invalidValues = returnInvalidValues();
//update state
setValidationErrors(prev => [...prev, ...invalidValues])
if (invalidValues.length > 0) {
console.log('validation errors!!');
return;
}
doLogin();
};
const returnInvalidValues = () => {
return Object.entries(formValues).filter(([inputName, value]) => formValues[inputName] === ''). map(invalidValue => `${inputName} is not valid`);
}
const doLogin = () => {
// do login logic
}
return (
<>
<form onSubmit={handleSubmit}>
<input type="email" name="email" onChange={handleChange} />
<input type="password" name="email" onChange={handleChange} />
<button type="submit">Login</button>
</form>
</>
);
}
export default SignUpForm;
You also can try useEffect
const initialFormState = {
email: '',
password: '',
}
const SignUpForm = () => {
const [formValues, setFormValues] = useState(initialFormState);
const [validationErrors, setValidationErrors] = useState<string>([]);
const handleChange = () => {
// handle the change implementation updating the formValues ...
}
//introduce useEffect here
useEffect(() => {
if (validationErrors.length > 0) {
console.log('validation errors!!');
return;
}
doLogin();
}, [validationErrors]);
const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
doValidationOnSubmit();
};
const doValidationOnSubmit = () => {
Object.entries(formValues).forEach(([inputName, value]) => {
if (formValues[inputName] === '') {
setValidationErrors((oldValidationErrors) => [...oldValidationErrors, `${inputName} is not valid`]);
}
});
}
const doLogin = () => {
// do login logic
}
return (
<>
<form onSubmit={handleSubmit}>
<input type="email" name="email" onChange={handleChange} />
<input type="password" name="email" onChange={handleChange} />
<button type="submit">Login</button>
</form>
</>
);
}
export default SignUpForm;
If it does not work for your case, you can delay using the latest state with setTimeout. With this approach, it will put the task to get the latest state to the end of the call stack queue (you can check this document)
const initialFormState = {
email: '',
password: '',
}
const SignUpForm = () => {
const [formValues, setFormValues] = useState(initialFormState);
const [validationErrors, setValidationErrors] = useState<string>([]);
const handleChange = () => {
// handle the change implementation updating the formValues ...
}
const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
doValidationOnSubmit();
setTimeout(() => {
if (validationErrors.length > 0) {
console.log('validation errors!!');
return;
}
doLogin();
})
};
const doValidationOnSubmit = () => {
Object.entries(formValues).forEach(([inputName, value]) => {
if (formValues[inputName] === '') {
setValidationErrors((oldValidationErrors) => [...oldValidationErrors, `${inputName} is not valid`]);
}
});
}
const doLogin = () => {
// do login logic
}
return (
<>
<form onSubmit={handleSubmit}>
<input type="email" name="email" onChange={handleChange} />
<input type="password" name="email" onChange={handleChange} />
<button type="submit">Login</button>
</form>
</>
);
}
export default SignUpForm;

Test useFormik hook with React Testing Library

I did a thorough search before positing this question, most of them were using <Formik/> where they had sent onSubmit handler as a prop so that it can be tested whether it is called or not when the form is submitted.
In my case, I'm using useFormik hook for my form and I can't get to reach the onSubmit code when the form is submitted. The type property of button is submit so the onClick function is undefined when I displayed the element in console log.
Below is excerpt from my code
Component
export const WithMaterialUI = () => {
const formik = useFormik({
initialValues: {
email: "foobar#example.com"
},
validationSchema: validationSchema,
onSubmit: (values) => {
alert(JSON.stringify(values, null, 2));
}
});
return (
<div>
<form onSubmit={formik.handleSubmit} data-testid="form">
<TextField
data-testid="email"
fullWidth
id="email"
name="email"
label="Email"
value={formik.values.email}
onChange={formik.handleChange}
error={formik.touched.email && Boolean(formik.errors.email)}
helperText={formik.touched.email && formik.errors.email}
/>
<Button color="primary" variant="contained" fullWidth type="submit">
Submit
</Button>
</form>
</div>
);
};
Test case
const mockSubmit = jest.fn();
describe("Test for formik onSubmit", () => {
it("should handle form submission", async () => {
const { getByTestId } = render(<WithMaterialUI />);
const email = getByTestId("email");
const form = getByTestId("form");
act(() => {
fireEvent.change(email, { target: { value: "abc#gmail.com" } });
});
act(() => {
fireEvent.submit(form);
});
await waitFor(() => {
expect(mockSubmit).toHaveBeenCalled();
});
});
});
I created a working example using CodeSandbox. Could anyone please help?

Updating & appending to React array state

I have a form where users create a coding problem. In the form, there is the option to add sample test cases via input and output text boxes. The user can click a button to add a new test case. Right now I have a state object that holds all of the form data, formObj, and within it is a sample_test_cases field which I want to hold an array of objects, like: [{ input: "", output: "" }].
The issue I am having is updating this array. I need to be able to concat a new object to it each time a test case is added. Then updating the state at that index when the text box is changed. I have tried creating a stateless array and updating it, then setting sample_test_cases to that array. This is not working, however.
Here is a sandbox with my code: https://codesandbox.io/s/eloquent-snowflake-n4bpl?file=/src/AddProblemForm.js
If anyone could give me tips that would really help. I'm not too familiar with Javascript or complex state management. Thanks.
See snippet below (I'm having issues with saving the sandbox). A few issues that it addresses:
You also kept track of state using some local arr variable; updating that won't trigger a re-render, so you'll need to modify the sample_test_cases array inside your formObj state.
The textarea value should also be reflecting what is in your state. To make this more convenient, I've passed the test case into the SampleTestCase component as a prop, so it will react to state changes.
React state should be treated as immutable. So when the input text box is updated, you should set the state to a new object with a new sample_test_cases array, which is constructed of the first i-1 test cases, a new ith test case with modified input, and the remaining test cases.
Move SampleTestCase outside of the AddProblemForm component. If you don't, you will find that whenever the input textarea is changed, you will lose keyboard focus. This is because the SampleTestCase component is being redefined on each render, which is triggered by a state change. (Similar problem: React.js - input losing focus when rerendering)
import React, { useState } from "react";
import { Form, Button, Col } from "react-bootstrap";
import { BsPlusSquare } from "react-icons/bs";
const SampleTestCase = ({ testCase, updateInput }) => {
return (
<Form.Row>
<Col>
<Form.Group controlId="input">
<Form.Label>Sample Input</Form.Label>
<Form.Control
required
as="textarea"
rows={2}
onChange={updateInput}
value={testCase.input}
/>
</Form.Group>
</Col>
<Col>
<Form.Group controlId="output">
<Form.Label>Sample Output</Form.Label>
<Form.Control
required
as="textarea"
rows={2}
// onChange={(event) =>
// setFormObj({
// ...formObj,
// sample_test_cases: {
// ...formObj.sample_test_cases,
// output: event.target.value
// }
// })
// }
/>
</Form.Group>
</Col>
</Form.Row>
);
};
const AddProblemForm = () => {
const [formObj, setFormObj] = useState({
sample_test_cases: [{ input: "", output: "" }]
// other state obj info
});
const AddSampleTestCase = () => {
// make new instance!
const newTestCase = { input: "", output: "" };
setFormObj({
...formObj,
sample_test_cases: [...formObj.sample_test_cases, newTestCase]
});
};
console.log(formObj);
const updateInputFor = (i) => (event) => {
event.preventDefault();
const { sample_test_cases } = formObj;
const testCase = sample_test_cases[i];
testCase.input = event.target.value;
setFormObj({
...formObj,
sample_test_cases: [
...sample_test_cases.slice(0, i),
testCase,
...sample_test_cases.slice(i + 1)
]
});
};
return (
<div>
Problem Form
<Form>
<h5 style={{ paddingTop: "1rem" }}>Sample Test Cases</h5>
{formObj.sample_test_cases.map((testCase, i) => (
<React.Fragment key={i}>
<SampleTestCase
key={i}
testCase={testCase}
updateInput={updateInputFor(i)}
/>
<hr />
</React.Fragment>
))}
<Button onClick={AddSampleTestCase}>
<div>Add Sample Test Case</div>
</Button>
</Form>
</div>
);
};
export default AddProblemForm;
I suggest you use useReducer hook in cases your state is complicated.
hooks API
useReducer is usually preferable to useState when you have complex
state logic that involves multiple sub-values or when the next state
depends on the previous one.
AddProblemForm.js:
import React, { useReducer } from "react";
import { Form, Button, Col } from "react-bootstrap";
import { BsPlusSquare } from "react-icons/bs";
import SampleTestCase from "./SampleTestCase";
const initialState = {
sample_test_cases: [],
counter: 0
// other state obj info
};
function reducer(state, action) {
switch (action.type) {
case "addSampleTestCase": {
const { data } = action;
return {
...state,
sample_test_cases: [...state.sample_test_cases, data],
counter: state.counter + 1
};
}
case "updateTest": {
const { index, value } = action;
return {
...state,
sample_test_cases: state.sample_test_cases.map((item, i) => {
if (i === index) {
return value;
} else {
return item;
}
})
};
}
default:
throw new Error();
}
}
const AddProblemForm = () => {
const [state, dispatch] = useReducer(reducer, initialState);
const AddSampleTestCase = () => {
dispatch({ type: "addSampleTestCase", data: { input: "", output: "" } });
};
/* console.log(state); */
return (
<div>
Problem Form
<Form>
<h5 style={{ paddingTop: "1rem" }}>Sample Test Cases</h5>
{state.sample_test_cases.map((sample_test_case, i) => (
<div key={i}>
<SampleTestCase
sample_test_case={sample_test_case}
updateValue={(value) =>
dispatch({ type: "updateTest", index: i, value })
}
/>
<hr />
</div>
))}
<Button onClick={AddSampleTestCase}>
<div>Add Sample Test Case</div>
</Button>
</Form>
</div>
);
};
export default AddProblemForm;
SampleTestCase.js:
import React from "react";
import { Form, Button, Col } from "react-bootstrap";
const SampleTestCase = ({ sample_test_case, updateValue }) => {
return (
<Form.Row>
<Col>
<Form.Group controlId="input">
<Form.Label>Sample Input</Form.Label>
<Form.Control
required
as="textarea"
rows={2}
value={sample_test_case.input}
onChange={(event) => updateValue(event.target.value)}
/>
</Form.Group>
</Col>
<Col>
<Form.Group controlId="output">
<Form.Label>Sample Output</Form.Label>
<Form.Control
required
as="textarea"
rows={2}
value={sample_test_case.output}
onChange={(event) => updateValue(event.target.value)}
/>
</Form.Group>
</Col>
</Form.Row>
);
};
export default SampleTestCase;

Using React.memo with hooks for controlled inputs

I'm providing some form functionality from a custom React hook. This hook has some functionality similar to Formik (but this is really basic stuff).
function useFormValidation(initialState, validate) {
const [values, setValues] = React.useState(initialState);
const [errors, setErrors] = React.useState({});
const [isSubmitting, setSubmitting] = React.useState(false);
React.useEffect(() => {
if (isSubmitting) {
const noErrors = Object.keys(errors).length === 0;
if (noErrors) {
console.log("authenticated!", values.email, values.password);
setSubmitting(false);
} else {
setSubmitting(false);
}
}
}, [errors]);
function handleChange(event) {
setValues({
...values,
[event.target.name]: event.target.value
});
}
function handleBlur() {
const validationErrors = validate(values);
setErrors(validationErrors);
}
function handleSubmit(event) {
event.preventDefault();
const validationErrors = validate(values);
setErrors(validationErrors);
setSubmitting(true);
}
return {
handleSubmit,
handleChange,
handleBlur,
values,
errors,
isSubmitting
};
}
The form is the following:
function Register() {
const {
handleSubmit,
handleChange,
handleBlur,
values,
errors,
isSubmitting
} = useFormValidation(INITIAL_STATE, validateAuth);
// const [email, setEmail] = React.useState("");
// const [password, setPassword] = React.useState("");
return (
<div className="container">
<h1>Register Here</h1>
<form onSubmit={handleSubmit}>
<Input
handleChange={handleChange}
handleBlur={handleBlur}
name="email"
value={values.email}
className={errors.email && "error-input"}
autoComplete="off"
placeholder="Your email address"
/>
{errors.email && <p className="error-text">{errors.email}</p>}
<Input
handleChange={handleChange}
handleBlur={handleBlur}
value={values.password}
className={errors.password && "error-input"}
name="password"
// type="password"
placeholder="Choose a safe password"
/>
{errors.password && <p className="error-text">{errors.password}</p>}
<div>
<button disabled={isSubmitting} type="submit">
Submit
</button>
</div>
</form>
</div>
);
}
And the memoized component is the next:
function Input({
handleChange,
handleBlur,
name,
value,
className,
autoComplete,
placeholder,
type
}) {
return (
<input
onChange={handleChange}
onBlur={handleBlur}
name={name}
value={value}
className={className}
autoComplete={autoComplete}
placeholder={placeholder}
type={type}
/>
);
}
function areEqual(prevProps, nextProps) {
console.log(`
prevProps: ${JSON.stringify(prevProps.value)}
nextProps: ${JSON.stringify(nextProps.value)}
`);
return prevProps.value === nextProps.value;
}
const useMemo = (component, propsAreEqual) => {
return memo(component, propsAreEqual);
};
export default useMemo(Input, areEqual);
I enter some text into the first input. Then, when I switch to the second Input and start typing, the first input loses the value. It's like the form is not rendering the LAST MEMOIZED input, but prior versions instead.
I'm a React beginner and can't figure out the solution. Any help please?
Try using the updater form of setState which takes a function:
function handleChange(event) {
// event.target wont be available when fn is run in setState
// so we save them in our own local variables here
const { name, value } = event.target;
setValues(prev => ({
...prev,
[name]: value
}));
}
Your areEqual method translates to
Re-render my Input ONLY when the value changes.
But in reality, your handleChange function from the hook is also changing. Also, you use the same handleChange for both the inputs. So, the Input "remembers" only the handleChange from the last time value had changed and since handleChange is tracking values via closure, it in-turn "remembers" the values when it was created.
Changing your areEqual method (or completely omitting it) to verify a change in handleChange, will solve your problem.
function areEqual(prevProps, nextProps) {
return (
prevProps.value === nextProps.value &&
prevProps.handleChange === nextProps.handleChange
);
}
A codesandbox of the solution here

Categories

Resources