React array update - reactjs

Working on reactJS project, and I know we don't mutate the state (usually i don't but don't know the exact reason) so I just tried some wrong approach even its working correctly.
import logo from './logo.svg';
import './App.css';
import Test from './test';
import {Home, NotFoundComponent,Contact, Profile} from './test/home'
import {
BrowserRouter as Router,
Route,
Link
} from 'react-router-dom'
import React, {Component} from 'react';
class App extends Component {
constructor(props) {
super(props);
this.state = {
list: [42, 33, 68],
};
}
onUpdateItem = i => {
this.state.list[i]++;
this.setState({
list: this.state.list,
})
// this.setState(state => {
// const list = state.list.map((item, j) => {
// if (j === i) {
// return item + 1;
// } else {
// return item;
// }
// });
// return {
// list,
// };
// });
};
render() {
return (
<div>
<ul>
{this.state.list.map((item, index) => (
<li key={item}>
The person is {item} years old.
<button
type="button"
onClick={() => this.onUpdateItem(index)}
>
Make me one year older
</button>
</li>
))}
</ul>
</div>
);
}
}
export default App;
this.state.list[i]++;
this.setState({
list: this.state.list,
})
what is the problem with when I update the code above instead map method, both give the correct output?
explain to me this behind the scene
code sandbox link

Here is the reason:
When you mutate an array, say list[i]++ the identity of the array is not changed so listBefore ==== listAfter, while you map over the array listBefore !== listAfter, when you invoke setState, ALL the children component will be invoked again by default even if you put nothing inside setState. Because react can not tell if the children update is necessary by default and it is a waste of resources actually.
You can prevent this from happening by telling the Children "Do NOT update" by calling shouldComponentUpdate or simply use PureComponent.
And the way to tell if updates are necessary is to compare using ===
In addition, Even if you do not care about performance. you should never do this. It will cause some debuggable bugs in some cases mainly because the updates in react is NOT synchronized. I never encounter by the way because I do not even dare to try

When you update your state you should take care of immutable.
onUpdateItem = i => {
//this.state.list[i]++;
//this.setState({
// list: this.state.list,
//})
this.setState({
list: this.state.list.map((value,index)=>{
if(i === index) return value+1;
return value;
})
});
}
If you are not maintaining immutable, your app will not work what you expected.
React states are updated follow life-cycle.
if without immutability, it means you are breaking the life-cycle.

Related

Results are not populating after fetching from API using React

I am working with some examples to fetch the data from an API using fetch. But, it is returning nothing in view. What i am doing wrong? Why it is not populating? Here is a link to the code.
Application code here:
import React, { Component } from 'react';
import { render } from 'react-dom';
import Hello from './Hello';
import axios from 'axios';
import './style.css';
const url='https://10degrees.uk/wp-json/wp/v2/posts';
class App extends Component {
constructor() {
super();
this.state = {
name: 'React',
searchInput: 'tirur',
avatarDatas: []
};
this.fetchData = this.fetchData.bind(this);
}
componentDidMount(){
this.fetchData(url);
}
fetchData = (apiToFetch)=>{
fetch(apiToFetch)
.then(result=>result.json())
.then(avatarData=>{
this.setState({
avatarDatas : avatarData
})
})
.catch((error) => console.log(error));
}
render() {
console.log(this.state.avatarDatas)
return (
<div>
<Hello name={this.state.name} />
<p>
{this.state.avatarDatas.map(item=>{
<div>{item}</div>
})}
</p>
</div>
);
}
}
render(<App />, document.getElementById('root'));
Your code is fetching data from the remote URL correctly. However, you're using the map function in your render method wrong. You have:
{this.state.avatarDatas.map(item=>{
<div>{item}</div>
})}
This does indeed map through all the entries in avatarDatas, but the function the callback you've provided the map isn't returning anything. You've written a function block with a JSX statement in it, and no return statement. What you should have written is:
{this.state.avatarDatas.map(item=>{
return (<div>{item}</div>);
})}
or even this, where there isn't an actual function block but just a return value:
{this.state.avatarDatas.map(item=>
<div>{item}</div>
)}
At this point, the avatarDatas still won't be listed, because the JSX in your map callback is trying to have item rendered. item is an object, and React won't know how to convert it to a string. Rather do this:
{this.state.avatarDatas.map(item=>
<div>{item.title.rendered}</div>
)}
(Or any other of the many fields that each avatarData has.)
Finally, React may still issue a warning, saying that each item in a list must have a unique key. Whenever you use map to create a number of elements, React expects you to give a unique identifier to each element, which it will use to identify that element when it needs to update your list during rerendering. That means that you should do this:
{this.state.avatarDatas.map((item, index) =>
<div key={index}>{item.title.rendered}</div>
)}
Now, your map callback assigns an index to each <div> and all is well with the world.

