UseEffect showing inappropriate value, but only once - javascript

I am trying to print the value of the state value whenever I change the password (using useEffect hook). Although it's working well, whenever I try to change the email, the value of email is also rendering in the console
useEffect(() => {
console.log(values);
}, [values.password]);
but as per my logic should be only rendered whenever the value of password is changed.
Following is the log
As I marked they must not be shown as they are rendering whenever I change the value of email
Following is my code
Form.js
import { useState } from "react";
export const useForm = (initialValues) => {
const [values, setValues] = useState(initialValues);
return [
values,
(e) => {
setValues({
//...values,
[e.target.name]: e.target.value,
});
},
];
};
App.js
import "./App.css";
import { useState, useEffect } from "react";
import { useForm } from "./Form";
const App = () => {
const [values, handelChange] = useForm({ email: "", password: "" });
useEffect(() => {
console.log(values);
}, [values.password]);
return (
<div className="field">
<input
type="email"
name="email"
value={values.email}
onChange={handelChange}
/>
<input
type="password"
name="password"
value={values.password}
onChange={handelChange}
/>
</div>
);
};
export default App;

The only thing you have to change is removing the commented values-destructoring at your useForm-hook:
return [
values,
(e) => {
setValues({
...values, // remove the comment from your code in the question!!
[e.target.name]: e.target.value,
});
},
];
};
The comment causes, that password is removed (you can call the prop password on values, but you get undefined) from the new values-object on every email-input. In the log, you see that, but as you described, only once!
Furthermore, I would change your useForm-hook to:
const useForm = (initialValues) => {
const [values, setValues] = useState(initialValues);
return [
values,
(e) => {
setValues(prevValues => {
return {
...prevValues,
[e.target.name]: e.target.value,
}
});
}
];
};
If the new state is computed using the previous state, you should use the previous state from params. React state updates can be batched, and not writing your updates this way can lead to unexpected results.

Related

Alter useMemo when write input in react testing library

I'm trying to do a test, in which when changing the input, I have to read the useMemo and change the disabled of my button, the userEvent is not making this change, has anyone gone through this?
I'm going to put part of my source code here, where the component and the test script are.
<>
<input
data-testid="ipt-email"
value={form.email}
onChange={(e) => {
setForm({ ...form, email: e.target.value });
}}
/>
<button data-testid="submit-sendUser" disabled={isDisabled}>
OK
</button>
</>
This is my hook
const isDisabled = useMemo(() => {
const { email } = form;
if (!email.length) return true
return false;
}, [form]);
Right after that is my unit test, where I write to the input and wait for the state to change
import userEvent from "#testing-library/user-event";
it("Should enable button when form is valid", async () => {
const wrapper = render(<MyComponent />);
const getEmail = wrapper.getByTestId("ipt-email");
await userEvent.type(getEmail, 'example#example.com');
const getBtnSubmit = wrapper.getByTestId("submit-sendUser");
console.log(wrapper.container.innerHTML);
expect(getBtnSubmit).not.toBeDisabled();
});
I can't make the input change reflect in the button hook
Need to wait for changes to occur after the action
await waitFor(() => expect(getBtnSubmit).not.toBeDisabled())
moving code inside the handler applieds to useEffect, but I feel its better to handle the validation inside the handler to so that code changes remain in one place.
Hope it helps
The combination of fireEvents and forced move to the "next frame" worked for me
const tick = () => {
return new Promise((resolve) => {
setTimeout(resolve, 0);
});
};
// async test
fireEvent.change(getEmail, {target: {value: 'example#example.com'}})
await tick();
expect(getBtnSubmit).not.toBeDisabled();
In your constant getEmail you get a component with a data-testid='ipt-name' instead of 'ipt-email' (but this is no longer relevant since the requester has modified his question...). The code below works for me :
my test :
import { render, screen, waitFor } from '#testing-library/react';
import App from './App';
import userEvent from '#testing-library/user-event';
it("Should enable button when form is valid", async () => {
render(<App />);
expect(screen.getByTestId("submit-sendUser")).toBeDisabled();
const getEmail = screen.getByTestId("ipt-email");
userEvent.type(getEmail, 'example#example.com');
await waitFor(() => expect(screen.getByTestId("submit-sendUser")).not.toBeDisabled());
});
my component :
import React, { useMemo, useState } from "react";
export const App = () => {
const [form, setForm] = useState({ email: '' });
const isDisabled = useMemo(() => {
const { email } = form;
if (!email || !email.length) return true;
return false;
}, [form]);
return (
<div>
<input
data-testid="ipt-email"
value={form.email}
onChange={(e) => {
setForm({ ...form, email: e.target.value });
}}
/>
<button data-testid="submit-sendUser" disabled={isDisabled}>
OK
</button>
</div>
);
};
export default App;

