Unable to update state in react - reactjs

I want to update the the state on the basis of a condition.
Please refer to the code and point out the problem in either the logic or code.
class App extends Component {
constructor(props) {
super(props);
this.state = {
backImgIndex: 1
};
this.changeSlide = this.changeSlide.bind(this);
}
changeSlide() {
if (this.state.backImgIndex === 3) ({
this.setState = {
backImgIndex: 1
});
} else {
this.setState = ({
backImgIndex: this.state.backImgIndex + 1
});
}
}
render() {
const imageURL = '/backImg/b' + this.state.backImgIndex + '.jpg';
return (
<Fragment>
<BackgroundSlider backurl={imageURL} />
<div className="slider">
<div className="left-arrow" style={{}} onClick={this.changeSlide} />
</div>
</Fragment>
);
}
}
export default App;
Is there any other way to update the same.
The end goal is to update the state which trigger the update in the background image upon the press of .left arrow.
Can div be used for such an event ?

You are doing the update in the wrong way. setState is a function that should be called in order for updating the state. In fact, you are overriding such function:
this.setState = {
backImgIndex: 1
};
So, what you need is to call setState and pass in the part of the state you wanna update:
this.setState({ backImgIndex: 1 });
This works:
changeSlide() {
if (this.state.backImgIndex === 3) {
this.setState({
backImgIndex: 1
});
} else {
this.setState({
backImgIndex: this.state.backImgIndex + 1
});
}
}
Final code
class App extends Component {
constructor(props) {
super(props);
this.state = {
backImgIndex: 1
};
this.changeSlide = this.changeSlide.bind(this);
}
changeSlide() {
if (this.state.backImgIndex === 3) {
this.setState({
backImgIndex: 1
});
} else {
this.setState({
backImgIndex: this.state.backImgIndex + 1
});
}
}
render() {
const imageURL = '/backImg/b' + this.state.backImgIndex + '.jpg';
return (
<Fragment>
<BackgroundSlider backurl={imageURL} />
<div className="slider">
<div className="left-arrow" style={{}} onClick={this.changeSlide} />
</div>
</Fragment>
);
}
}

Wrong syntax in setState.
Plus, i would recommend preparing the new state before setting it:
changeSlide() {
let imgI=this.state.backImgIndex;
imgI=(imgI===3)?1:imgI+1;
this.setState({ backImgIndex: imgI});
}
Actually, a better option (because State Updates May Be Asynchronous):
changeSlide() {
this.setState((state,props)=>(state.backImgIndex===3?{ backImgIndex: 1}:{ backImgIndex: state.backImgIndex+1}));
}

First of all, I suggest you to use the ES6 syntax for more clarity :
class App extends Component {
constructor(props) {
super(props);
this.state = {
backImgIndex: 1
};
}
changeSlide = () => {
if (this.state.backImgIndex === 3) {
this.setState = ({
backImgIndex: 1
});
} else {
this.setState = ({
backImgIndex: this.state.backImgIndex + 1
});
}
}
render() {
const imageURL = `/backImg/b'${this.state.backImgIndex}.jpg`;
return (
<Fragment>
<BackgroundSlider backurl={imageURL} />
<div className="slider">
<div className="left-arrow" style={{}} onClick={this.changeSlide} />
</div>
</Fragment>
);
}
}
export default App;
I changed your function to an arrow function, and used the template string for your imageURL.
I think that the problem is in your setState. Can you please log the result of this.state.backImgIndex after updating the state ?
EDIT : As pointed out in the other comments, you're updating your state the wrong way. I corrected it in this example so you can have a clean example

Related

Error: Can't perform a React state update on an unmounted component. This is a no-op, but it indicates a memory leak in your application

