The state is updated only on the next keystroke but with the previous state. Screen 1
When you click on updateForm (), it is also empty, only after the second click, the state is updated. Screen 2
I understand that this is due to asynchrony, but in this case I do not know how to use it.
Home.jsx
import React, { useState } from 'react';
import { Form } from '../components/Form/Form';
const Home = () => {
const [dateForm, setDataForm] = useState({});
const updateForm = eachEnry => {
setDataForm(eachEnry);
console.log(dateForm);
};
return (
<div>
<Form updateForm={updateForm} />
</div>
);
};
export default Home;
Form.jsx
import React, { useState } from 'react';
import './Form.scss';
export const Form = ({ updateForm }) => {
const initInputState = {
name: '',
password: ''
};
const [dataForm, setDataForm] = useState(initInputState);
const { name, password } = dataForm;
const onChange = e => {
setDataForm({
...dataForm,
[e.target.name]: e.target.value
});
};
const onSubmit = e => {
e.preventDefault();
updateForm(dataForm);
};
return (
<div>
<form onSubmit={onSubmit}>
<input
type="text"
placeholder="Name"
value={name}
onChange={onChange}
name="name"
/>
<input
placeholder="Password"
onChange={onChange}
value={password}
type="text"
name="password"
/>
<button type="submit">Login</button>
</form>
</div>
);
};
Your code is working fine. You just doing console.log before the state is updated. State updates happen not when you using an update state function. It's happening when all component action and nested components actions are done.
Check your code with console log on another place click to check
As you can see I placed a console log on every Home component rerender. You can check that all works fine.
P.S. I did some improvements to your code. Check if u like it. And add a comment to updateForm function. Check this one too, please.
You evidently are not setting your state properly, here
setDataForm({
...dataForm,
[e.target.name]: e.target.value
});
should be
setDataForm(c => ({
...c,
[e.target.name]: e.target.value
}));
Related
I have a form that has all text elements. How come when I use this change handler function and set the state using the change event listener, it logs what the state was before the change?
const handleChange = event => {
const { name, value } = event.target
setSomeState(prevDateInputs => ({
...prevStateInputs,
[name]: value,
}))
console.log(someState) // ← this logs the value of the state before it was changed
}
In the recent react rfcs release you can use 'use' same as javascript async await method. more details can be found in this link https://github.com/reactjs/rfcs/pull/229
Is it essential to use setState? Could useRef work?
import {useRef} from 'react';
const App = () => {
const inputRef = useRef(null);
function handleChange() {
const {name, value} = inputRef.current;
console.log({[name] : value});
}
return (
<div>
<input
onChange={handleChange}
ref={inputRef}
type="text"
id="message"
name="message"
/>
</div>
);
};
export default App;
But assuming your real use case doesn't involve a console.log, it may not matter if setState doesn't update instantly. In the below example We see the new value displayed on the screen near instantly in the h2 tag even if the console.log shows the old value:
import {useState} from 'react';
const App = () => {
const [message, setMessage] = useState('');
const [someState, setSomeState] = useState('');
const handleChange = event => {
const { name, value } = event.target
setMessage(value)
setSomeState({[name]:value})
console.log('value is:', someState);
};
return (
<div>
<input
type="text"
id="message"
name="message"
onChange={handleChange}
value={message}
/>
<h2>{JSON.stringify(someState)}</h2>
</div>
);
};
export default App;
Some more details here.
Here's an example of a component I have in my react project:
import {useState, useEffect, useRef} from 'react';
import { useSelector, useDispatch } from 'react-redux';
import { createWorker } from '../../actions/worker';
import '../components.scss';
const New = () => {
const [worker, setWorker] = useState(
{
name: "",
email: "",
confirmation_email: "",
admin: false
}
);
const [checked, setChecked] = useState(false); //Determines if admin checkbox is checked or not
const [successMessage, setSuccessMessage] = useState(null); //If there's a success in the uploading process, we simply will put a message at the bottom.
const dispatch = useDispatch();
const errors = useSelector((state) => state.errors.error);
const selectedWorker = useSelector((state) => state.workers.current_worker); //We will be using this to determine if the user has a right to access this page
const renderedAlreadyRef = useRef(false); //Let's us know if we've rendered it already or not
useEffect(() => {
//We'll be using this to see if allWorkers.workers has been updated. We also use the ref renderedAlreadyRef to ensure it only runs after rendering
if (renderedAlreadyRef.current === true && Object.keys(errors).length === 0){
setSuccessMessage("Worker created successfully");
}
}, [errors])
const handleSubmit = (e) => {
//Handles submitting the form
e.preventDefault();
dispatch(createWorker(worker));
renderedAlreadyRef.current = true;
}
const handleChange = (e) => {
const newKey = e.target.id;
const newValue = e.target.value
if (newKey === "admin"){
setChecked(!checked)
setWorker(oldState => ({...oldState, "admin": !checked}))
}
else{
setWorker(oldState => ({ ...oldState, [newKey]: newValue}));
}
}
if (Object.keys(selectedWorker).length !== 0){
if (selectedWorker.admin === 1){
return(
<>
<form id="worker_form" onSubmit={e => handleSubmit(e)}>
<label>
Worker Name:
<input type="text" defaultValue={worker.name} id="name" onChange={e => handleChange(e)}></input>
</label>
<label>
Worker Email:
<input type="text" defaultValue={worker.email} id="email" onChange={e => handleChange(e)}></input>
</label>
<label>
Confirmation Email:
<input type="text" defaultValue={worker.confirmation_email} id="confirmation_email" onChange={e => handleChange(e)}></input>
</label>
<label>
Are they an admin?: <input type="checkbox" checked={checked} id="admin" onChange={e => handleChange(e)} />
</label>
<button type="submit" onClick={e => handleSubmit(e)} className="submit_new_button">Submit</button>
</form>
<h3 className='new_messages'>{successMessage}</h3>
</>
)
}
else{
return(
<div id="Forbidden">
<h1>Error 403 - Forbidden</h1>
<h2>You do not have access to this page</h2>
</div>
)
}
}
else{
return(<h1>Loading...</h1>)
}
}
export default New;
Up until yesterday, this was working flawlessly, but as of yesterday, suddenly, the things that are conditionally rendered no longer work. They just load forever... That is unless I change something in the file (or even just add a bit of whitespace) and then save that file. Suddenly, the thing that wasn't rendering does (in the case of this component, it's the successMessage that doesn't change). I have no idea what could be causing this as I changed a bunch of things yesterday. If you have any idea please let me know as this is confounding me.
I figured it out. Turns out that using refs was the reason this was happening. I'm assuming that based on the way refs work, it didn't re-render the page after they changed (which I guess makes sense since they don't trigger useEffects either).
I'm trying to validate a simple form with a single input just for practice. I also don't want the value that the user types in the input to disappear after page refresh, for that reason, I did a little bit of searching and found out about saving that data using localStorage. After trying to implement that for a while, I managed to do that, when I refresh the page, the value is still there. However, now, when I'm trying to validate the form using useForm from react-hook-form, It just doesn't work for some reason, when I try to use that same useForm logic with an input without using localStorage, It works just fine, but while trying to add localStorage functionality, then it doesn't. I hope I'm describing my problem at least okey, here's the code :
import React, {useEffect, useState } from "react";
import "./App.css"
import { useForm } from "react-hook-form";
const getForm = () => {
const storedValues = localStorage.getItem("form");
if(!storedValues) return {
name: "",
age: ""
}
return JSON.parse(storedValues);
}
function Home() {
const [values, setValues] = useState(getForm)
const {register, handleSubmit, watch} = useForm();
const handleChange = (e) => {
setValues((previousValues) => ({
...previousValues,
[e.target.name]: e.target.value,
}))
}
const onSubmit = async data => { console.log(data); };
useEffect(()=>{
localStorage.setItem("form", JSON.stringify(values))
}, [values])
return (
<div className="container">
<form onSubmit={handleSubmit(onSubmit)}>
<input value={values.name} onChange={handleChange} name="name" placeholder="name" />
<input value={values.age} onChange={handleChange} name="age" placeholder="age"/>
<button type="submit">Submit</button>
</form>
</div>
)
}
export default Home;
This code works fine since I'm not adding useForm register to the input, but if I do that, then It gets buggy, like this :
<input value={values.name} onChange={handleChange} name="name" placeholder="name" {...register("name")} />
The latest code only works If I remove the value atrribute from the input, but I can't do that, If I do, I can't use localStorage anymore.
Looking at the documentation, you had the syntax a little off with your register function. That function takes a second argument, which is an object of props, and that is where you want to define value, name and onChange.
Like this:
<input
placeholder="name"
{...register("name", {
onChange: handleChange,
name: "name",
value: values.name
})}
/>
Here is the full code I have working on a codesandbox. That's really all I changed, expect removing the watch import.
import React, { useEffect, useState } from "react";
import "./styles.css";
import { useForm } from "react-hook-form";
const getForm = () => {
const storedValues = localStorage.getItem("form");
if (!storedValues)
return {
name: "",
age: ""
};
return JSON.parse(storedValues);
};
function Home() {
const [values, setValues] = useState(getForm);
const { register, handleSubmit } = useForm();
const handleChange = (e) => {
setValues((previousValues) => ({
...previousValues,
[e.target.name]: e.target.value
}));
};
const onSubmit = async (data) => {
console.log(data);
};
useEffect(() => {
localStorage.setItem("form", JSON.stringify(values));
}, [values]);
return (
<div className="container">
<form onSubmit={handleSubmit(onSubmit)}>
<input
placeholder="name"
{...register("name", {
onChange: handleChange,
name: "name",
value: values.name
})}
/>
<input
value={values.age}
onChange={handleChange}
name="age"
placeholder="age"
/>
<button type="submit">Submit</button>
</form>
</div>
);
}
export default Home;
I'm trying to implement a login function to my react app.
import React, { useState, useEffect } from 'react'
import { useQuery, useLazyQuery, useMutation } from "#apollo/client"
import { useForm } from "react-hook-form"
import { LOGIN } from '../queries/queries'
const Login = () => {
const [formValue, setFormValue] = useState({})
const { loading, error, data } = useQuery(LOGIN, {
variables: {
email: formValue.email,
password: formValue.password
}
})
const { register, handleSubmit } = useForm()
const onSubmit = (value) => {
setFormValue(value)
}
if (loading) return <p>loading</p>
return(
<>
<form onSubmit={handleSubmit(onSubmit)} >
<input
type="text"
name="email"
placeholder="E-mail"
ref={register}
/>
<input
type="password"
name="password"
placeholder="Password"
ref={register}
/>
<button type="submit">
Login
</button>
</form>
</>
)
}
When I code console.log(data.user) for example, error happens because user is not undefined.
I know I can get object from data if I code variables directly, but I want to get it after handleSubmit.
I think if I can make data object initially, error would not happen.
Then is there any way to do that?
try "data?.user" instead of "data.user" when referring to that object attribute
the question mark should disable the error if the object doesnt exist
update:
you can also try declaring data as an empty objec literal:
{ loading, error, data = {} }
Since I'm learning how to build React forms with hooks, I went through the 3 quicks posts that culminate with this one. Everything is going well until I get to the last step when you create your custom hook with:
function useFormInput(initialValue) {
const [value, setValue] = useState(initialValue);
function handleChange(e) {
setValue(e.target.value);
}
return {
value,
onChange: handleChange
};
}
The Input is:
const Input = ({ type, name, onChange, value, ...rest }) => (
<input
name={name}
type={type}
value={value}
onChange={event => {
event.preventDefault();
onChange(name, event.target.value);
}}
{...rest}
/>
);
And the Form is:
const Form = () => {
const email = useFormInput("");
const password = useFormInput("");
return (
<form
onSubmit={e =>
e.preventDefault() || alert(email.value) || alert(password.value)
}
>
<Input
name="email"
placeholder="e-mail"
type="email"
{...email}
/>
<Input
name="password"
placeholder="password"
type="password"
{...password}
/>
<input type="submit" />
</form>
);
};
So in useFormInput() Chrome complains about
TypeError: Cannot read property ‘value’ of undefined at handleChange
which I'm pretty sure is pointing me to
function handleChange(e) {
setValue(e.target.value);
}
If I console.log(e) I get 'email', as expected (I think?), but if I try console.log(e.target) I get undefined. So obviously e.target.value doesn't exist. I can get it working by just using
setValue(document.getElementsByName(e)[0].value);
but I don't know what kind of issues this might have. Is this a good idea? Are there drawbacks to getting it to work this way?
Thanks
The issue comes from the onChange prop in the Input component
onChange={event => {
event.preventDefault();
onChange(name, event.target.value);
}}
you're calling onChange like this onChange(name, event.target.value); (two arguments, the first one is a string), while in your custom hook you define the callback like this
function handleChange(e) {
setValue(e.target.value);
}
it's expecting one argument, an event.
So either call onChange with one argument (the event) :
onChange={event => {
event.preventDefault();
onChange(event);
}}
or change the implementation of the callback.
Try this out:
const handleChange = e => {
const { inputValue } = e.target;
const newValue = +inputValue;
setValue(newLimit);
};
Had this issue with a calendar picker library react-date-picker using Register API. Looking at the documentation found out that there's another way of handling components that don't return the original event object on the onChange function using the Controller API.
More details on Controller API Docs
Example:
/*
* React Function Component Example
* This works with either useForm & useFormContext hooks.
*/
import { FC } from 'react'
import { Controller, useFormContext } from 'react-hook-form'
import DatePicker,{ DatePickerProps } from 'react-date-picker/dist/entry.nostyle'
const FormDateInput: FC<Omit<DatePickerProps, 'onChange'>> = ({
name,
...props
}) => {
const formMethods = useFormContext()
const { control } = formMethods ?? {}
return (
<Controller
render={({ field }) => <DatePicker {...props} {...field} />}
name={name ?? 'date'}
control={control}
/>
)
}
export default FormDateInput