onChange Not Updating State At Correct Time - javascript

I'm trying to save the value of the input field to state. When the defaultValue is 'projectName', and I delete the word 'Name' from the input field, I want the state to update so that the defaultValue is 'project'. When I console.log e.target.value in the onChange, I can see the change happening when I make the deletion, and my code in the onChange is saving the value to state, but unfortunately, the state does not update. Any thoughts as to why?
Here is a Code Sandbox: https://codesandbox.io/s/amazing-river-o15h4?file=/src/Child.js
... And here is a screenshot of the console.log in the onChange and the setState call not updating:
App.js
import "./styles.css";
import Child from "./Child";
export default function App() {
const thisIsState = {
id: 1,
projectName: "projectName",
description: "description"
};
return (
<div className="App">
<Child project={thisIsState} />
</div>
);
}
Child.js
import { useState, useEffect } from "react";
import "./styles.css";
export default function Child(props) {
console.log(props);
const [state, setState] = useState({
projectName: "",
description: ""
});
let project = props.project;
let errors = props.errors;
useEffect(
(state) => {
setState({
...state,
projectName: project.projectName,
description: project.description
});
console.log("useEffect1 state: ", state);
},
[project, errors]
);
const onChange = (e) => {
console.log("e.target.value in onChange: ", e.target.value);
setState((state) => ({
...state,
[e.target.name]: e.target.value
}));
console.log("onChange() state: ", state);
};
return (
<div className="App">
<form>
<input
type="text"
placeholder="Project Name"
name="projectName"
defaultValue={props.project.projectName}
onChange={onChange}
style={{ marginBottom: "15px" }}
/>
<br />
<input
type="text"
placeholder="Project Name"
name="projectDescription"
defaultValue={props.project.description}
onChange={onChange}
/>
</form>
</div>
);
}

Try something like this in your Child component instead of console.log(props). Props does not change because you did not change default state. If you try to log actual state, it is changing.
const [state, setState] = useState({
projectName: "",
description: ""
});
console.log(state);

This question has already been answered in a different question. The thing is setstate function is asynchronous. To overcome this you can use callback functions to print the state after it is updated. The link to the original answer is below
State not updating when printing on same function where updating in React Js

Related

How to listen redux state changes in react hooks?

I have multiple forms and buttons which user can edit now I would like to display a button save if the state of redux changes.
live demo : display button save when the state changes
Here is my redux.
const initialState = {
firstName: "Kunta ",
lastName: "Kinte",
age: 35,
country: "Ghana",
color: "#000"
};
const DetailsReducer = (state = initialState, action) => {
const { name, value } = action;
return { ...state, [name]: value };
};
export default DetailsReducer;
Here is my js code to show save button if there is a change in redux state
import React, { useState, useEffect } from "react";
import { useSelector, useDispatch } from "react-redux";
const Settings = () => {
const fields = useSelector((state) => state);
const dispatch = useDispatch();
const [saveBtn, setSaveBtn] = useState(false);
useEffect(() => {
setSaveBtn(true); // show save btn if there is changes in state
}, []);
console.log(fields.firstName);
return (
<div>
<div>
<h1>Edit </h1>
First Name:{" "}
<input
name="firstname"
value={fields.firstName}
onChange={(e) =>
dispatch({ name: "firstName", value: e.target.value, type: "" })
}
/>
{saveBtn === true && <button className="btn-save">save </button>}
</div>
</div>
);
};
export default Settings;
[1]: https://codesandbox.io/s/multiple-inputs-kkm6l?file=/src/Components/Settings.js:0-816
What do I need to do to solve this problem.?
Did you try this ?
const fields = useSelector((state) => state.WHATEVER_REDUCER);
useEffect(() => {
setSaveBtn(true); // show save btn if there is changes in state
}, [fields]);
You can try something like this:
<input
name="firstname"
value={fields.firstName}
onChange={(e) =>
dispatch({ name: "firstName", value: e.target.value, type: "" }, setSaveBtn(true))
}
/>
While also removing:
useEffect(() => {
setSaveBtn(true); // show save btn if there is changes in state
}, []);
You can do it like this. Remove effect hook, move setSaveBtn to input onChange and after you click save, just set setSaveBtn to false.
import React, { useState, useEffect } from "react";
import { useSelector, useDispatch } from "react-redux";
const Settings = () => {
const fields = useSelector((state) => state);
const dispatch = useDispatch();
const [saveBtn, setSaveBtn] = useState(false);
console.log(fields.firstName);
return (
<div>
<div>
<h1>Edit </h1>
First Name:{" "}
<input
name="firstname"
value={fields.firstName}
onChange={(e) => {
dispatch({ name: "firstName", value: e.target.value, type: "" })
setSaveBtn(true)
}}
/>
{saveBtn === true &&
<button
onClick={() => setSaveBtn(false)}
className="btn-save">save </button>}
</div>
</div>
);
};
export default Settings;