I'm getting the above error and I don't know how to handle it.
I got a component. And in the render() i'm looping through an array and placing another component and parsing a value to that component like this:
render() {
let allProducts = this.state.products.map((product, i) => {
return (
<div key={product.article}>
...
<PriceStock value={product.article} />
...
</div>
)
})
}
In the PriceStock component i'm fetching some data with axios like the code below:
export default class PriceStock extends React.Component {
constructor(props) {
super(props);
this.state = ({
buttoprice: ''
})
this.getPriceAndStock = this.getPriceAndStock.bind(this)
}
getPriceAndStock(articleNo) {
return axios.post('LINK_TO_URL', {
articleNo: articleNo
}).then(result => {
return result.data
})
}
async componentDidMount() {
let pricestock;
pricestock = await this.getPriceAndStock(this.props.value)
let bruttoPrice = PRICE_TO_PARSE_TO_THE_STATE;
this.setState({ buttoprice: bruttoPrice })
}
render() {
return (
<div >
{this.state.buttoprice}
</div>
);
}
}
The error seems to happen when I try to setState in the componentDidMount, any suggestions?
this is an error occurs because you are updating state before it gets initialized
perform your loading activities in the constructor it is the right way to do it
getPriceAndStock(orderNumber, articleNo) {
return axios.post('LINK_TO_URL', {
orderNr: orderNumber, vareNr: articleNo
}).then(result => {
return result.data
})
}
constructor() {
this.getPriceAndStock(this.props.value)
.then(pricestock=>{
let bruttoPrice = PRICE_TO_PARSE_TO_THE_STATE;
this.state({ buttoprice: bruttoPrice })
})
.catch(console.log)
}
Found the answear in this question: https://github.com/material-components/material-components-web-react/issues/434
It's remindend me a little bit about the comment with another stackoverflow question.

How to setState in ComponentDidMount

I have something strange with a react app,
I used it in a django project and want to re-use it in a laravel project but it doesn't want to work properly ...
Here is the code of my component :
import React from "react"
import {LeftControl, RightControl, CountControl } from './controls'
import {Slide} from './slide'
import axios from "axios"
export default class Slider extends React.Component {
constructor(props){
super(props);
this.state = {
items : [],
active:0
}
}
componentDidMount() {
axios.get('/coming')
.then((res)=>{
this.setState({ items: res.data, active: 0})
});
setInterval( () => {
this.goToNextSlide()
},5000);
}
goToPrevSlide = () => {
const n = this.state.items.length
if (this.state.active == 0) {
this.setState({active : n-1})
} else {
this.setState({active: this.state.active - 1})
}
}
goToNextSlide = () => {
const n = this.state.items.length
if (this.state.active == n-1){
this.setState({active : 0})
} else {
this.setState({active: this.state.active +1})
}
}
render(){
return(
<div className="slider">
<div className="slider__controls">
<CountControl active={this.state.active} length={this.state.items.length} />
<LeftControl goToPrevSlide={this.goToPrevSlide} />
<RightControl goToNextSlide={this.goToNextSlide}/>
</div>
<div className="slider__items">
{
this.state.items
.map((item, i) => (
<Slide active={this.state.active} index={i} key={i} id={item.id} first_name={item.first_name} last_name={item.last_name} role={item.role} conference_date={item.conference_date} thumbnail={item.thumbnail} />
))
}
</div>
</div>
)
}
}
Uncommenting the setState in componentDidMount raise the following error :
Warning: Can't perform a React state update on an unmounted component. This is a no-op, but it indicates a memory leak in your application. To fix, cancel all subscriptions and asynchronous tasks in the componentWillUnmount method.
in Slider
The component works well on my other project ...
Anyone would have an idea what is the problem ?
Thank you
As riwu commented, you get the warning because the axios call and timer you define in componentDidMount try to set the state of Slider after it has been unmounted. Do the following instead:
export default class Slider extends React.Component {
...
constructor(props) {
super(props);
this._isMounted = false;
this.state = {
items : [],
active:0,
}
}
componentDidMount() {
this._isMounted = true;
axios.get('/coming')
.then((res) => {
if (this._isMounted) {
this.setState({ items: res.data, active: 0})
}
});
this.timer = setInterval(() => {
this.goToNextSlide();
}, 5000);
}
componentWillUnmount() {
this._isMounted = false;
clearInterval(this.timer);
}
...
}

React: Issues with Conditional Rendering

