State Change Isn't Saving in React and Mongoose? - javascript

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.

Related

React Updating Form Data

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

NextJS - can't identify where state change is occurring on page component when using shallow Router push

I have an app. that uses NextJS. I have a page that looks like the following:
import React from 'react'
import { parseQuery } from '../lib/searchQuery'
import Search from '../components/search'
class SearchPage extends React.Component {
static getInitialProps ({ query, ...rest }) {
console.log('GET INITIAL PROPS')
const parsedQuery = parseQuery(query)
return { parsedQuery }
}
constructor (props) {
console.log('CONSTRUCTOR OF PAGE CALLED')
super(props)
this.state = props.parsedQuery
}
render () {
return (
<div>
<div>
<h1>Search Results</h1>
</div>
<div>
<h1>DEBUG</h1>
<h2>PROPS</h2>
{JSON.stringify(this.props)}
<h2>STATE</h2>
{JSON.stringify(this.state)}
</div>
<div>
<Search query={this.state} />
</div>
</div>
)
}
}
export default SearchPage
getInitialProps is ran for SSR - it receives the query string as an object (via Express on the back end) runs it through a simple 'cleaner' function - parseQuery - which I made, and injects it into the page via props as props.parsedQuery as you can see above. This all works as expected.
The Search component is a form with numerous fields, most of which are select based with pre-defined fields and a few a number based input fields, for the sake of brevity I've omitted the mark up for the whole component. Search takes the query props and assigns them to its internal state via the constructor function.
On changing both select and input fields on the Search component this code is ran:
this.setState(
{
[label]: labelValue
},
() => {
if (!this.props.homePage) {
const redirectObj = {
pathname: `/search`,
query: queryStringWithoutEmpty({
...this.state,
page: 1
})
}
// Router.push(href, as, { shallow: true }) // from docs.
this.props.router.push(redirectObj, redirectObj, { shallow: true })
}
}
)
The intention here is that CSR takes over - hence the shallow router.push. The page URL changes but getInitialProps shouldn't fire again, and subsequent query changes are handled via componentWillUpdate etc.. I confirmed getInitialProps doesn't fire again by lack of respective console.log firing.
Problem
However, on checking/unchecking the select fields on the Search component I was surprised to find the state of SearchPage was still updating, despite no evidence of this.setState() being called.
constructor isn't being called, nor is getInitialProps, so I'm unaware what is causing state to change.
After initial SSR the debug block looks like this:
// PROPS
{
"parsedQuery": {
"manufacturer": [],
"lowPrice": "",
"highPrice": ""
}
}
// STATE
{
"manufacturer": [],
"lowPrice": "",
"highPrice": ""
}
Then after checking a select field in Search surprisingly it updates to this:
// PROPS
{
"parsedQuery": {
"manufacturer": ["Apple"],
"lowPrice": "",
"highPrice": ""
}
}
// STATE
{
"manufacturer": ["Apple"],
"lowPrice": "",
"highPrice": ""
}
I can't find an explanation to this behaviour, nothing is output to the console and I can't find out how to track state changes origins via dev. tools.
Surely the state should only update if I were to do so via componentDidUpdate? And really shouldn't the parsedQuery prop only ever be updated by getInitialProps? As that's what created and injected it?
To add further confusion, if I change a number input field on Search (such as lowPrice), the URL updates as expected, but props nor page state changes in the debug block. Can't understand this inconsistent behaviour.
What's going on here?
EDIT
I've added a repo. which reproduces this problem on as a MWE on GitHub, you can clone it here: problem MWE repo.
Wow, interesting problem. This was a fun little puzzle to tackle.
TL;DR: This was your fault, but how you did it is really subtle. First things first, the problem is on this line:
https://github.com/benlester/next-problem-example/blob/master/frontend/components/search.js#L17
Here in this example, it is this:
this.state = props.parsedQuery
Let's consider what is actually happening there.
In IndexPage.getInitialProps you are doing the following:`
const initialQuery = parseQuery({ ...query })
return { initialQuery }
Through Next's mechanisms, this data passes through App.getInitialProps to be returned as pageProps.initialQuery, which then becomes props.initialQuery in IndexPage, and which is then being passed wholesale through to your Search component - where your Search component then "makes a copy" of the object to avoid mutating it. All good, right?
You missed something.
In lib/searchQuery.js is this line:
searchQuery[field] = []
That same array is being passed down into Search - except you aren't copying it. You are copying props.query - which contains a reference to that array.
Then, in your Search component you do this when you change the checkbox:
const labelValue = this.state[label]
https://github.com/benlester/next-problem-example/blob/master/frontend/components/search.js#L57
You're mutating the array you "copied" in the constructor. You are mutating your state directly! THIS is why initialQuery appears to update on the home page - you mutated the manufacturers array referenced by initialQuery - it was never copied. You have the original reference that was created in getInitialProps!
One thing you should know is that even though getInitialProps is not called on shallow pushes, the App component still re-renders. It must in order to reflect the route change to consuming components. When you are mutating that array in memory, your re-render reflects the change. You are NOT mutating the initialQuery object when you add the price.
The solution to all this is simple. In your Search component constructor, you need a deep copy of the query:
this.state = { ...cloneDeep(props.query) }
Making that change, and the issue disappears and you no longer see initialQuery changing in the printout - as you would expect.
You will ALSO want to change this, which is directly accessing the array in your state:
const labelValue = this.state[label]
to this:
const labelValue = [...this.state[label]]
In order to copy the array before you change it. You obscure that problem by immediately calling setState, but you are in fact mutating your component state directly which will lead to all kinds of weird bugs (like this one).
This one arose because you had a global array being mutated inside your component state, so all those mutations were being reflected in various places.

React: scrollIntoView only works inside of setTimeout

My application consists of a basic input where the user types a message. The message is then appended to the bottom of all of the other messages, much like a chat. When I add a new chat message to the array of messages I also want to scroll down to that message.
Each html element has a dynamically created ref based on its index in the loop which prints them out. The code that adds a new message attempts to scroll to the latest message after it has been added.
This code only works if it is placed within a setTimeout function. I cannot understand why this should be.
Code which creates the comments from their array
comments = this.state.item.notes.map((comment, i) => (
<div key={i} ref={i}>
<div className="comment-text">{comment.text}</div>
</div>
));
Button which adds a new comment
<input type="text" value={this.state.textInput} onChange={this.commentChange} />
<div className="submit-button" onClick={() => this.addComment()}>Submit</div>
Add Comment function
addComment = () => {
const value = this.state.textInput;
const comment = {
text: value,
ts: new Date(),
user: 'Test User',
};
const newComments = [...this.state.item.notes, comment];
const newState = { ...this.state.item, notes: newComments };
this.setState({ item: newState });
this.setState({ textInput: '' });
setTimeout(() => {
this.scrollToLatest();
}, 100);
}
scrollToLatest = () => {
const commentIndex = this.state.xrayModalData.notes.length - 1;
this.refs[commentIndex].scrollIntoView({ block: 'end', behavior: 'smooth' });
};
If I do not put the call to scrollToLatest() inside of a setTimeout, it does not work. It doesn't generate errors, it simply does nothing. My thought was that it was trying to run before the state was set fully, but I've tried adding a callback to the setState function to run it, and it also does not work.
Adding a new comment and ref will require another render in the component update lifecycle, and you're attempting to access the ref before it has been rendered (which the setTimeout resolved, kind of). You should endeavor to use the React component lifecycle methods. Try calling your scrollToLatest inside the lifecycle method componentDidUpdate, which is called after the render has been executed.
And while you're certainly correct that setting state is an asynchronous process, the updating lifecycle methods (for example, shouldComponentUpdate, render, and componentDidUpdate) are not initiated until after a state update, and your setState callback may be called before the component is actually updated by render. The React docs can provide some additional clarification on the component lifecycles.
Finally, so that your scroll method is not called on every update (just on the updates that matter), you can implement another lifecycle method, getSnapshotBeforeUpdate, which allows you to compare your previous state and current state, and pass a return value to componentDidUpdate.
getSnapshotBeforeUpdate(prevProps, prevState) {
// If relevant portion or prevState and state not equal return a
// value, else return null
}
componentDidUpdate(prevProps, prevState, snapshot) {
// Check value of snapshot. If null, do not call your scroll
// function
}

React Checkbox Does Not Update

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} />
...
`;

