When is it safe to save state in React? - javascript

Let's say I have a react component that updates state from a form.
class Form extends React.Component {
constructor(props) {
super(props);
this.handleChange = this.handleChange.bind(this);
this.state = {
someCheckboxState: false,
}
}
render() {
return (
<input onChange={this.handleChange} checked={this.state.someCheckboxState} />
);
}
handleChange(event) {
this.setState({
someCheckboxState: event.target.checked,
});
}
}
Now I want to send that state to the server (or somewhere). If I just do this
handleChange(event) {
this.setState({
someCheckboxState: event.target.checked,
});
SendStateToServer(JSON.stringify(this.state)); // BAD! Not yet mutated
}
I could put it in render but then it will get sent to the server on initial render as well as well and it seems silly to send state a function called render.
When is it okay to persist/serialize the state?

The second argument of React's setState is a callback that is fired after the state transition is complete.
this.setState(newState, () => console.log(this.state));
So, in your case:
handleChange(event) {
this.setState({
someCheckboxState: event.target.checked,
}, () => {
SendStateToServer(JSON.stringify(this.state));
});
}

Yes, setState() second parameter, perfect way to do such things.
class Form extends React.Component {
constructor(props) {
super(props);
this.handleChange = this.handleChange.bind(this);
this.state = {
someCheckboxState: false,
}
}
render() {
return ( < input type = "checkbox"
onChange = {
this.handleChange
}
checked = {
this.state.someCheckboxState
}
/>
);
}
handleChange(event) {
this.setState({
someCheckboxState: event.target.checked,
}, () => { // do your work
console.log(JSON.stringify(this.state));
});
}
}
ReactDOM.render( < Form / > , document.getElementById('root'))
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="root"></div>

Related

How to replace componentWillReceiveProps with getDerivedStateFromProps

I read and tried so hard, but still could not find a way to successfully replace componentWillReceiveProps with getDerivedStateFromProps.
Here is my code, componentWillReceiveProps one is working fine.
but the getDerivedStateFromProps which I tried is not working(after componentDidMount() running OK, state.movies eventually become [] empty).
Thanks for your kind help!
class OK_MovieListContainer extends React.Component {
constructor(props) {
super(props);
this.state = {
movies: [],
title:''
};
this.discoverMovieDB = this.discoverMovieDB.bind(this);
this.searchMovieDBAPI = this.searchMovieDBAPI.bind(this);
}
componentDidMount() {
this.discoverMovieDB();
}
UNSAFE_componentWillReceiveProps(nextProps, prevState) {
if (nextProps.match.params.title && prevState.title !== nextProps.match.params.title) {
this.searchMovieDBAPI(nextProps.match.params.title);
this.setState({ title: nextProps.match.params.title });
}
}
async discoverMovieDB() {
.....
}
async searchMovieDBAPI(title) {
const promisedData = await MovieDBAPI.search(title);
if (promisedData) {
this.setState({ movies: promisedData[0] });
}
}
}
render() {
return (
<div className="App">
<MovieList movies={this.state.movies} />
</div>
);
}
}
export default OK_MovieListContainer;
the following is what I tried with getDerivedStateFromProps , but not working(after componentDidMount() running OK, state.movies eventually become [] empty). ....T.T
class MovieListContainer extends React.Component {
constructor(props) {
super(props);
this.state = {
movies: [],
title:''
};
this.discoverMovieDB = this.discoverMovieDB.bind(this);
this.searchMovieDBAPI = this.searchMovieDBAPI.bind(this);
}
static getDerivedStateFromProps(nextProps, prevState) {
if (nextProps.match.params.title && nextProps.match.params.title !== prevState.title) {
return {
//this state changes then componentDidUpdate is called
title: nextProps.match.params.title
};
}
//indicate state update not required
return null;
}
componentDidMount() {
this.discoverMovieDB();
}
componentDidUpdate(nextProps, prevState) {
if (nextProps.match.params.title !== prevState.title) {
this.searchMovieDBAPI(nextProps.match.params.title);
this.setState({ title: nextProps.match.params.title });
}
}
async discoverMovieDB() {
.....
}
async searchMovieDBAPI(title) {
const promisedData = await MovieDBAPI.search(title);
if (promisedData) {
this.setState({ movies: promisedData[0] });
}
}
}
render() {
return (
<div className="App">
<MovieList movies={this.state.movies} />
</div>
);
}
}
export default MovieListContainer;
Thank you so much!