UseEffect showing inappropriate value, but only once

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.

react component does not rerender after update state

I have this react functional component where I have file input .
after I choose the file I assume the text in h1 tag should convert from
choose file to test but nothing happen
the handleChange function gets fired
the console.log print state.show : true
the INSIDE console.log() print state.show : true but does not show the string test
import React, { useState } from 'react';
export default ({ selectedFile }) => {
const [state, setState] = useState({});
const handleChange = e => {
console.log('in handleChange');
setState({ ...state, show: true });
};
console.log('My state: ', state);
return (
<div className='file-uploader'>
<input type='file' id='upload' hidden onChange={handleChange} />
<label htmlFor='upload'>
{state.show ? <h1> {console.log('INSIDE ', state)} test</h1> : <h1>choose file</h1>}
</label>
</div>
);
};
You need the following property {show: false} in your initial state.
import React, { useState } from 'react';
export default ({ selectedFile }) => {
const [state, setState] = useState({show: false});
const handleChange = e => {
console.log('in handleChange');
setState({ ...state, show: true });
};
console.log('My state: ', state);
return (
<div className='file-uploader'>
<input type='file' id='upload' hidden onChange={handleChange} />
<label htmlFor='upload'>
{state.show ? <h1>test</h1> : <h1>choose file</h1>}
</label>
</div>
);
};
Live Demo

How to manage a React form only with Redux? Without redux-forms

Before asking this I did a Google search and I didn't find any good resource to manage a form only with Redux. All of the examples use redux-forms. It is only one form and I don't want to install that library to use only on one, minimal, small form.
I can't use local state because the user has the option to go to another screen and then be back on the screen which contains the form, so at some point the component could be unmounted and mounted again and I want it to keep its state.
This is the component I have so far and the way I've been working on it:
import { compose } from 'redux';
import { connect } from 'react-redux';
import React from 'react';
import FormField from '../FormField/FormField';
import {
startupThirdStepFormAction,
} from '../../pages/StartupApplication/actions/startupApplicationActions';
const StepThreeForm = ({
startupThirdStepForm,
startupThirdStepFormActionHandler,
}) => (
<>
<Container>
<Row>
<Col>
<Form>
<FormField
value={startupThirdStepForm.firstName}
label="First Name"
controlId="firstName"
onChange={e =>
startupThirdStepFormActionHandler({
firstName: e.target.value,
})
}
/>
<FormField
value={startupThirdStepForm.middleName}
label="Middle Name"
controlId="middleName"
onChange={e =>
startupThirdStepFormActionHandler({
middleName: e.target.value,
})
}
/>
</Form>
</Col>
</Row>
</Container>
</>
);
export default compose(
connect(
store => ({
startupThirdStepForm: store.startupApplicationReducer.startupThirdStepForm,
}),
dispatch => ({
isStepDoneActionHandler: index => {
dispatch(isStepDoneAction(index));
},
startupThirdStepFormActionHandler: form => {
dispatch(startupThirdStepFormAction(form));
},
}),
),
)(StepThreeForm);
Right now as you may see I am trying to send the value to the store like this:
onChange={e =>
startupThirdStepFormActionHandler({
firstName: e.target.value,
})
That is for the firstName field, but when I do the same for the middleName field, it obviously cleans the firstName field.
Here is the reducer:
const initialState = {
startupThirdStepForm: {},
};
const handlers = {
[ActionTypes.STARTUP_THIRD_STEP_FORM](state, action) {
return {
...state,
startupThirdStepForm: action.payload.startupThirdStepForm,
};
},
}
export default createReducer(initialState, handlers);
And the action:
export const startupThirdStepFormAction = startupThirdStepForm => ({
type: ActionTypes.STARTUP_THIRD_STEP_FORM,
payload: { startupThirdStepForm },
});
So what can I do to keep the state of the form fields without cleaning the others?
Try doing the following for your reducer instead:
const handlers = {
[ActionTypes.STARTUP_THIRD_STEP_FORM]: (state, action) {
return {
...state,
startupThirdStepForm: {
// to preserve old state
...state.startupThirdStepForm,
// to update with new data
...action.payload.startupThirdStepForm,
},
};
},
}
A very quick suggestion is to create a copy of the state. For you to easily identify what field to update you can add name and value inside payload.
onChange={e =>
startupThirdStepFormActionHandler({
name: "firstName"
value: e.target.value,
})
const handlers = {
[ActionTypes.STARTUP_THIRD_STEP_FORM](state, action) {
let newStartupThirdStepForm = Object.assign({}, state.startupThirdStepForm);
newStartupThirdStepForm[action.payload.name] = action.payload.value;
return {
...state,
startupThirdStepForm: newStartupThirdStepForm,
};
},
}

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