Rendering arrays using .map that contains state variables - not updating - javascript

I have an array that I am rendering on the render() function. Each element in the array is a HTML element that has state variables that I need to display, the HTML are displaying correctly, but the internal state variables do not update even when the rendering is happening
state = {
array: [],
id: 2
}
updateState() {
this.setState({id: 4})
}
componentDidMount(){
array = [<div> {this.state.id} </div>, <div> {this.state.id} </div>]
}
render() {
{this.state.array.map(el => return el)}
//assume something happens here that triggers updateState() multiple times: buttons presses, etc
}
I never see 4, it re renders but keeps the old value 2

You are creating the array in the componentDidMount function which is only being called once when the component first renders.
You should do something like this
//create function
createArray = () => [<div> {this.state.id} </div>, <div> {this.state.id} </div>]
then use it in your code like this
{this.createArray().map(el => el)}
Hope this helps.

You need to save the data and render again:
state = {
id: 2
}
updateState() {
this.setState({id: 4})
}
componentDidMount(){
this.getElements(this.state.id)
}
getElements = (id) => {
return [<div> {id} </div>, <div> {id} </div>]
}
render() {
{this.getElements(this.state.id).map(el => el)}
//assume something happens here that triggers updateState() multiple times: buttons presses, etc
}

Related

multiple react state updates renders only the last update

I made a little function to add custom alerts to the state array, and the view renders them. The problem is, if i call the function twice in a row, the first alert is rendered only for a moment, and then its replaced with the second alert. When i call the method with a mouse click, the function works correctly.
I have tried to apply some waiting before pushing to the array list, but no luck with that.
const Element = () => {
const [alerts, setAlerts] = React.useState([])
const addAlert = (data) => {
setAlerts([...alerts, <CustomAlert key={alerts.length} message={data.message} color={data.color} />])
}
return (
<div>
<button onClick={() => {
// this renders only the last state update.
addAlert({message: "test", color: "error"});
addAlert({message: "2", color: "error"})
}
}>
add alert button
</button>
<div>
{alerts}
</div>
</div>
);
}
React updates the state asynchronously. This means when you are updating the state 2 times in a row, accessing the value of alerts directly might not have the latest inserted item. You should use a function instead when calling setAlerts:
const [alerts, setAlerts] = React.useState([]);
const addAlert = (data) => {
setAlerts((prevAlerts) => {
const newAlerts = [...prevAlerts];
newAlerts.push(
<CustomAlert
key={alerts.length}
message={data.message}
color={data.color}
/>
);
return newAlerts;
});
};
return (
<div>
<button
onClick={() => {
// this renders only the last state update.
addAlert({ message: "test", color: "error" });
addAlert({ message: "2", color: "error" });
}}
>
add alert button
</button>
</div>
);
alerts in your code refers to the value of the current render in both case so your addAlert won't work. To fix this, you can use the setter version with a function:
setAlerts(currentAlerts => [...currentAlters, <CustomAlert key={alerts.length} message={data.message} color={data.color} />])

Rerender component from different component

In component A I have a function that adds a object to my localforage:
localforage.setItem('trackedMovies', value)
In component B I map through the trackedMovies array that I grab from localforage:
getData = async () => {
const trackedMovies = await localforage.getItem<[]>('trackedMovies');
this.setState({
data: trackedMovies
})
}
componentDidMount = () => {
this.getData()
}
render() {
return (
<React.Fragment>
<h1>Dashboard</h1>
{ this.state.data.map(movie => {
return (
<span key={movie.id}>{movie.original_title}</span>
)
})}
</React.Fragment>
)
}
Initially this works fine. But if I add new objects in the trackedMovies array component B does not update so I have to manually refresh the page to see the newly added object.
How do I rerender component B to show the results in the updated trackedMovies array?
Try maintaining the elements of the localStorage in a state. So whenever you add an item in component A, add it to the state as well. Then you can pass it to B.

React - change this.state onClick rendered with array.map()