React component abstraction confusion (componentDidMount)

Using React, when we render a list of items and then insert an item to that list, the newly inserted item does not trigger componentDidMount
class Item extends React.Component {
componentDidMount() {
console.log(this.props.i)
}
render() {
return (<div>something</div>)
}
}
class App extends React.Component {
constructor(props) {
super(props)
this.state = {items: []}
this.prependItem = this.prependItem.bind(this)
}
prependItem() {
this.setState(prevState => {
return {items: [0, ...prevState.items]}
})
}
render() {
const {items} = this.state
return (
<div className="App">
<button onClick={() => this.prependItem()}>+</button>
{items.map((item, index) => {
return (<Item key={index} i={index}/>)
})}
</div>
);
}
}
Using the above code, a natural expectation would be '0' whenever the button is clicked, but in truth the output is 0, 1, 2, 3, 4, ...
Essentially, the newly inserted item is treated as if it is exactly the same object as the previous item with that index, which can be unintuitive at times.
I guess a more direct question may be, how would I prepend an item to a list properly so that the componentDidMount for the new item (not the rest) would be triggered?
Let's start with React's approach. It renders Virtual DOM and next compares it with real DOM to realize what element to update, what to delete and what to create.
React does not know your intention and to be stable/predictable it should be really straightforward. So React operates on 2 things: type(it's tag on browser level or component name in JSX) and key prop.
Once both match - component will be updated. If any differs - and no other matches - then it will be delete + create.
There is really complete article on reconciliation in ReactJS docs.
When you use key={index} and add element to the very beginning it ends up with updating all the elements except last one(since there was no component with such a key in DOM yet). And only last element is created while others are updated. Including that you've just inserted.
Also it should explain why use some random data for key is bad idea: you will never predict what component is updated or if random data is unique over the time all the component will be deleted and re-created on each render cycle. It'd be bottleneck for performance.
import React, { useState, useEffect } from "react";
import ReactDOM from "react-dom";
import "./styles.css";
function Item({ i }) {
const inintialI = useRef(i);
console.log(`rendered ${i}; initially it was ${initialI.current}`);
useEffect(() => {
console.log(`created for ${i}`);
return (() => {
console.log(`deleted for ${i}`);
})
}, []);
return <li>{i}</li>;
}
function App() {
const [items, addItems] = useState([]);
return (
<React.Fragment>
<button onClick={() => addItems(items => [items.length, ...items])}>
+
</button>
<ul>
{items.map((i, index) => (
<Item key={index} i={i} />
))}
</ul>
</React.Fragment>
);
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
Here is short snippet(at codesandbox) that output in console when component is created and updated. So important things we are easier to ensure:
React.Component renders each time parent renders
with key={index} we will always re-created last item while we actually insert item into list at index #0.
all other items not only are re-rendered but actually are updated that is not good for perfomance

How can I change what is rendred with a click event in React

I'm new to React, and I'm trying to figure out how to adjust what appears in render based on a click event. My component receives two props "front" and "back". I want the component to display this.props.front upon rendering and change to this.props.back when the div is clicked. I'm having trouble figuring out how to accomplish this in my handleClick function.
Any help would be appreciated!
import React, { Component } from 'react';
class Card extends Component {
handleClick = event => {
}
render() {
return (
<div className="Card" onClick={this.handleClick}>
<h1>{this.props.front}</h1>
</div>
);
}
}
export default Card;
You could add a state to this component which is a boolean that toggles itself
class Card extends Component {
constructor(props) {
this.state = {
showFront: true
}
}...
And than use your handleClick method to switch the state back and forth
handleClick = (e) => {
this.setState({showFront: !this.state.showFront})
}
And in your render function you could put a conditional to show
render() {
return (
<div className="Card" onClick={this.handleClick}>
{
this.state.showFront
? <h1>{this.props.front}</h1>
: <h1>{this.props.back}</h1>
}
</div>
);
}
A comment to this answer was made but was deleted - i think it's a subject worth touching.
the comment said you should use the setState(updater()) and not pass an object.
it's true that when the app becomes more complex, you have several state updates together and data states may not be what you believe they are at that moment, updater function is apropriate (setState is async and could batch calls this is why we have the function that flushes all and helps us maintain state integrity comparing old states with new ones.
but for this answer and the complexity of the question an updater isn't necessary and the code should work just fine (and it gets to the point of using state and toggling which is the right way of doing what was asked).
you can use the updater function any time you please - even for the most simplest state change. And like said here, maybe it is best practice to just always use it :)
for more reference
React.Compoment setState & Updater function
In react you trigger render by changing the state of component. If this component needs to recieve props "front" and "back" then parent component should have saved in state if the state is "front" or "back" and pass down to component callback function to handle change. Something like:
import React, { Component } from 'react';
class ParentCard extends Component {
state = { isFront: true };
handleClick = event => {
this.setState({isFront: !this.state.isFront})
}
render = () => {
const { front } = this.state;
return (
<Card front={front} onClick={this.handleClick} />
);
};
export default ParentCard;
Also you can make Card component "pure" just by creating it as function which returns JSX.
import React from 'react';
const Card = ( { isFront, onClick } ) => {
return (
<div className="Card" onClick={onClick}>
<h1>{isFront ? `text if is front` : `text if it is not`}</h1>
</div>
);
}
}
export default Card;
Hope it helps :)
I'd say in that case you want to use state rather than props here, particularly when the state you want to change is being dictated by the component itself.
class Card extends Component {
state = {
mode: 'front' // default state to front
}
handleClick = () => this.setState({ mode: 'back' })
render() {
return (
<div className="Card" onClick={this.handleClick}>
<h1>{this.props.mode}</h1>
</div>
);
}
}
export default Card;
If this is really a toggle then of course you can use a Boolean flag instead, but you get the idea.
This component itself is currently not set up as a stateless functional component so if thats what you also wanted to achieve. Youll want to make these changes as well as pass props of a boolean in your stateful component.
import React from 'react';
const Card = (props) => {
return (
<div className="Card" onClick={props.handleClick}>
{props.showFront
?
<h1>props.front</h1>
:
<h1>props.back</h1>
}
</div>
);
}
export default Card;
you'll want to utilize the previous state to toggle your state because it could cause issues later down the road with batching, so your stateful component should look something like:
import React, {Component} from "React";
class StatefulCard extends Component {
state = {
showFront: true // default state to front
}
handleClick = () => {
this.setState(prevState => {
return {
showFront: !prevState.showFront
}
}
}
render() {
return (
<div>
<Card
handleClick={this.handleClick}
showFront={this.state.showFront}
/>
</div>
);
}
}
export default Card;

React: setState of array of object changes every item

I'm building a memory game using React. I have an array of cards where each card can be "matching" (the user has just clicked on it) or "matched" (the user has found both cards and they are now completely shown). My issue is that when I try to set the matching state using setState the state changes for every card and not just the clicked one. Here's what I have:
import React from 'react';
import ReactDOM from 'react-dom';
import Card from './card';
import './index.css';
class Game extends React.Component {
constructor() {
super();
this.state = {
cards: Array(4).fill(
{
matching: false,
matched: false,
}
),
currentlyMatching: false,
}
}
handleCardClick(index, type) {
let currentCardsState = this.state.cards.slice();
currentCardsState[index].matching = true;
this.setState({cards: currentCardsState});
}
renderCard(index, type) {
return <Card value={type}
matching={this.state.cards[index].matching}
matched={this.state.cards[index].matched} // this line is the issue
onClick={() => this.handleCardClick(index, type)}
/>;
};
render() {
return <div id="game">
{this.renderCard(0,"red")}
{this.renderCard(1, "green")}
{this.renderCard(2, "red")}
{this.renderCard(3, "green")}
</div>;
};
}
ReactDOM.render(
<Game />,
document.getElementById('root')
);
The problem you have is that you are not creating 4 independent objects for every card. You are creating one object which appears in the array four times. That means that changing any index affects all indices.
That's how Array.fill works.
To create four independent states, you need something like this:
const cards = [];
for(var i = 0; i < 4; i++) {
cards.push({
matched: false,
matching: false
));
}
You can add a shouldComponentUpdate to each Card component to prevent unnecessary re-renders:
shouldComponentUpdate(prevProps, prevState) {
return prevProps !== this.props
}
Or you can specifically target a single prop:
shouldComponentUpdate(prevProps, prevState) {
return prevProps.matched !== this.props.matched
}
And this is exactly what is expected: you are setting in the state a new array of objects and for this reason the render is called again for each of them

Rendering in react with array.map

I have an array of strings which I would like to render as a list, with a colored text. The user can change the color with a button.
For that I have built a component called which receives an array and renders a list with the array's values and a button to change the color:
import React, { Component } from "react";
const renderArray = arr => (arr.map(value => (
<li>
{value}
</li>
)))
class List extends Component {
constructor(props) {
super(props);
this.state = {
color: 'red'
}
}
toggleColor = () => {
if (this.state.color === "red") {
this.setState({color: "blue"});
} else {
this.setState({color: "red"});
}
}
render() {
const style = {
color: this.state.color
};
return (
<div style={style}>
<ul>
{renderArray(this.props.array)}
</ul>
<button onClick={this.toggleColor}>Change color</button>
</div>
);
}
}
export default List;
The List is called with:
<List array={arr} />
And arr:
const arr = ['one', 'two', 'three'];
Fiddle here: Fiddle
But this seems incorrect to me. I rerender the whole array by calling renderArray() each time the color changes. In this case it is not too bad but what if the renderArray() is much more complex?
To my understanding, I need to create a new list only if the array prop changes and this could do in getDerivedStateFromProps (or in componentWillReceiveProps which will be deprecated...):
componentWillReceiveProps(nextProps)
{
const renderedArray = renderArray(nextProps.array);
this.setState({ renderedArray });
}
And then, on render, use this.state.renderedArray to show the list.
But this seems strange, to store a rendered object in the state...
Any suggestions?
Thanks!
1) React uses the concept of virtual DOM to calculate the actual difference in memory and only if it exists, render the difference into DOM
2) You can "help" React by providing a "key", so react will better understand if it's needed to re-render list/item or not
3) Your code componentWillReceiveProps can be considered as a bad practice because you're trying to make a premature optimization. Is repaint slow? Did you measure it?
4) IMHO: renderArray method doesn't make sense and can be inlined into List component
React render the DOM elements efficiently by using a virtual DOM and checks if the update needs to happen or not and hence, it may not be an issue even if you render the list using props. To optimise on it, what you can do is to make use of PureComponent which does a shallow comparison of state and props and doesn't cause a re-render if nothing has changed
import Reactfrom "react";
const renderArray = arr => (arr.map(value => (
<li>
{value}
</li>
)))
class List extends React.PureComponent { // PureComponent
constructor(props) {
super(props);
this.state = {
color: 'red'
}
}
toggleColor = () => {
if (this.state.color === "red") {
this.setState({color: "blue"});
} else {
this.setState({color: "red"});
}
}
render() {
const style = {
color: this.state.color
};
return (
<div style={style}>
<ul>
{renderArray(this.props.array)}
</ul>
<button onClick={this.toggleColor}>Change color</button>
</div>
);
}
}
export default List;

Resources