Nothing displays when I want to edit in ReactJS

When I want to edit my input nothing displays and I have this error:
Warning: A component is changing a controlled input to be uncontrolled. This is likely caused by the value changing from defined to undefined, which should not happen. Decide between using a controlled or uncontrolled input element for the lifetime of the component. More info: https://reactjs.org/link/controlled-components input form
But I have output in my console.log.
import axios from "axios";
import React, { useEffect } from "react";
import { useParams } from 'react-router-dom'
import { useState } from "react";
const Edituser = () => {
const { id } = useParams();
const [user, setUser] = useState({
name: "",
email: "",
password: "",
c_password: "",
role: ""
});
const { name, email, password, c_password, role } = user;
const onInputChange = e => {
setUser({ ...user, [e.target.name]: e.target.value })
};
useEffect(() => {
Updaate();
},
[]
);
const Updaate = async () => {
const response = await axios.get(`http://127.0.0.1:8000/api/edit-user/${id}`);
setUser(response.data)
console.log(response.data)
};
const Updateuser = async e => {
e.preventDefault();
await axios.put(
`http://127.0.0.1:8000/api/update-user/${id}`, user
);
};
return (
<form className="register-form">
<div className="head-register" >
<h3>register</h3>
</div>
<label htmlFor="name">Username</label>
<input name="name" onChange={e => onInputChange(e)} value={name} ></input>
<h1>hello</h1>
</form>
);
}
export default Edituser;
Nothing displays when I want to edit in React.

Uncaught TypeError: Cannot read property 'filter' of undefined React

I have a simple react app, which I am using to display a list of users. I have a search box, which filters the list of users and returns the searched user,
However, When I search for users using the search box, I get a TypeError: Cannot read property 'filter' of undefined
The page initially displays fine, it just when I search for the users that the error is thrown. I know the users are being generated correctly because I console logged filteredUsers The users are all outputted correctly.
Here is my App.js
import { useState } from 'react';
import React from 'react';
import CardList from './components/CardList';
import SearchBox from './components/SearchBox';
import { users } from './users';
import 'tachyons';
const App = () => {
const [state, setState] = useState({
users,
searchField: '',
});
const filteredUsers = state.users.filter((user) => {
return user.name.toLowerCase().includes(state.searchField.toLowerCase());
});
const onSearchChange = (e) => {
setState({ searchField: e.target.value });
};
return (
<div className='App tc'>
<h1>RoboFriends</h1>
<SearchBox searchChange={onSearchChange} />
<CardList robots={filteredUsers} />
</div>
);
};
export default App;
and here is my SearchBox.js
import React from 'react';
const SearchBox = ({ searchField, searchChange }) => {
return (
<div className='pa2'>
<input
className='pa3 ba b--green bg-lightest-blue'
type='search'
placeholder='Search Users'
onChange={searchChange}
/>
</div>
);
};
export default SearchBox;
Because you are using state.users here, filter expect it to be an array.
const filteredUsers = state.users.filter((user) => {
return user.name.toLowerCase().includes(state.searchField.toLowerCase());
});
so you need to set the initial state as an array
const [state, setState] = useState({
users: [], //or a initial value of array of users.
searchField: '',
});
You also need to retain the users when you update the state.
const onSearchChange = (e) => {
setState( prev => ({ ...prev, searchField: e.target.value }));
};
//OR
const onSearchChange = (e) => {
setState({ ...state, searchField: e.target.value });
};
However, this line is not good because it's being called on every render, you should probably "upgrade" it with useMemo like this.
const filteredUsers = useMemo(() => state.users.filter((user) => {
return user.name.toLowerCase().includes(state.searchField.toLowerCase());
}), [state])
Change
const onSearchChange = (e) => {
setState({ searchField: e.target.value });
};
to
const onSearchChange = (e) => {
setState({ searchField: e.target.name.value });
};
And make users into an array

How to change React-Hook-Form defaultValue with useEffect()?