Understanding ReactJS Controlled form components

I am implementing the following code based on the following page: https://facebook.github.io/react/docs/forms.html
class Reservation extends React.Component {
constructor(props) {
super(props);
this.state = {
isGoing: true,
numberOfGuests: 2
};
this.handleInputChange = this.handleInputChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
}
handleInputChange(event) {
const target = event.target;
const value = target.type === 'checkbox' ? target.checked : target.value;
const name = target.name;
this.setState({
[name]: value
});
}
handleSubmit(event) {
event.preventDefault();
let data = {
isGoing: this.state.isGoing,
numberOfGuests: this.state.numberofGuests
}
/* Send data in ajax request here */
}
render() {
return (
<form onSubmit={this.handleSubmit}>
<label>
Is going:
<input
name="isGoing"
type="checkbox"
checked={this.state.isGoing}
onChange={this.handleInputChange} />
</label>
<br />
<label>
Number of guests:
<input
name="numberOfGuests"
type="number"
value={this.state.numberOfGuests}
onChange={this.handleInputChange} />
</label>
<input type="submit" value="Submit" />
</form>
);
}
}
Some questions I have about it:
Why do we need to store the component values in state? Why not
just grab the values we need when the form is submitted as normally
would be done with standard JavaScript? The recommended way would seem to reload the render function for every single character typed in or deleted. To me this makes little sense.
Since the execution of setState is asynchronous and there may be a delay between this.setState() and actually accessing the state with this.state.numberOfGuests does this mean that this code may end up grabbing the state before it has been set? If so, why is this code being suggested in the official React docs? If not, why not?
Regarding point number two, then yes it is logically possible that handleSubmit could run before the state update in handleInputChanged has completed. The reason this isn't mentioned in the React docs, or is generally a concern for anyone is because the setState function runs really quickly. As an experiment I made a codepen to determine the average time taken for setState to run. It seems to take in the order of around 0.02 milliseconds. There is no way someone can change their input, then submit the form in less than that time. In fact, the e.preventDefault() call in handleSubmit takes nearly a quarter of that time anyway.
If you have a situation where it is absolutely crucial that setState has completed before continuing, then you can use a callback function to setState, e.g.
this.setState({
colour: 'red'
}, () => {
console.log(this.state.color)
});
Then red will always be logged, as opposed to the following where the previous value may be logged.
this.setState({
colour: 'red'
});
console.log(this.state.color);
Very good questions! Here's my take on them:
1. Controlled or Uncontrolled - that is the question
You don't have to use controlled form elements. You can use uncontrolled and grab the values as you suggest in your onFormSubmit handler by doing something like event.isGoing.value - plain ole JavaScript (or use refs as some React articles suggest). You can even set a default value with uncontrolled no problem by using, you guessed it, defaultValue={myDefaultValue}.
The above being said, one reason to use controlled components would be if you're looking to give real time feedback while the user is still typing. Say you need to do an autocomplete lookup or provide validation like password strength. Having a controlled component that re-renders with the values in the state makes this super simple.
2. this.setState() asynchronous issues?
[Maybe incorrectly,] I view internally component state updates more like a queue system. No call to this.setState() will be lost and shouldn't overwrite another one when dealing with synchronous code. However, there could be a time where a render is running behind a setState update, but it will eventually have and render the most recent value. Ex: the user types 3 characters, but they only see 2, then a short time later they should see the third. So, there was a point in time where the read to this.state read an "old" value, but it was still eventually updated. I hope I'm making sense here.
Now, I mention synchronous code above because with asynchronous code (like with AJAX) you could potentially introduce a race condition where this.setState() overwrites a newer state value.
Why do we need to store the component values in state?
We don't really need to store the component values in state and it's perfectly fine to access those values on form submit.
However, storing the component values in state has its own advantages. The main reason behind this is to make React state as the single source of truth. When the state is updated (on handleInputChange method), React checks the components (or specifically the parts of components or sub-trees) which needs to be re-rendered.
Using this approach, React helps us to achieve what was earlier achieved via Two Way Binding helpers. In short:
Form updated -> handleInputChange -> state updated -> updated state passed to other components accessing it
For example say, you have a <Card /> component which needs input from the user via a <Form /> component and you wish to display the information as the user types, then the best way would be update your state which would cause React to look for the sub-trees where it was accessed and re-render only those. Thus your <Card /> component will update itself as you type in the <Form />.
Also, React doesn't re-render everything for every single character typed but only those sub-trees which needs to reflect the text change. See the React diffing algorithm for how it is done. In this case, as you type in the characters in a particular field in the form, only those sub-trees in components will be re-rendered which display that field.
Execution of setState and asynchronous behavior
As mentioned by the React docs:
State Updates May Be Asynchronous
React may batch multiple setState() calls into a single update for
performance.
Because this.props and this.state may be updated asynchronously, you
should not rely on their values for calculating the next state.
Your code should work fine since you're not relying on the previous state to calculate the next state and also you're accessing this.state in a different block. So your state updates should be reflected fine; however if you don't want to think about the state updates (or suspect that your code might pick up previous state), React docs also mention an alternative method (which is actually better) that accepts a function rather than an object:
this.setState((prevState, props) => ({
counter: prevState.counter + props.increment
}));
If you're still wondering when you can use the setState method safely, use it if other components don't rely on the state or when you don't need to persist the state (save in local storage or a server). When working with larger projects its always a good idea to make use of state containers to save yourself the hassle, like Redux.

Categories

Resources