React load value and allow user to alter value within component

I'm new to React (16.4.2), and I'm trying to understand the way it works. I don't want to complicate things with redux; I just want to know about the core react library.
I have an application, and (eventually down the children chain) there is an input, which is a component, RangeInput. It's just a wrapper component for an input.
The problem is two parts
I should be able to change the value within the range (as a user)
if there is data in the local storage, it should load it the first time. This also means that the user should still be able to alter/change the input value.
Right now with this, I see to only be able to do one of the other. I know I'm not understanding something here.
What needs to happen?
Thanks,
Kelly
Here are the classes:
export class RangeInput extends React.Component {
constructor(props) {
super(props);
this.ds = new DataStore();
this.state = {
value: props.value
};
}
static getDerivedStateFromProps(props, state) {
console.log('props', props, 'state', state);
if (props.value !== state.value) {
return {value: props.value};
}
return null;
}
onChange(event) {
const target = event.target;
this.setState({
value: target.value
});
if (this.props.onChange) {
this.props.onChange({value: target.value});
}
}
onKeyUp(event) {
if (event.keyCode !== 9) {
return;
}
const target = event.target;
if (this.props.onChange) {
this.props.onChange({value: target.value});
}
}
render() {
return <div>
<input type="number" value={this.state.value}
onChange={this.onChange.bind(this)}
onKeyUp={this.onKeyUp.bind(this)}/>
</div>;
}
}
const DATA_LOAD = 'load';
export class Application extends React.Component {
constructor() {
super();
this.state = {
value: -1,
load = DATA_LOAD
};
}
componentDidMount() {
if (this.state.load === DATA_LOAD) {
this.state.load = DATA_CLEAN;
const eco = this.ds.getObject('the-app');
if (eco) {
this.setState({value: eco});
}
}
}
render(){
return <RangeInput value={this.state.value} />;
}
}
ReactDOM.render(
<Application/>,
document.getElementById('root')
);
I think this situation can be simplified quite a bit:
import React from 'react';
export const RangeInput = props => (
<input
value={props.value}
onChange={props.setValue} />
)
export class Application extends React.Component {
constructor(props) {
super(props);
this.state = { value: -1, };
}
componentDidMount() {
var val = localStorage.getItem('myVal');
if (val) this.setState({value: val})
}
setValue(e) {
this.setState({value: e.target.value})
localStorage.setItem('myVal', e.target.value);
}
render() {
return <RangeInput
value={this.state.value}
setValue={this.setValue.bind(this)} />;
}
}
Here we have two components: <RangeInput>, a stateless component, and <Application>, the brains behind the operation.
<Application> keeps track of the state, and passes a callback function to RangeInput. Then, on keydown, <RangeInput> passes the event object to that callback function. Application then uses the event object to update the state and the localStorage. On refresh, the last saved value is fetched from localStorage and present in the input (if available).

What is the difference between this.state.function and this.function in ReactJS