I am creating a page for user to update personal data with React-Hook-Form.
Once paged is loaded, I use useEffect to fetch the user's current personal data and set them into default value of the form.
I put the fetched value into defaultValue of <Controller />.
However, it is just not showing in the text box.
Here is my code:
import React, {useState, useEffect, useCallback} from 'react';
import { useForm, Controller } from 'react-hook-form'
import { URL } from '../constants';
const UpdateUserData = props => {
const [userData, setUserData] = useState(null);
const { handleSubmit, control} = useForm({mode: 'onBlur'});
const fetchUserData = useCallback(async account => {
const userData = await fetch(`${URL}/user/${account}`)
.then(res=> res.json());
console.log(userData);
setUserData(userData);
}, []);
useEffect(() => {
const account = localStorage.getItem('account');
fetchUserData(account);
}, [fetchUserData])
const onSubmit = async (data) => {
// TODO
}
return (
<div>
<form onSubmit={handleSubmit(onSubmit)}>
<div>
<label>User Name:</label>
<Controller
as={<input type='text' />}
control={control}
defaultValue={userData ? userData.name : ''}
name='name'
/>
</div>
<div>
<label>Phone:</label>
<Controller
as={<input type='text' />}
control={control}
defaultValue={userData ? userData.phone : ''}
name='phone'
/>
</div>
<button>Submit</button>
</form>
</div>
);
}
export default UpdateUserData;
The called API is working well and the value is actually set to userData state.
{
name: "John",
phone: "02-98541566"
...
}
I also tried to setUserData with mock data in useEffect(), and it doesn't work either.
Is there any problem in my above code?
#tam answer is halfway through what is needed to make it work with version 6.8.3.
You need to provide the default value but also to useEffect to reset. That particular distinction is required if you have a form that you reload with another entity. I have a complete example in CodeSanbox here.
In a nutshell:
You need to define your defaultValues in the userForm.
const { register, reset, handleSubmit } = useForm({
defaultValues: useMemo(() => {
return props.user;
}, [props])
});
Then you need to listen to potential change.
useEffect(() => {
reset(props.user);
}, [props.user]);
The example in the Code Sandbox allows swapping between two users and have the form change its values.
You can use setValue (https://react-hook-form.com/api/useform/setvalue).
Import it from useForm:
const { handleSubmit, control, setValue} = useForm({ mode: 'onBlur' });
Then call it with the user data after it's received:
useEffect(() => {
if (userData) {
setValue([
{ name: userData.name },
{ phone: userData.phone }
]);
}
}, [userData]);
You can remove the default values from the form.
EDIT: See alternative answers below if this does not work.
setValue didn't work for me. Alternatively, you can use the reset method:
Reset either the entire form state or part of the form state.
Here is working code:
/* registered address */
const [registeredAddresses, setRegisteredAddresses] = useState([]);
const { register, errors, handleSubmit, reset } = useForm <FormProps> ({
validationSchema: LoginSchema,
});
/**
* get addresses data
*/
const getRegisteredAddresses = async () => {
try {
const addresses = await AddressService.getAllAddress();
setRegisteredAddresses(addresses);
setDataFetching(false);
} catch (error) {
setDataFetching(false);
}
};
useEffect(() => {
getRegisteredAddresses();
}, []);
useEffect(() => {
if (registeredAddresses) {
reset({
addressName: registeredAddresses[0].name,
tel: registeredAddresses[0].contactNumber
});
}
}, [registeredAddresses]);
Found another easy way, I used reset API from useForm
const { handleSubmit, register, reset } = useForm({ resolver });
After you call API and get back response data, you call reset with new apiData, make sure apiData key's are same as input keys (name attribute):
useEffect(() => {
reset(apiData);
}, [apiData]);
form's default values are cached and hence once you get the data from API, we reset the form state with new data.
#tommcandrew's setValue parameter formatting didn't work for me.
This format did:
useEffect(() => {
const object = localStorage.getItem('object');
setValue("name", object.name);
}, [])
although this post is 2 months old, I stumbled upon this issue today and searched for a couple of ways to do it. The most effective way I've come up with is using useMemo to set your defaultValues, like this :
const { control, errors, handleSubmit } = useForm({
reValidateMode: 'onChange',
defaultValues: useMemo(() => yourDefaultValues, [yourDefaultValues]),
});
This allows you to properly set values in your form, without the struggle of multiple implementations if you happen to have field arrays (which was my case).
This also works while using the advanced smart form component exemple from the official documentation. Let me know if you have any questions !
This works for nested objects (I'm using version 6.15.1)
useEffect(() => {
for (const [key, value] of Object.entries(data)) {
setValue(key, value, {
shouldValidate: true,
shouldDirty: true
})
}
}, [data])
Using reset is a simple solution.
const { reset } = useForm();
onClick={()=> reset({ firstname: 'Joe' }, { lastname: 'Doe' }) }
As of react-hook-form 7.41, you can use defaultValues with async functions like this:
const {
formState: { isLoading },
} = useForm({
defaultValues: fetch('API'),
// resetOptions: {
// keepDirtyValues: true
// }
});
now the defaultValue field type is look like this:
type AsyncDefaultValues<TFieldValues> = (payload?: unknown) => Promise<TFieldValues>;
isLoading for the async defaultValues loading state.

onChange in React doesn't capture the last character of text

This is my render function:
render: function() {
return <div className="input-group search-box">
<input
onChange={this.handleTextChange}
type="text"
value={this.state.text}
className="form-control search-item" />
<span className="input-group-btn"></span>
</div>
}
and I have this as my event handler:
handleTextChange: function(event) {
console.log(event.target.value);
this.setState({
text: event.target.value
});
}
The problem is that when I "save" an item, or console.log print the output, the last character is missing - for instance, if I enter "first", I'll get "firs" printed out, and there needs to be another key event to capture the last character. I've tried onKeyUp - which doesn't let me type anything in, and I've also tried onKeyDown and onKeyPress, which output nothing.
What is happening here and why? and how can I get that last character to show up?
When are you logging the state? Remember that setState is asynchronous, so if you want to print the new state, you have to use the callback parameter. Imagine this component:
let Comp = React.createClass({
getInitialState() {
return { text: "abc" };
},
render() {
return (
<div>
<input type="text" value={this.state.text}
onChange={this.handleChange} />
<button onClick={this.printValue}>Print Value</button>
</div>
);
},
handleChange(event) {
console.log("Value from event:", event.target.value);
this.setState({
text: event.target.value
}, () => {
console.log("New state in ASYNC callback:", this.state.text);
});
console.log("New state DIRECTLY after setState:", this.state.text);
},
printValue() {
console.log("Current value:", this.state.text);
}
});
Typing a d at the end of the input will result in the following being logged to the console:
Value from event: abcd
New state DIRECTLY after setState: abc
New state in ASYNC callback: abcd
Notice that the middle value is missing the last character. Here's a working example.
Since setState() function in asynchronous, I used await.I achieved this using async and await, here is my code
render: function() {
return <div className="input-group search-box">
<input
onChange={(e) => {this.handleTextChange(e)}}
type="text"
value={this.state.text}
className="form-control search-item" />
<span className="input-group-btn"></span>
</div>
}
The handleTextCahnge function:
handleTextChange = async function(event) {
await this.setState({text: event.target.value});
console.log(this.state.text);
}
Since React v.16.8 react hooks can be helpful.
I would recommend useState AND useEffect.
The Example is in React Native, however it should show how to work with the useEffect. More information about useEffect: https://reactjs.org/docs/hooks-effect.html
import React, {useState, useEffect} from 'react';
import { TextInput } from 'react-native';
export interface Props{}
const InformationInputs: React.FC<Props> = (props) => {
const [textInputs, setTextInputs] = useState("");
const handleValueChange = () => {
console.log(textInputs);
}
useEffect(() => {
handleValueChange();
}, [textInputs]);
return (
<TextInput
placeholder={'Text'}
onChangeText={(value: string) => { setTextInputs(value) }}
/>
);
};
import React, { useState, useEffect } from 'react';
const MyComponent = () => {
const [userInput, setUserInput] = useState("");
const changeHandler = (e) => {
setUserInput(e.target.value);
}
useEffect(()=> {
//here you will have correct value in userInput
},[userInput])
return (
<div>
<input onChange={changeHandler} value={userInput}></input>
</div>
)
}
setState() function in asynchronous. Without using callback you can use another auxiliary variable to store and use the updated value immediately. Like :
export default class My_class extends Component{
constructor(props)
{
super(props):
this.state={
text:"",
};
this.text="";
}
render: function() {
return <div className="input-group search-box">
<input
onChange={this.handleTextChange}
type="text"
value={this.state.text}
className="form-control search-item" />
<span className="input-group-btn"></span>
</div>
}
handleTextChange: function(event) {
console.log(event.target.value);
this.text = event.target.value;
this.setState({
text: event.target.value
});
}
You will get your updated value in this.text variable immediately. But you should use this.state.text to show text in your UI.
You are printing to the console before setting the state. Write your console log after the state is set. It will show the full text. (I had the same problem)
handleTextChange: function(event) {
**console.log(event.target.value)**
this.setState({
text: event.target.value
});
}
Since React v. 16.8 you can use react hooks.
import React, { useState } from 'react';
const MyComponent = () => {
const [userInput, setUserInput] = useState("");
const changeHandler = (e) => {
setUserInput(e.target.value);
}
return (
<div>
<input onChange={changeHandler} value={userInput}></input>
</div>
)
}
It works great for me.

Categories