In my React-App, i use the Firebase SDK. If a user wants to reset his password, he will be redirected to a page within my app. If the code is valid, the component <PWResetConfirmForm /> should be rended. If the code is invalid, the component <PWResetOutdatedForm /> is to be rendered.
My Page Component looks like this:
class PWResetConfirmPage extends Component {
constructor(props) {
super(props);
this.state = {};
this.verfiyResetPassword = this.verfiyResetPassword.bind(this);
}
verfiyResetPassword() {
const params = (new URL(`http://dummy.com${this.props.location.search}`)).searchParams;
const code = params.get("oobCode")
auth.doVerfiyPasswordReset(code)
.then(function () {
return (
<div className="HomePage-Main">
<TopBar></TopBar>
<PWResetConfirmForm></PWResetConfirmForm>
</div>
);
})
.catch(function () {
return (
<div className="HomePage-Main">
<TopBar></TopBar>
<PWResetOutdatedForm></PWResetOutdatedForm>
</div>
);
})
}
render() {
return (
<div>
{this.verfiyResetPassword()}
</div>
);
}
}
export default PWResetConfirmPage
When i try to run, i get a blank page and not error.
Where is my issue and how can i fix that?
Thank you very much for your help and for your time
You will not be able to return JSX from within then()/catch() of auth.doVerfiyPasswordReset() like that. You can instead approach this by taking advantage of React.Component lifecycle method componentDidMount and using setState() to manipulate state properties for conditional rendering. I've added state properties to the component, one to track whether loading (API call has completed) and one to track whether the call was a success (then) or failure (catch). These properties are used to conditionally generate JSX content for rendering. This is assuming that verfiyResetPassword() is intended to run when the component is first mounted, instead of every time render() is called:
class App extends Component {
constructor() {
super();
this.state = {
isResetVerified: null,
loading: true
};
}
componentDidMount() {
this.verfiyResetPassword();
}
verfiyResetPassword() {
const params = (new URL(`http://dummy.com${this.props.location.search}`)).searchParams;
const code = params.get("oobCode")
auth.doVerfiyPasswordReset('foobar')
.then(() => {
this.setState({
...this.state,
isResetVerified: true,
loading: false
});
})
.catch(() => {
this.setState({
...this.state,
isResetVerified: false,
loading: false
});
})
}
getContent() {
if (this.state.loading) {
return (
<div>Loading...</div>
);
} else {
if (this.state.isResetVerified) {
return (
<div className="HomePage-Main">
<TopBar></TopBar>
<PWResetConfirmForm></PWResetConfirmForm>
</div>
);
} else {
return (
<div className="HomePage-Main">
<TopBar></TopBar>
<PWResetOutdatedForm></PWResetOutdatedForm>
</div>
);
}
}
}
Here is a basic example in action.
Also, in the constructor this.verfiyResetPassword = this.verfiyResetPassword.bind(this); would only be needed if verfiyResetPassword() is executed by a DOM event such as button onClick or similar.
Hopefully that helps!
I could still fix the error myself:
class PWResetConfirmPage extends Component {
constructor(props) {
super(props);
this.state = {
isValid: false,
code: "",
};
this.verfiyResetPassword = this.verfiyResetPassword.bind(this);
}
componentDidMount() {
const params = (new URL(`http://dummy.com${this.props.location.search}`)).searchParams;
const code = params.get("oobCode")
this.setState({code:code})
auth.doVerfiyPasswordReset(code)
.then(() => {
this.setState({
...this.state,
isValid: true,
});
})
.catch(() => {
this.setState({
...this.state,
isValid: false,
});
})
}
verfiyResetPassword() {
if (this.state.isValid) {
return (
<div>
<TopBar></TopBar>
<PWResetConfirmForm code={this.state.code}></PWResetConfirmForm>
</div>
);
} else {
return (
<div>
<TopBar></TopBar>
<PWResetOutdatedForm></PWResetOutdatedForm>
</div>
);
}
}
render() {
return (
<div className="HomePage-Main">
{this.verfiyResetPassword()}
</div>
);
}
}
export default PWResetConfirmPage

reset component's internal state on route change

I am using react-router-v4 along with react 16.
I want to reset the component's internal state when the user go to a different route or comes back to the same route . Route change should destroy the internal state of a component but it doesn't . And I can't even find a way to notify the component when the route changes as it's a nested component not a direct render of a Route component. Please help.
Here's the code or live codepen example --
const initialProductNames = {
names: [
{ "web applications": 1 },
{ "user interfaces": 0 },
{ "landing pages": 0 },
{ "corporate websites": 0 }
]
};
export class ProductNames extends React.Component {
state = {
...initialProductNames
};
animProductNames = () => {
const newArray = [...this.state.names];
let key = Object.keys(newArray[this.count])[0];
newArray[this.count][key] = 0;
setTimeout(() => {
let count = this.count + 1;
if (this.count + 1 === this.state.names.length) {
this.count = 0;
count = 0;
} else {
this.count++;
}
key = Object.keys(newArray[count])[0];
newArray[count][key] = 1;
this.setState({ names: newArray });
}, 300);
};
count = 0;
componentDidMount() {
this.interval = setInterval(() => {
this.animProductNames();
}, 2000);
}
componentWillUnmount() {
clearInterval(this.interval);
}
componentWillReceiveProps(nextProps) {
console.log(nextProps.match);
if (this.props.match.path !== nextProps.match.path) {
this.setState({ ...initialProductNames });
this.count = 0;
}
}
render() {
return (
<section className="home_products">
<div className="product_names_container">
I design & build <br />
{this.createProductNames()}
</div>
</section>
);
}
createProductNames = () => {
return this.state.names.map(nameObj => {
const [name] = Object.keys(nameObj);
return (
<span
key={name}
style={{ opacity: nameObj[name] }}
className="product_names_anim">
{name}
</span>
);
});
};
}
I got the solution . I didn't quit understood why state as property initializer doesn't reset/intialize on remount. I think it only initialize once, not on every route change] -
I wanted to know how to reset a component's state on route change. But it turns out that you don't have to . Each route renders a specific component . When route changes all other components are unmounted and all the state of those components are also destroyed. But see my code. I was using es7+ property initializer to declare state,count . That's why the state wasn't resetting/initializing again when the component remounted on route change.
To fix it, all i did is i put the state,initialProductNames,count; all of those into constructor. And now it's working perfectly .
Now fresh state on every mount and remount!!
You can use a listener on the Route change as the example on this previous question And there you can add a function to update the main state.
componentDidUpdate(prevProps) {
if (this.props.location !== prevProps.location) {
this.onRouteChanged();
}
}
onRouteChanged() {
console.log("ROUTE CHANGED");
}
The problem is not the state, it's the initialProductNames. Property initializer is a sugar syntax, in fact it is the same as creating a constructor and moving the code into the constructor. The problem is in the initialProductNames, which is created outside the component, that is, only once for the whole system.
For create a new initialProductNames for any instance of ProductNames, do that:
export class ProductNames extends React.Component {
initialProductNames = {
names: [
{ "web applications": 1 },
{ "user interfaces": 0 },
{ "landing pages": 0 },
{ "corporate websites": 0 }
]
};
state = {
...this.initialProductNames
};
// more code
componentWillReceiveProps(nextProps) {
console.log(nextProps.match);
if (this.props.match.path !== nextProps.match.path) {
this.setState({ ...this.initialProductNames });
this.count = 0;
}
}
Here is an example showing that the state is always recreated every remount: https://codesandbox.io/s/o7kpy792pq
class Hash {
constructor() {
console.log("Hash#constructor");
}
}
class Child extends React.Component {
state = {
value: new Hash()
};
render() {
return "Any";
}
}
class App extends React.Component {
state = {
show: true
};
render() {
return (
<div className="App">
<button
type="button"
onClick={() =>
this.setState({
show: !this.state.show
})
}
>
Toggle
</button>
{this.state.show && <Child />}
</div>
);
}
}

Incrementing state value by one using React

In React I am trying to make a button increment a value stored in state.
However using the code below function my value is set undefined or NaN when using handleClick.
class QuestionList extends React.Component {
constructor(props) {
super(props);
this.state = {value: 0};
// This binding is necessary to make `this` work in the callback
this.handleClick = this.handleClick.bind(this);
}
handleClick = (prevState) => {
this.setState({value: prevState.value + 1});
console.log(this.state.value)
}
Can you tell me why this is happening? it should be correct according to the docs here:
https://facebook.github.io/react/docs/state-and-lifecycle.html
Because you are using the handleClick function incorrectly. Here:
handleClick = (prevState) => { .... }
prevState will be an event object passed to handleClick function, you need to use prevState with setState, like this:
handleClick = () => {
this.setState(prevState => {
return {count: prevState.count + 1}
})
}
Another issue is, setState is async so console.log(this.state.value) will not print the updated state value, you need to use callback function with setState.
Check more details about async behaviour of setState and how to check updated value.
Check the working solution:
class App extends React.Component {
constructor(props){
super(props);
this.state={ count: 1}
}
onclick(type){
this.setState(prevState => {
return {count: type == 'add' ? prevState.count + 1: prevState.count - 1}
});
}
render() {
return (
<div>
Count: {this.state.count}
<br/>
<div style={{marginTop: '100px'}}/>
<input type='button' onClick={this.onclick.bind(this, 'add')} value='Inc'/>
<input type='button' onClick={this.onclick.bind(this, 'sub')} value='Dec'/>
</div>
)
}
}
ReactDOM.render(
<App />,
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'></div>
set state is async so you wont see the value update when the console.log happens. You should have the state value printed out on the UI so you can see whats happening. To fix the console log try this.
class QuestionList extends React.Component {
constructor(props) {
super(props);
this.state = {value: 0};
}
handleClick = (prevState) => {
this.setState({value: prevState.value + 1}, () => {
console.log(this.state.value)
});
}
NOTE: when you define an inline lambda (arrow function) for a react class this is bound correctly so you dont need to bind it in the constructor.
also you can change the way you pass the previous number if its just a state increment like this
handleClick = () => {
this.setState({value: this.state.value + 1}, () => {
console.log(this.state.value)
});
}
Hello there, try these codes to increment your value
class Counter extends React.Component{
constructor(props){
super(props);
this.addOne = this.addOne.bind(this);
this.state = {
count : 0
}
}
addOne() { // addOne as HandleClick
this.setState((preState) => {
return {
count : preState.count + 1
};
});
}
render() {
return (
<div>
<h1>Count : {this.state.count}</h1>
<button onClick={this.addOne}>+1</button>
</div>
);
}
}
ReactDOM.render(<Counter />, document.getElementById('YOUR-ID'));
class SkuVariantList extends React.Component {
constructor(props) {
super(props)
this.state = {
clicks: 0
};
this.clickHandler = this.clickHandler.bind(this)
}
componentDidMount() {
this.refs.myComponentDiv.addEventListener('click', this.clickHandler);
}
componentWillUnmount() {
//this.refs.myComponentDiv.removeEventListener('click', this.clickHandler);
}
clickHandler() {
var clk = this.state.clicks
this.setState({
clicks: clk + 1
});
}
render() {
let children = this.props.children;
return (
<div className="my-component" ref="myComponentDiv">
<h2>My Component ({this.state.clicks} clicks})</h2>
<h3>{this.props.headerText}</h3>
{children}
</div>
);
}
}
Try this out
class QuestionList extends React.component {
constructor(props){
super(props)
this.state = {
value : 0
}
}
handleClick(){
this.setState({
value : this.state.value + 1
})
}
render(){
return( <button type="button" onClick={this.handleClick.bind(this)}> {this.state.value} </button> )
}
}
Note that when you set a state, it triggers the render function, which will reflect the current state. Try it out in the browser!
import React from 'react'
class App extends React.Component{
constructor(){
super()
this.state = {
count: 0
}
this.handleClick = this.handleClick.bind(this)
}
handleClick(){
this.setState(prevState => {
return {
count: prevState.count + 1
}
})
}
render(){
return(
<div style = {{display: 'flex', fontSize: 30, flexDirection: 'column', alignItems:'center'}}>
<h1>{this.state.count}</h1>
<button onClick = {this.handleClick}>Change</button>
</div>
)
}
}
export default App
This is the shortest code for that. First, initialize the state, then perform a method to increment.
state = {
counter: 0
}
increaseHandler = () => {
let counter = this.state.counter
counter += 1
this.setState({counter: counter})
}
You can do it this way also where we do both increment and decrement operation with same function making it more modular and redable
class CounterApp extends React.Component{
constructor(){
super();
//here count is initially assigned with 0
this.state ={
count:0
}
}
//when we click Increment or Decrement +1 or -1 is passed to step and the value gets changed it gets updated to the view
increment = (step) =>{
this.setState({
count:this.state.count + step
})
}
render(){
const { count } = this.state;//same as const count = this.state.count;
return(
<div>
<div className="counter-app">
<h2 className="value">{count}</h2>
<button onClick={() => this.increment(+1)}>Increment</button>
<button onClick={() => this.increment(-1)}>Decrement</button>
</div>
</div>
)
}
}

Resources