I am learning the concept of States in React. I am trying to understand the difference between using this.handleChange, and this.state.handleChange.
I would be grateful if someone could explain to me, the exact difference between the two, and why would this.state.handleChange not work?
class MyApp extends React.Component {
constructor(props) {
super(props);
this.state = {
inputValue: ''
}
this.handleChange = this.handleChange.bind(this);
}
handleChange(event) {
this.setState({
inputValue: event.target.value
});
}
render() {
return (
<div>
< GetInput input={this.state.inputValue} handleChange={this.handleChange} />
{ /* this.handleChanges, and this.state.handleChanges */ }
< RenderInput input={this.state.inputValue} />
</div>
);
}
};
class GetInput extends React.Component {
constructor(props) {
super(props);
}
render() {
return (
<div>
<h3>Get Input:</h3>
<input
value={this.props.input}
onChange={this.props.handleChange}/>
</div>
);
}
};
class RenderInput extends React.Component {
constructor(props) {
super(props);
}
render() {
return (
<div>
<h3>Input Render:</h3>
<p>{this.props.input}</p>
</div>
);
}
};
You can technically call this.state.handleChange so long as you add handleChange in your state.
But it doesn't really make sense since you don't want React to keep a track of it, and it will probably not change (unless you are doing some clever tricks).
constructor(props) {
super(props);
this.state = {
handleChange: e => {
e.preventDefault();
console.log("this.state.handleChange");
}
};
}
One would normally declare a member function in a class.
handleChange = e => {
e.preventDefault();
console.log("this.handleChange");
};
Here is the full working code
(working demo available on CodeSandBox).
import React from "react";
import ReactDOM from "react-dom";
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
handleChange: e => {
e.preventDefault();
console.log("this.state.handleChange");
}
};
}
handleChange = e => {
e.preventDefault();
console.log("this.handleChange");
};
render() {
return (
<div className="App">
<h1>Hello CodeSandbox</h1>
<button onClick={this.handleChange}>this.handleChange</button>
<button onClick={this.state.handleChange}>
this.state.handleChange
</button>
</div>
);
}
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
When you say this.state.something this means something is in the state field of the class. When you say this.someFunction this means something is in the class itself. this here is pointing out our class.
class App extends React.Component {
state = {
something: "Something",
}
someFunction = () => console.log(this.state.something);
render() {
return (
<div>
<button onClick={this.someFunction}>Click</button>
</div>
);
}
}
ReactDOM.render(
<App />,
document.getElementById("app")
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="app"></div>
So, you can't use this.state.handleChange since there is no handleChange in the state. It is a function belongs to the class. This is why we use this.handleChange.
you can store a function in state
constructor(super){
super(props)
this.state = {
generateANumber: () => this.setState({ number: Math.floor(Math.random() * 100) }),
number: 0
}
}
then if you want to call it in your render method
render() {
return <p> {this.state.number} <button onClick={() => this.state.generateANumber()} Press Me To Generate A New Number </button> </p>
}
This is the concept of storing a function in state. This.function just means the function belongs to that class so you can use it using the this keyword.

react child component fails to render when calling parent callback with setstate

I have a parent/child component:
class Input extends React.Component {
constructor(props) {
super(props);
this.state = {value: this.props.value};
this.handleChange = this.handleChange.bind(this);
}
handleChange(e) {
// event is persisted, used to update local state
// and then call parent onChange callback after local state update
e.persist();
this.setState(
{value: e.target.value},
() => this.props.onChange(e)
);
}
render() {
return (<input ... onChange={this.handleChange} />);
}
}
class Page extends React.Component {
constructor(props) {
super(props);
this.state = {modified: false};
this.handleChange = this.handleChange.bind(this);
}
handleChange(e) {
const {name, value} = e.target;
console.log(`${name}: ${value}`);
// the two line execute fine, and everything works ok
// but as soon as I add the bottom on, Input no longer updates!
this.setState({modified: true});
}
render() {
return (
<Input ... onChange={this.handleChange}
style={{backgroundColor: this.state.modified?"red":"blue"}}
/>
);
}
}
So as soon as I do a setState on the parent, the child component no longer renders properly. Why? Does it have something to do with the event object or the fact that the parent event handler is called from the child event handler?
Use this (add spaces before and after ?):
style={{backgroundColor: this.state.modified ? "red" : "blue"}}
instead of this:
style={{backgroundColor: this.state.modified?"red":"blue"}}
After this change your example works fine:
class Input extends React.Component {
constructor(props) {
super(props);
this.state = {value: this.props.value};
this.handleChange = this.handleChange.bind(this);
}
handleChange(e) {
// event is persisted, used to update local state
// and then call parent onChange callback after local state update
e.persist();
this.setState(
{value: e.target.value},
() => this.props.onChange(e)
);
}
render() {
return (<input style={this.props.style} onChange={this.handleChange} />);
}
}
class Page extends React.Component {
constructor(props) {
super(props);
this.state = {modified: false};
this.handleChange = this.handleChange.bind(this);
}
handleChange(e) {
const {name, value} = e.target;
console.log(`${name}: ${value}`);
// the two line execute fine, and everything works ok
// but as soon as I add the bottom on, Input no longer updates!
this.setState({modified: true});
}
render() {
return (
<Input onChange={this.handleChange}
style={{backgroundColor: this.state.modified ? "red":"blue"}}
/>
);
}
}
ReactDOM.render(
<Page />,
document.getElementById('container')
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="container">
<!-- This element's contents will be replaced with your component. -->
</div>

Setting state with onClick in render function

I have a button in render(), and I want it's onClick() to set the state. I know you shouldn't be setting the state in render() because it causes an infinite loop, so how should I go about this?
function initialState(props) {
return {
edit: false,
value: props.value,
};
}
export default class MyButton extends React.Component {
constructor(props) {
super(props);
this.state = initialState(props);
}
onCancel() {
this.setState({ edit: false, value: this.props.value });
}
onClick() {
this.state.edit ? this.onCancel() : this.setState({ edit: true });
}
render() {
return (
<div onClick={this.onClick}>
BUTTON
</div>
);
}
Updated to show what the code I'm trying now and the warning I'm getting thousands of times:
Warning: setState(...): Cannot update during an existing state transition (such as
within `render` or another component's constructor). Render methods should be a
pure function of props and state; constructor side-effects are an anti-pattern, but
can be moved to `componentWillMount`.
warning # warning.js?0260:44
getInternalInstanceReadyForUpdate # ReactUpdateQueue.js?fd2c:51
enqueueSetState # ReactUpdateQueue.js?fd2c:192
ReactComponent.setState # ReactComponent.js?702a:67
onCancel # mybutton.js?9f63:94
onClick # mybutton.js?9f63:98
render # mybutton.js?
...
Not really sure what you want to do since the previous answers didn't solve the issue. So if you provide some more information it might get easier.
But here is my take on it:
getInitialState() {
return (
edit: true
);
}
handleEdit() {
this.setState({edit: true});
}
handelCancel() {
this.setState({edit: false});
}
render() {
var button = <button onClick={this.handelEdit}>Edit</button>
if(this.state.edit === true) {
button = <button onClick={this.handelCancel}>Cancel</button>
}
return (
<div>
{button}
</div>
);
}
To set the state for your use case you need to set the state somewhere but I wouldn't do it this way. I would bind a function to the onClick event.
function initialState(props) {
return {
edit: false,
value: props.value,
};
}
export default class MyButton extends React.Component {
constructor(props) {
super(props);
this.state = initialState(props);
this.handleButtonClick = this.onClick.bind(this);
}
onCancel() {
this.setState({ edit: false, value: this.props.value });
}
onClick() {
this.state.edit ? this.onCancel() : this.setState({ edit: true });
}
render() {
return (
<div onClick={this.handleButtonClick}>
BUTTON
</div>
);
}
Look here for more information
Try to make use of arrow functions to bind onBtnClick and onCancel function to the context and see if it solves your problem.
function initialState(props) {
return {
edit: false,
value: props.value,
};
}
export default class MyButton extends React.Component {
constructor(props) {
super(props);
this.state = initialState(props);
}
onCancel = ()=> {
this.setState({ edit: false, value: this.props.value });
}
onBtnClick = () => {
this.state.edit ? this.onCancel() : this.setState({ edit: true });
}
render() {
return (
<div onClick={this.onBtnClick}>
BUTTON
</div>
);
}

Categories

Resources