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.
Related
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
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.
What is the difference in these two usages of code?
FirstExample does lose focus every input change (It looks like every change causes rerender)..
SecondExample does not lose focus and works as intended.
example
import React, { useState } from "react";
import "./styles.css";
import {
InputBase,
} from "#material-ui/core";
export default function App() {
const [state, setState] = useState({ first: "", second: "" });
const FirstExample = () => {
return (
<InputBase
id={"first"}
placeholder={"first"}
multiline
value={state.first}
onChange={(e) => {
setState((prevState) => {
return {...prevState, first: e.target.value };
})}}
/>
);
};
const SecondExample = () => {
return (
<InputBase
id={"second"}
placeholder={"second"}
multiline
value={state.second}
onChange={(e) => {
setState((prevState) => {
return {...prevState, second: e.target.value };
})}}
/>
);
};
return <div className="App">
<FirstExample/>
{SecondExample()}
</div>;
}
Can somebody explain to me what and why there is a such difference?
there is codesandbox example: example
First, the input losing its value: That's happens because the way you used the setState. Your new state consist of just an object containing the [second] key, you did not preserve the rest of the object. The correct way to do it should be:
<InputBase
id={"second"}
placeholder={"second"}
multiline
value={state.second}
onChange={(e) => {
setState({...state, second: e.target.value });
}}
/>
So you make sure to get the whole object first, and override what you want.
The first input is loosing it's focus because the component is being re-created everytime the state of the App updates. If you insert the input inside the return(), or outside of the App, to then call it, it will work just fine.
I'm trying to handle changes in inputs. I know how to do this in React, but now I'm using also Redux and I have no idea how to change values in inputs. When I try to type letters nothing change. Can you tell me what should I do in handleChange and handleSelect functions? Or maybe there is any other solution? Here's my code
import React, { Component } from 'react';
import { connect } from 'react-redux';
import PropTypes from 'prop-types';
import { updateSensor, getSensorData } from '../actions/sensors';
class EditSensorPage extends Component {
static propTypes = {
sensorName: PropTypes.string.isRequired,
sensorCategory: PropTypes.string.isRequired,
updateSensor: PropTypes.func.isRequired,
getSensorData: PropTypes.func.isRequired
}
handleChange = e => {
// this.setState({ sensorName: e.target.value })
}
handleSelect = e => {
// this.setState({ sensorCategory: e.target.value })
}
handleSubmit = e => {
e.preventDefault();
console.log("name: " + this.state.name, "category: " + this.state.category)
const id = this.props.match.params.id;
const sensorName = this.props.sensorName;
const sensorCategory = this.props.sensorCategory;
// const { sensorName, sensorCategory } = this.state;
const sensor = { sensorName, sensorCategory };
this.props.updateSensor(id, sensor);
}
componentDidMount() {
const id = this.props.match.params.id;
this.props.getSensorData(id)
}
render() {
return (
<div className="col-md-6 m-auto">
<div className="card card-body mt-5">
<h2 className="text-center">Edytuj czujnik</h2>
<form onSubmit={this.handleSubmit}>
<div className="form-group">
<label>Nazwa</label>
<input
type="text"
className="form-control"
name="sensorName"
onChange={this.handleChange}
value={this.props.sensorName}
/>
</div>
<div className="form-group">
<label>Kategoria</label>
<select className="form-control" onChange={this.handleSelect} value={this.props.sensorCategory}>
<option></option>
<option value="temperature">Czujnik temperatury</option>
<option value="humidity">Czujnik wilgotności</option>
</select>
</div>
<div className="form-group">
<button className="btn btn-primary">Potwierdź</button>
</div>
</form>
</div>
</div>
);
}
}
const mapStateToProps = state => ({
sensorName: state.sensors.sensorName,
sensorCategory: state.sensors.sensorCategory,
})
export default connect(mapStateToProps, { updateSensor, getSensorData })(EditSensorPage);
Assuming you set up the redux actions/function correctly, all you need is the dispatch to fire the redux action.
basically you want to do:
const mapDispatchToProps = dispatch => {
return {
updateSensor: data => dispatch(updateSensor(data))
}
}
Then in your handle Select/handle change function:
/* this would varies depends on how updateSensor is defined. Just make sure
the function `updateSensor` is returning an action such as
{ type: 'UPDATE_SENSOR', payload: value }
*/
handleChange = event => {
this.props.updateSensor({sensorName: event.target.value})
}
You might find this question is useful when trying to get the insight into dispatch and mapDispatchToProps:
What is mapDispatchToProps?
I am having a strange issue whilst trying to implement an auto save feature to capture my controlled inputs and save them to sessionStorage().
I have an auto-save function that runs every 30 seconds. I create an object from my input values, and then save those into sessionStorage(). I run a check to see if my created object of input values matches the currently stored object. If the new object is different, I replace the current object in sessionStorage with this new object. This seems pretty straight forward to me.
What is happening, is that I am watching the sessionStorage update one character at a time, much like how the controlled inputs I am using work when setting their values from the onChange() function. Once the object is updated fully with what I typed, it resets back to being blank.
I will show an example of the described issue with the sessionStorage below the code examples.
Here is my AddPost component, that contains the 'add post' form and the auto-save function for now:
import React, { useState, useEffect } from 'react';
//Styles
import {
AddPostContainer,
AddPostInfoInput,
AddPostInfoLabel,
AddPostTextArea,
PostOptionWrapper,
PostOptionGroup,
AddPostBtn
} from './styles';
//Components
import LivePreview from './LivePreview/LivePreview';
import Icon from '../../../Icons/Icon';
const AddPost = props => {
const [htmlString, setHtmlString] = useState('');
const [title, setTitle] = useState('');
const [postBody, setPostBody] = useState('');
const [author, setAuthor] = useState('');
const [tags, setTags] = useState('');
const [featuredImage, setFeaturedImage] = useState('');
const autoSave = async () => {
const autoSaveObject = {
title,
author,
tags,
featuredImage,
postBody
};
try {
await window.sessionStorage.setItem(
'add_post_auto_save',
JSON.stringify(autoSaveObject)
);
} catch (e) {
return;
}
};
setInterval(() => {
const currentSave = window.sessionStorage.getItem('add_post_auto_save');
const autoSaveObject = {
title,
author,
tags,
featuredImage,
postBody
};
if (currentSave === JSON.stringify(autoSaveObject)) {
return;
} else {
autoSave();
}
}, 10000);
return (
<AddPostContainer>
<AddPostInfoLabel htmlFor="title">Title</AddPostInfoLabel>
<AddPostInfoInput
type="text"
value={title}
onChange={e => setTitle(e.target.value)}
placeholder="enter post title"
id="title"
/>
<AddPostTextArea
inputwidth="100%"
height="400px"
value={postBody}
onChange={e => {
setHtmlString(e.target.value);
setPostBody(e.target.value);
}}
/>
<AddPostInfoLabel htmlFor="postbody">Live Preview:</AddPostInfoLabel>
<LivePreview id="postbody" htmlstring={htmlString} />
<PostOptionWrapper>
<PostOptionGroup width="33%">
<AddPostInfoLabel htmlFor="author">Author:</AddPostInfoLabel>
<AddPostInfoInput
type="text"
value={author}
onChange={e => setAuthor(e.target.value)}
placeholder="enter author's name"
id="author"
inputwidth="60%"
/>
</PostOptionGroup>
<PostOptionGroup width="33%">
<AddPostInfoLabel htmlFor="tags">Tags:</AddPostInfoLabel>
<AddPostInfoInput
type="text"
placeholder="enter tags separated by ,"
value={tags}
onChange={e => setTags(e.target.value)}
id="tags"
inputwidth="60%"
/>
</PostOptionGroup>
<PostOptionGroup width="33%">
<AddPostInfoLabel htmlFor="featuredImage">
Feat. Image:
</AddPostInfoLabel>
<AddPostInfoInput
type="text"
placeholder="enter image url"
value={featuredImage}
onChange={e => setFeaturedImage(e.target.value)}
id="featuredImage"
inputwidth="60%"
/>
</PostOptionGroup>
</PostOptionWrapper>
<AddPostBtn type="button">
<Icon icon={['far', 'plus-square']} size="lg" pSize="1em">
<p>Add Post</p>
</Icon>
</AddPostBtn>
</AddPostContainer>
);
};
export default AddPost;
Here is the auto-save function on it's own:
const autoSave = async () => {
const autoSaveObject = {
title,
author,
tags,
featuredImage,
postBody
};
try {
await window.sessionStorage.setItem(
'add_post_auto_save',
JSON.stringify(autoSaveObject)
);
} catch (e) {
return;
}
};
setInterval(() => {
const currentSave = window.sessionStorage.getItem('add_post_auto_save');
const autoSaveObject = {
title,
author,
tags,
featuredImage,
postBody
};
if (currentSave === JSON.stringify(autoSaveObject)) {
return;
} else {
autoSave();
}
}, 30000);
This auto-save function runs once every 30 seconds, and then replaces what is in sessionStorage with what the current values for the input fields are. I use JSON.stringify() on the objects to compare them. (note: the obj from sessionStorage is already stringified.). If that match returns true, nothing is saved as the current input values are also what is saved. Else, it saves the new object into sessionStorage.
My thought was that I needed to make autoSave() async, as updating both session and local storage is asynchronous and doesn't happen immediately (although pretty close to it). That didn't work.
Here is what the sessionStorage object is when it tries to save:
It may be a lower quality, but you can see how it is updating the 'title' property. It behaves like a controlled input, character by character being added to the value.
Can someone point out what is going on here? I am at a loss on this one. Thanks in advance!
The main issue you have is that setInterval is being called on every render and the created intervals are never being cleared.
That means that if you type 10 characters into a text input, then you'll have 10 intervals firing every 10 seconds.
To avoid this using hooks you need to wrap your setInterval call with useEffect and return a deregistration function that will clear the interval when re-rendering (or on unmount). See the Effects with Cleanup documentation.
Here is the minimal updated version using useEffect:
const autoSave = (postData) => {
try {
window.sessionStorage.setItem(
'add_post_auto_save',
JSON.stringify(postData)
);
} catch (e) {
}
};
useEffect(() => {
const intervalId = setInterval(() => {
const autoSaveObject = {
title,
author,
tags,
featuredImage,
postBody
};
const currentSave = window.sessionStorage.getItem('add_post_auto_save');
if (currentSave === JSON.stringify(autoSaveObject)) {
return;
} else {
autoSave(autoSaveObject);
}
}, 10000);
return () => {clearInterval(intervalId)};
});
If you don't want to clear and recreate the interval on every render you can conditionally control when the effect is triggered. This is covered in the useEffect Conditionally firing an effect documentation.
The main thing is that you'll need to pass in every dependency of the useEffect, which in your case is all of your state variables.
That would look like this - and you would need to make sure that you include every state variable that is used inside the useEffect hook. If you forget to list any of the variables then you would be setting stale data.
useEffect(() => {
//... your behaviour here
}, [title, author, tags, featuredImage, postBody]);
Further reading:
Here's a blog post from Dan Abramov that delves more into hooks, using setInterval as an example: https://overreacted.io/making-setinterval-declarative-with-react-hooks/
Also, you don't need to have five or six separate useState calls if it makes sense for the post data to always be "bundled" together.
You can store the post data as an object in useState instead of managing them all separately:
const [postData, setPostData] = useState({
htmlString: '',
title: '',
author: '',
tags: '',
featuredImage: '',
postBody: '',
});
function updateData(value, key) {
setPostData((prevData) => {
return {
...prevData,
[key]: value
};
});
}
const autoSave = (postData) => {
try {
window.sessionStorage.setItem(
'add_post_auto_save',
JSON.stringify(postData)
);
} catch (e) {}
};
useEffect(() => {
const intervalId = setInterval(() => {
const currentSave = window.sessionStorage.getItem('add_post_auto_save');
if (currentSave === JSON.stringify(postData)) {
return;
} else {
autoSave(postData);
}
}, 10000);
return () => {
clearInterval(intervalId)
};
}, [postData]);
// jsx:
<AddPostInfoInput
type="text"
value={postData.title}
onChange={e => updateData(e.target.value, 'title')}
placeholder="enter post title"
id="title"
/>
Good question and nice formatting too. Your problem is happening because you are creating a new interval each time your component updates, that is a lot since you are using controlled inputs. I guess you can get what you want changing your component to a class component and create the setInterval on the componentDidMount method. And don't forget to clean the interval on the component unmounting, here is an example:
import ReactDOM from "react-dom";
import React from "react";
class Todo extends React.Component {
state = {
text1: "",
text2: "",
interval: null
};
componentDidMount() {
this.setState({
interval: setInterval(() => {
const { text1, text2 } = this.state;
const autoSaveObject = {
text1,
text2
};
console.log(JSON.stringify(autoSaveObject));
}, 3000)
});
}
componentWillUnmount() {
clearInterval(this.state.interval);
}
render() {
return (
<div>
<h1>TODO LIST</h1>
<form>
<input
value={this.state.text1}
onChange={e => this.setState({ text1: e.target.value })}
/>
<input
value={this.state.text2}
onChange={e => this.setState({ text2: e.target.value })}
/>
</form>
</div>
);
}
}
ReactDOM.render(<Todo />, document.getElementById("root"));
try to useEffect rather the setInterval
as this shape
this code will make update setItem in session when the state element is change
you can get the getItem when any time you need
const [state,setState]=useState({
htmlString:"",
title:"",
postBody:"",
author:"",
tags:"",
featuredImage:""
})
useEffect(async () => {
const autoSaveObject = { ...state };
try {
await window.sessionStorage.setItem(
'add_post_auto_save',
JSON.stringify(autoSaveObject));
} catch (e) {
return console.log(e)
}
}, [state])