I'm new to React and JavaScript.
I have a Menu component which renders an animation onClick and then redirects the app to another route, /coffee.
I would like to pass the value which was clicked (selected) to function this.gotoCoffee and update this.state.select, but I don't know how, since I am mapping all items in this.state.coffees in the same onClick event.
How do I do this and update this.state.select to the clicked value?
My code:
class Menus extends Component{
constructor (props) {
super(props);
this.state = {
coffees:[],
select: '',
isLoading: false,
redirect: false
};
};
gotoCoffee = () => {
this.setState({isLoading:true})
setTimeout(()=>{
this.setState({isLoading:false,redirect:true})
},5000)
}
renderCoffee = () => {
if (this.state.redirect) {
return (<Redirect to={`/coffee/${this.state.select}`} />)
}
}
render(){
const data = this.state.coffees;
return (
<div>
<h1 className="title is-1"><font color="#C86428">Menu</font></h1>
<hr/><br/>
{data.map(c =>
<span key={c}>
<div>
{this.state.isLoading && <Brewing />}
{this.renderCoffee()}
<div onClick={() => this.gotoCoffee()}
<strong><font color="#C86428">{c}</font></strong></div>
</div>
</span>)
}
</div>
);
}
}
export default withRouter(Menus);
I have tried passing the value like so:
gotoCoffee = (e) => {
this.setState({isLoading:true,select:e})
setTimeout(()=>{
this.setState({isLoading:false,redirect:true})
},5000)
console.log(this.state.select)
}
an like so:
<div onClick={(c) => this.gotoCoffee(c)}
or so:
<div onClick={(event => this.gotoCoffee(event.target.value}
but console.log(this.state.select) shows me 'undefined' for both tries.
It appears that I'm passing the Class with 'c'.
browser shows me precisely that on the uri at redirect:
http://localhost/coffee/[object%20Object]
Now if I pass mapped 'c' to {this.renderCoffee(c)}, which not an onClick event, I manage to pass the array items.
But I need to pass not the object, but the clicked value 'c' to this.gotoCoffee(c), and THEN update this.state.select.
How do I fix this?
You can pass index of element to gotoCoffee with closure in render. Then in gotoCoffee, just access that element as this.state.coffees[index].
gotoCoffee = (index) => {
this.setState({isLoading:true, select: this.state.coffees[index]})
setTimeout(()=>{
this.setState({isLoading:false,redirect:true})
},5000)
}
render(){
const data = this.state.coffees;
return (
<div>
<h1 className="title is-1"><font color="#C86428">Menu</font></h1>
<hr/><br/>
{data.map((c, index) =>
<span key={c}>
<div>
{this.state.isLoading && <Brewing />}
{this.renderCoffee()}
<div onClick={() => this.gotoCoffee(index)}
<strong><font color="#C86428">{c}</font></strong></div>
</div>
</span>)
}
</div>
);
}
}
so based off your code you could do it a couple of ways.
onClick=(event) => this.gotoCoffee(event.target.value)
This looks like the approach you want.
onClick=() => this.gotoCoffee(c)
c would be related to your item in the array.
All the answers look alright and working for you and it's obvious you made a mistake by not passing the correct value in click handler. But since you're new in this era I thought it's better to change your implementation this way:
It's not necessary use constructor at all and you can declare a state property with initial values:
class Menus extends Component{
state= {
/* state properties */
};
}
When you declare functions in render method it always creates a new one each rendering which has some cost and is not optimized. It's better if you use currying:
handleClick = selected => () => { /* handle click */ }
render () {
// ...
coffees.map( coffee =>
// ...
<div onClick={ this.handleClick(coffee) }>
// ...
}
You can redirect with history.replace since you wrapped your component with withRouterand that's helpful here cause you redirecting on click and get rid of renderCoffee method:
handleClick = selected => () =>
this.setState(
{ isLoading: true},
() => setTimeout(
() => {
const { history } = this.props;
this.setState({ isLoading: false });
history.replace(`/${coffee}`);
}
, 5000)
);
Since Redirect replaces route and I think you want normal page change not replacing I suggest using history.push instead.
You've actually almost got it in your question. I'm betting the reason your state is undefined is due to the short lived nature of event. setState is an asynchronous action and does not always occur immediately. By passing the event off directly and allowing the function to proceed as normal, the event is released before state can be set. My advice would be to update your gotoCoffee function to this:
gotoCoffee = (e) => {
const selectedCoffee = e.target.value
this.setState({isLoading:true,select:selectedCoffee},() =>
{console.log(this.state.select})
setTimeout(()=>{
this.setState({isLoading:false,redirect:true})
},5000)
}
Note that I moved your console.log line to a callback function within setState so that it's not triggered until AFTER state has updated. Any time you are using a class component and need to do something immediately after updating state, use the callback function.

setState update array value using index in react

I want to update array value using index, is below code ok?
handleChange = index => e => {
const { rocket } = this.state // ['tesla', 'apple', 'google']
rocket[index] = e.target.value
this.setState({ rocket })
}
my jsx
<div>{rocket.map((val,i) => <input type="text" onChange={handleChange(i)} value={val} />)}</div>
I know it worked, but just to be sure it's ok to mutate the state like that.
It's not okay to mutate state this way.
The following line mutates the array in the current state in a way that can lead to bugs in your program particularly with components down the Component tree using that state.
This is because the state is still the same array.
rocket[index] = e.target.value
//console.log(this.state.rocket) and you see that state is updated in place
Always treat state as immutable
You can remedy this by writing.
const newRocket = [
...rocket.slice(0, index),
e.target.value,
...rocket.slice(index + 1)
]
This way a new array is created and components in the Component tree can be updated when React does a reconciliation.
Note that
The only way to mutate state should be through calls to Component.setState.
Now that you have a new array, you can update the component state like so:
this.setState({ rocket: newRocket })
Instead of changing existing value, you could use Array.prototype.splice().
The splice() method changes the contents of an array by removing existing elements and/or adding new elements.
var arr= ['A','B','E','D'];
arr.splice(2,1,'C')
console.log(arr)//The result will be ['A','B','C','D'];
.as-console-wrapper {max-height: 100% !important;top: 0;}
Stackblitz demo
CODE SNIPPET
class App extends Component {
constructor() {
super();
this.state = {
name: 'Demo using Array.prototype.slice()',
rocket: ['tesla', 'apple', 'google'],
link: 'https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/slice'
};
}
handleChange(index, e) {
const { rocket } = this.state;
rocket.splice(index, 1, e.target.value)
this.setState({ rocket: [...rocket] }, () => {
//call back function of set state you could check here updated state
console.log(this.state.rocket)
});
}
render() {
return (
<div>
<b><a target="_blank" href={this.state.link}>{this.state.name}</a></b>
{
this.state.rocket.map((val, i) =>
<p key={i}>
<input type="text" onChange={(e) => { this.handleChange(i, e) }} value={val} />
</p>)
}</div>
);
}
}
render(<App />, document.getElementById('root'));

How to automatically run a function after a page loads in react?

In my componentDidMount(), I am calling an actionCreator in my redux file to do an API call to get a list of items. This list of items is then added into the redux store which I can access from my component via mapStateToProps.
const mapStateToProps = state => {
return {
list: state.list
};
};
So in my render(), I have:
render() {
const { list } = this.props;
}
Now, when the page loads, I need to run a function that needs to map over this list.
Let's say I have this method:
someFunction(list) {
// A function that makes use of list
}
But where do I call it? I must call it when the list is already available to me as my function will give me an error the list is undefined (if it's not yet available).
I also cannot invoke it in render (before the return statement) as it gives me an error that render() must be pure.
Is there another lifecycle method that I can use?
Just do this, and in redux store please make sure that initial state of list should be []
const mapStateToProps = state => {
return {
list: someFunction(state.list)
};
};
These are two ways you can play with received props from Redux
Do it in render
render() {
const { list } = this.props;
const items = list && list.map((item, index) => {
return <li key={item.id}>{item.value}</li>
});
return(
<div>
{items}
</div>
);
}
Or Do it in componentWillReceiveProps method if you are not using react 16.3 or greater
this.state = {
items: []
}
componentWillReceiveProps(nextProps){
if(nextProps.list != this.props.list){
const items = nextProps.list && nextProps.list.map((item, index) => {
return <li key={item.id}>{item.value}</li>
});
this.setState({items: items});
}
}
render() {
const {items} = this.state;
return(
<div>
{items}
</div>
);
}
You can also do it in componentDidMount if your Api call is placed in componentWillMount or receiving props from parent.

Categories

Resources