I have the following code:
import React from "react"
export default function Form() {
const [formData, setFormData] = React.useState(
{firstName: ""}
)
console.log(formData.comments)
function handleChange(event) {
setFormData(prevFormData => {
return {
...prevFormData,
[event.target.name]: event.target.value
}
})
}
return (
<form>
<input
type="text"
placeholder="First Name"
onChange={handleChange}
name="firstName"
value={formData.firstName}
/>
</form>
)
}
I'm trying to understand a few aspects of that that I'm not 100% on, and would appreciate if anyone could advise.
My understanding of the order of operations is as such:
The user types text into the input
onChange runs and triggers the handleChange callback
The handle change callback runs. This triggers the setter function which changes the state.
The form component is re-rendered due to the setter function being run.
I believe this is right, but it leads to a bit of confusion for me:
How is the console.log(formData.comments) being run each time a change is made? Does this happen when the form component is re-rendered? And if so, why doesn't const [formData, setFormData] = React.useState({firstName: ""}); also reset each time?
When my handleChange function runs, it triggers the callback which returns an identical object, except that we reassign one property, which is: [event.target.name] : event.target.value. If event.target.name and event.target.value are coming from what I pass to the input component, and value={formData.firstName}, how does my state ever update? Since I type a new character in the field, but the state isn't updated, and therefore value isn't updated, which means the old value should still be used.
Where can I view more information about event which is passed to my handler function?
Thanks to anyone who can help, I appreciate it!
The console statement runs because of the re-rendering of the component . Since formData is a state and uses useState api , it is not meant to re-initialize . Thats how react works since it compares the DOM to render new changes .
The setFormData is called and although it still contains the previous value since you are hardcoding the input value which is same as before but since the set method api of the useState is called the component is bound to re-render . Again this is how react works
You can always console log the event you passed inside the handler
Related
I want to see the changes in the use of react hooks on state immediately. My changes are one step behind on state. How can I solve this.
const [categoryID, setCategoryID] = useState(0);
const changeCategory = (e) => {
setCategoryID(e.target.value);
console.log(categoryID);
};
<Field
as="select"
onChange={changeCategory}
style={formStyle().inputStyle}
className="form-control"
type="text"
name="categoryID"
>
When I select the first value, the result appears 0. If I chose my second value, I see the value I chose first on the console.
State change is asynchronous in react hooks hence there is no guarantee that the state will be updated just after setting it.
For this reason we have useEffect to the rescue that accepts a dependency and will be executed after that dependency is changed.
Example-
useEffect(() => {
console.log(categoryID);
},[categoryID])
Now in this case the dependency is categoryID and whenever it's value is changed the function will be executed and will console the updated value.
The main reason behind this behavior is setState is asynchronous so the changes should not be expected immediately.
Specially for this cases there is a hook called useEffect() to capture changes on state. If you add categoryId into the dependency array of useEffect then it will be trigger once you have change on that state. Thus you can log your changes as the following:
useEffect(() => {
console.log('changed', categoryID);
}, [categoryID]);
Suggested read is Using the Effect Hook.
We have a big web application mostly built using KnockoutJS. I'm looking at if there is a possibility to migrate to React, but in a way that require rewriting the entire application. My idea was to use a bottom-up approach: start by replacing the basic building blocks one by one.
I was inspired by some prior work to call ReactDOM.render inside a KnockoutJS binding handler:
ko.bindingHandlers.react = {
init() {
return {controlsDescendantBindings: true};
},
update(element, valueAccessor) {
const {component, props} = valueAccessor();
ReactDOM.render(React.createElement(component, props), element);
}
};
Knockout has its own dependency tracking system, so it will call the update method whenever some data changes.
It works perfectly, and the component is re-rendered to reflect any changes to data. However, it breaks down when data is updated inside a React event handler. E.g., this component does not work as expected:
const Input = function ({value}) {
return <input type="text"
value={value()}
onChange={e => value(e.target.value)}/>;
}
Note: value in this case is a ko.observable, and calling it like in the event handler will cause the bindingHandler's update method to be called, which in turn calls ReactDOM.render. However, this render only works once, after that the component stops updating.
The issue is demonstrated in this CodePen. Click the box and try to type something. One update goes through, after than, the component's function stops being called.
Edit: I believe the issue is that the second call to ReactDOM.render (when the value is updated by the user in the onChange handler) is not synchronous. This means that Knockout's dependency detection cannot work and any subsequent calls no longer call the update method of the binding handler.
Can this be circumvented somehow?
As I guessed, the issue seems to be that ReactDOM.render is asynchronous in some cases - in this case when called from an event handler in React.
This means that if you dereference any observables that you depend on in the update method itself, Knockout's dependency tracking mechanism works as expected. This is why the modification Gawel1908 proposed makes it work - not because the value is "reset", but because props.value is dereferenced.
I decided to instead use a convention: always unwrap any such observables in the valueAccessor itself:
<div data-bind="react: { component: Input, props: { value: val(), setValue: val }}">
</div>
And don't unwrap it in the component:
const Input = function ({value, setValue}) {
return <input
type="text"
value={value}
onChange={e => setValue(e.target.value)}/>;
}
Updated, working codepen.
For me worked:
update(element, valueAccessor) {
const {component, props} = valueAccessor();
props.value(props.value());
ReactDOM.render(React.createElement(component, props), element);
}
if you refresh your observable it will work but unfortunetaly i don't know how.
i saw similar questions but can't get some solution.
i have some input when you paste some text to input i'm trying to set it to state and render this text. But when paste in first time state not change in second time when paste everything works.
I don't understand why it happen i already used some setTimeOut function too for it but not helped.
here is my code:
import React from "react";
import { render } from "react-dom";
class App extends React.Component {
constructor() {
super();
this.state = {
no: ""
};
}
pasteSomeText = e => {
this.setState({
no: e.target.value
});
};
render() {
return (
<div style={styles}>
{this.state.no}
<input onPaste={event => this.pasteSomeText(event)} />
</div>
);
}
}
render(<App />, document.getElementById("root"));
Here is codeSandBox example: https://codesandbox.io/s/1-react-hello-world-tvscf?fontsize=14
To get the data from the event, use e.clipboardData.getData('text'). See https://developer.mozilla.org/en-US/docs/Web/API/Element/paste_event
e.target.value gives you the value for the text field before it updates with the paste data, so you can cancel/modify the paste if you want.
pasteSomeText(e) {
this.setState({
no: e.clipboardData.getData('text')
});
};
Side note: with the call from the HTML, you don't need to use this arrow function (which uses more memory), see my example above
Basically the reason why you get empty state on firts onpaste call is that onpaste fires before the input's value is changed. So you get empty event.target.value and for all other calls you get previous value.
And if you still decide to keep going with onpaste you should keep in mind that you can get pasted value with event.clipboardData.getData('Text') but it can differ from what have been pasted in input because of different behavior of clipboardData for different input types.
https://developer.mozilla.org/en-US/docs/Web/API/DataTransfer/types
So for work with clipboard data I will recommend to use DataTransfer module from https://www.npmjs.com/package/fbjs to get more consistant behavior in different systems and browsers.
Is a diferente kind of event you will need to get the clipboard data from the event and set it to the state
const value = e.clipboardData.getData('text')
use
onChange
instead of using
onPaste
Updated https://codesandbox.io/s/1-react-hello-world-h2jgv
I cannot get my checkbox state to change. The default option in my Mongoose model is false. I can successfully get it to update the user's profile when they check it and submit only on the first time (it'll change to true), but when they go back to edit the profile (same routes) and $set the profile fields, it doesn't change the boolean value of the "displayEmailOnProfile" to false. It just remains checked. The console.logs() are kinda weird as well. When the box is checked, it prints out true and then the state for displayEmailOnProfile is false. When it's unchecked, the state for displayEmailOnProfile is true?
Then when I hit submit, it's not updating anything on the mongoose model?
constructor (props) {
super(props);
this.state = {
displayEmailOnProfile: false,
errors: {}
}
this.onSubmit = this.onSubmit.bind(this);
this.onCheck = this.onCheck.bind(this);
}
componentWillReceiveProps(nextProps) {
// Set component fields state
this.setState({
displayEmailOnProfile: profile.displayEmailOnProfile,
});
}
}
onChange = (e) => {
this.setState({[e.target.name]: e.target.value});
}
onSubmit = (e) => {
e.preventDefault();
const profileData = {
displayEmailOnProfile: this.state.displayEmailOnProfile
}
this.props.createProfile(profileData, this.props.history);
}
onCheck = (e) => {
console.log(this.state);
console.log(e.currentTarget.checked);
this.setState({
displayEmailOnProfile: e.currentTarget.checked
});
}
And here is the HTML / React markup
<div className="form-check mb-4">
<input className="form-check-input" type="checkbox" id="defaultCheck1" name="displayEmailOnProfile" checked={this.state.displayEmailOnProfile} onChange={this.onCheck}></input>
<label class="form-check-label" for="customCheck1">Display Email On Profile</label>
</div>
Your code is completely correct and console.log is printing the right thing. You are doing console.log inside the onCheck method — which is printing the current value.
What happens is, when you do set state, life cycle methods are called asynchronously which update the component and set the state. So, if you do console.log of your state right after setting the state (which is synchronous), it will print the previous state, because it is not yet updated.
Following are the lifecycle methods which are called:
shouldComponentUpdate
componentWillUpdate
componentDidUpdate
So, if you want to check if the value was updated or not, you should do console.log inside componentDidUpdate. It will tell you your updated state.
In order to explain the working, I have created a short demo for you on code sandbox with your code sample. Which explains the working and shows that your code is correct.
Now, as far as it is concerned that Mongoose is not updating the values, I can't say anything without looking at your code. You must be having some mistakes in the code for updating the values. I'm not sure, but cross check if you are using the same page for updating as for submit make sure, you change the request from post to put and check for how to update values using Mongoose.
I want to update an array each time a checkbox is toggled to true. With this current code, if I click on a checkbox it will log that it's false. Even though I have just updated the state. Does setState just take some time, like an API call? That doesn't make sense to me.
import React, {Component} from 'react';
class Person extends Component {
constructor(props) {
super(props);
this.state = {
boxIsChecked: false
};
this.checkboxToggle = this.checkboxToggle.bind(this);
}
checkboxToggle() {
// state is updated first
this.setState({ boxIsChecked: !this.state.boxIsChecked });
console.log("boxIsChecked: " + this.state.boxIsChecked);
if (this.state.boxIsChecked === false) {
console.log("box is false. should do nothing.");
}
else if (this.state.boxIsChecked === true) {
console.log("box is true. should be added.");
this.props.setForDelete(this.props.person._id);
}
}
render() {
return (
<div>
<input type="checkbox" name="person" checked={this.state.boxIsChecked} onClick={this.checkboxToggle} />
{this.props.person.name} ({this.props.person.age})
</div>
);
}
}
export default Person;
I have tried onChange instead of onClick. I feel like I'm already following advice I've read about for the basic component formulation from here and here. Is the fact I'm using Redux for other values affecting anything? Is there a way to just read what the checkbox is, instead of trying to control it? (The checkbox itself works fine and the DOM updates wether it's checked or not correctly.)
I know that this thread is answered but i also have a solution for this, you see the checkbox didn't update with the provided value of setState, i don't know the exact reason for this problem but here is a solution.
<input
key={Math.random()}
type="checkbox"
name="insurance"
defaultChecked={data.insurance}
/>
by giving the key value of random the checkbox gets re-rendered and the value of the checkbox is updated, this worked for me. Also i am using hooks, but it should work for class based implementation to.
reference: https://www.reddit.com/r/reactjs/comments/8unyps/am_i_doing_stupid_or_is_this_a_bug_checkbox_not/
setState() is indeed not reflected right away:
Read here in the docs:
setState() enqueues changes to the component state and tells React
that this component and its children need to be re-rendered with the
updated state. This is the primary method you use to update the user
interface in response to event handlers and server responses.
Think of setState() as a request rather than an immediate command to
update the component. For better perceived performance, React may
delay it, and then update several components in a single pass. React
does not guarantee that the state changes are applied immediately.
setState() does not always immediately update the component. It may
batch or defer the update until later. This makes reading this.state
right after calling setState() a potential pitfall. Instead, use
componentDidUpdate or a setState callback (setState(updater,
callback)), either of which are guaranteed to fire after the update
has been applied. If you need to set the state based on the previous
state, read about the updater argument below.
setState() will always lead to a re-render unless
shouldComponentUpdate() returns false. If mutable objects are being
used and conditional rendering logic cannot be implemented in
shouldComponentUpdate(), calling setState() only when the new state
differs from the previous state will avoid unnecessary re-renders.
and here some experiments
So it might be better to catch the event and check it, and not depend on the this.setState()
Something like that:
handleChange: function (event) {
//you code
if (event.target.checked) {
console.log("box is true. should be added.");
this.props.setForDelete(this.props.person._id);
}
}
and
render() {
return (
<div>
<input type="checkbox" name="person" checked={this.state.boxIsChecked}
onChange={this.handleChange.bind(this)}/>
{this.props.person.name} ({this.props.person.age})
</div>
);
}
You can't access the updated value of state just after calling setState().
If you want to do stuff with updated value of state, you can do like this. i.e inside setState() callback function
checkboxToggle() {
// state is updated first
this.setState({ boxIsChecked: !this.state.boxIsChecked },()=>{
console.log("boxIsChecked: " + this.state.boxIsChecked);
if (this.state.boxIsChecked === false) {
console.log("box is false. should do nothing.");
}
else if (this.state.boxIsChecked === true) {
console.log("box is true. should be added.");
this.props.setForDelete(this.props.person._id);
}
});
React solution:
HTML:
<input type="checkbox" ref={checkboxRef} onChange={handleChange}/>
JS:
const checkboxRef = useRef("");
const [checkbox, setCheckbox] = useState(false);
const handleChange = () => {
setCheckbox(checkboxRef.current.checked);
};
Now your true/false value is stored in the checkbox variable
To expand no newbies answer; what worked for me:
remove preventDefault() from onChange Event-Listener but this didn't feel right for me since I want only react to update the value. And it might not apply in all cases like OPs.
include the checked value in the key like key={"agree_legal_checkbox" + legalcheckboxValue} checked={legalcheckboxValue}
I got both these solutions from newbies answer but having a math.random key just felt wrong. Using the value for checked causes the key only to change when when the checked value changes as well.
(not enough reputation to simply post this as a comment, but still wanted to share)
I arrived here trying to figure out why my checkbox, rendered using the HTM tagged template library, wasn't working.
return html`
...
<input type="checkbox" checked=${state.active ? "checked" : null} />
...
`;
It turns out I was just trying too hard with that ternary operator. This works fine:
return html`
...
<input type="checkbox" checked=${state.active} />
...
`;