React: setState of array of object changes every item - reactjs

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

Related

React array update

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.

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;

Know what is causing “Warning: setState(…): Cannot update during an existing state transition…”, but not sure where the cause should be moved to

I am running into this error. Numerous SO questions on it, but still having difficulty figuring out where I need to move the call to the action to get around the error.
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.
I have two sibling containers that I am trying to share a Redux store between: update the store in one via an action so that I can use it another one. One container is a search/filter <BasicQuerySearch />box and the other container is a list of results <BasicQueryPositionList />.
The results are narrowed down as the user types. As the field is reduced a <div> below the search box states how many current results there are. That number is determined in the list of results container where it should be passed through the action, update the store, so I can call it in the search box container. The update is happening, but not without this error polluting the browser console.
I know what the cause of the error is: updating the store in render() with:
getCount(n) {
this.props.posCount(n.length);
}
It is just unclear to me where to call it outside of the render() that will permit updating after the container has mounted. Thus, componentDidMount()) wouldn't work because it won't update after the container has mounted and the user has started typing.
Here is the parent container and the two siblings:
// parent container
// ./query.js
import _ from 'lodash';
import React, { Component } from 'react';
import { connect } from 'react-redux';
// imports for the basic query when selected
import BasicQueryPositionList from './bq_position_list';
import BasicQuerySearch from './bq_search';
// imports for the advanced query when selected
// .... these are to come
import { getQuery, clearQuery } from '../../actions/results';
class Query extends Component {
componentWillUnmount() {
this.props.clearQuery();
}
// render the main element of the container
render() {
return (
<div className='panel panel-default'>
<div className='panel-heading'>
<h4><strong>Compensation Data Results Query</strong></h4>
</div>
<div className='panel-body'>
{!this.props.pos_list
?
(
<div>Loading...</div>
)
:
this.props.pos_list.length == 1
?
(
<h5>The query is currently not available for this survey.</h5>
)
:
(
<div>
<BasicQuerySearch />
<hr />
<BasicQueryPositionList />
</div>
)
}
</div>
</div>
);
}
}
// map the state in the Redux store to props
function mapStateToProps(state) {
return {
pos_list: state.results.pos_list,
}
}
export default connect (mapStateToProps, { clearQuery })(Query);
// sibling #1 container
// ./bq_search.js
import _ from 'lodash';
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { searchTerm } from '../../actions/results';
class BasicQuerySearch extends Component {
// Declare the state "term"
constructor(props) {
super(props);
this.state = {
term: '',
};
}
// When someone enters into the search box, it narrows down
// the list of positions; also filters out speciality chars
onInputChange(term) {
var cleanString = term.replace(/([`~!##$%^&*()_|+\-=?;:''",.<>\{\}\[\]\\\/]+)/g, "");
term = cleanString.toLowerCase();
if (!term) {
term = ''
}
this.setState({ term });
this.props.searchTerm(term);
}
// renders the search box that filters down the list of positions
render() {
const { pos_count } = this.props;
console.log(pos_count);
return (
<div>
<input
className='form-control'
placeholder='Enter Keyword or Position Code'
value={this.state.term}
onInput={event => this.onInputChange(event.target.value)}
/>
<h5><b>{pos_count}</b> {pos_count == 1 ? 'Position' : 'Positions'} Found</h5>
</div>
)
}
}
function mapStateToProps(state) {
return {
pos_count: state.results.pos_count,
}
}
export default connect (mapStateToProps, { searchTerm })(BasicQuerySearch);
// sibling #2 container
// ./bq_position_list.js
import _ from 'lodash';
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { clearQuery, getQuery, posCount } from '../../actions/results';
class BasicQueryPostionList extends Component {
// renders the options and also filters down the results
renderPositionsOptions(pos) {
// generate the <option> list
return _.map(pos, p => {
var option = p.FinalCode + ' - ' + p.PosTitle
return (
<option
key={p.FinalCode}
value={p.FinalCode}
>
{option}
</option>
);
})
}
// filter down the list of positions
filterPositions(term, pos) {
const filtered_items = _.filter(pos, p =>
(p.FinalCode + ' - ' + p.PosTitle).toLowerCase().match(term)
)
return filtered_items;
}
// count how many positions are in the list
getCount(n) {
this.props.posCount(n.length);
}
// renders the search box that filters down the list of positions
render() {
const { search_term, pos_list } = this.props;
const filtered_positions = this.filterPositions(search_term, pos_list);
this.getCount(filtered_positions);
return (
<div>
<h4>Position:</h4>
<select className='form-control'>
<option></option>
{this.renderPositionsOptions(filtered_positions)}
</select>
</div>
);
}
}
// map the state in the Redux store to props
function mapStateToProps(state) {
return {
pos_list: state.results.pos_list,
survey_id: state.results.survey_id,
search_term: state.results.search_term
}
}
export default connect (mapStateToProps, { clearQuery, getQuery, posCount })(BasicQueryPostionList);
There is a more fundamental problem here with the structuring of this code. You are in a way duplicating state. The state representing the number of filtered results is really derived state from the pos_list itself, and you shouldn't be storing state derived from other state back into the state tree.
What I would recommend doing, is moving the filterPositions method out from the one component into a shared location. Then import it in both your BasicQuerySearch and BasicQueryPostitionList components and use it in their mapStateToProps function to get the filtered list as well as the count.
This will mean you are no longer duplicating state and also stop the above warning from happening.
The downside of this is that you will be calling the filterPositions function twice instead of once, but the solution for that is to look at making filterPositions a memoized selector (with something like the reselect library).
why not put your code in componentDidUpdate
...
getCount(n) {
this.props.posCount(n.length);
}
// renders the search box that filters down the list of positions
render() {
const { search_term, pos_list } = this.props;
const filtered_positions = this.filterPositions(search_term, pos_list);
return (
<div>
<h4>Position:</h4>
<select className='form-control'>
<option></option>
{this.renderPositionsOptions(filtered_positions)}
</select>
</div>
);
}
componentDidUpdate(prevProps , prevState){
const { search_term, pos_list } = this.props;
const filtered_positions = this.filterPositions(search_term, pos_list);
this.getCount(filtered_positions);
}
...

Meteor - how to give tracker autorun a callback

I have a little piece of code that renders data from the database according to the path name. My only problem is that when I try to retrieve that data, using this.state.note._id it returns an error that says it cannot find _id of undefined. How would I access my object that is put into a state? It only gives the error when I try to access the items inside the object such as _id
import React from "react";
import { Tracker } from "meteor/tracker";
import { Notes } from "../methods/methods";
export default class fullSize extends React.Component{
constructor(props){
super(props);
this.state = {
note: [],
document: (<div></div>)
};
}
componentWillMount() {
this.tracker = Tracker.autorun(() => {
Meteor.subscribe('notes');
let note = Notes.find({_id: this.props.match.params.noteId}).fetch()
this.setState({ note: note[0] });
});
}
renderDocument(){
console.log(this.state.note);
return <p>Hi</p>
}
componentWillUnmount() {
this.tracker.stop();
}
render(){
return <div>{this.renderDocument()}</div>
}
}
I know that the reason it is returning undefined is because (correct me if I am wrong) the page is rendering the function before the the tracker could refresh the data. How would I get like some sort of callback when the tracker receives some data it will call the renderDocument function?
You're initializing your note state as an array but then you're setting it to a scalar later. You're also not checking to see if the subscription is ready which means that you end up trying to get the state when it is still empty. The tracker will run anytime a reactive data source inside it changes. This means you don't need a callback, you just add any code you want to run inside the tracker itself.
You also don't need a state variable for the document contents itself, your render function can just return a <div /> until the subscription becomes ready.
Note also that .findOne() is equivalent to .find().fetch()[0] - it returns a single document.
When you're searching on _id you can shorthand your query to .findOne(id) instead of .findOne({_id: id})
import React from "react";
import { Tracker } from "meteor/tracker";
import { Notes } from "../methods/methods";
export default class fullSize extends React.Component{
constructor(props){
super(props);
this.state = {
note: null
};
}
componentWillMount() {
const sub = Meteor.subscribe('notes');
this.tracker = Tracker.autorun(() => {
if (sub.ready) this.setState({ note: Notes.findOne(this.props.match.params.noteId) });
});
}
renderDocument(){
return this.state.note ? <p>Hi</p> : <div />;
}
componentWillUnmount() {
this.tracker.stop();
}
render(){
return <div>{this.renderDocument()}</div>
}
}

How to get the state of a React app?

So, I've built up this React app with some state. I want to know what that state is, so I can save it to localStorage and let state carry from one session to another. Basically, the app is pretty complex and I don't want people to lose their "place" just because the closed it and opened it again later.
Reading through the React docs though, I don't see anything that references accessing a component's state from outside of React.
Is this possible?
You should never ever try to get a state from a component as a component should always be representing and not generate state on its own. Instead of asking for the component's state, ask for the state itself.
That being said, I definitely see where you're coming from. When talking about React, the term "state" seems to be pretty ambiguous indeed.
I don't see anything that references accessing a component's state
from outside of React.
Once and for all, here's the difference:
Local state, shouldn't persist: this.state, this.setState et al. Local state lives only within the component and will die once the component dies.
Global state, can be persisted: this.props.passedState. Global state is only passed to the component, it can not directly modify it. The view layer will adjust to whatever global state it got passed.
Or in simple:
this.state means local state, won't get persisted.
this.props.state means passed state, could be persisted, we just don't know and we don't care.
Example
The following example uses stuff of Babel's stage-1 Preset
React is all about giving a display representation of a data structure. Let's assume we have the following object to visibly represent:
let fixtures = {
people: [
{
name: "Lukas"
},
{
name: "fnsjdnfksjdb"
}
]
}
We have an App component in place, that renders Person components for every entry in the people array.
import React, { Component, PropTypes } from 'react';
import ReactDOM from 'react-dom';
class Person extends Component {
static propTypes = {
name: PropTypes.string.isRequired
}
render() {
return (
<li>
<input type="text" value={this.props.name} />
</li>
);
}
}
class App extends Component {
static propTypes = {
people: PropTypes.array.isRequired
}
render() {
let people = this.people.map(person => {
<Person name={person.name} />
});
return (
<ul>
{people}
</ul>
);
}
}
ReactDOM.render(
<App people={fixtures} />,
document.getElementById('yourid')
);
Now, we will implement focus functionality. There are 2 options:
We don't care about which person was focused the last time the user used the app, so we use local state.
We do care about which person was focused the last time the user used the app, so we use global state.
Option 1
The task is simple. Just adjust the People component so that it knows (and rerenders) once the focus changed. The original data structure won't be changed and the information whether a component is focused or not is
merely client side information that will be lost once the client is closed/reset/whatever.
State is local: We use component.setState to dispatch changes to local state
class Person extends Component {
static propTypes = {
name: PropTypes.string.isRequired
}
constructor(...args) {
super(...args);
this.state = {
isFocused: false
}
this.onFocus = this.onFocus.bind(this);
this.onBlur = this.onBlur.bind(this);
}
static propTypes = {
name: PropTypes.string.isRequired
}
onFocus() {
this.setState({
isFocused: true;
});
}
onBlur() {
this.setState({
isFocused: false;
});
}
render() {
let borderColor = this.state.isFocused ? '#ff0' : '#000';
style = {
border: `1px solid ${borderColor}`
}
return (
<li>
<input
style={style}
type="text"
value={this.props.name}
onFocus={this.onFocus}
onBlur={this.onBlur}
/>
</li>
);
}
}
Option 2
We actually want to persist the focused element to whatever store (eg. backend) we have, because we care about the last state. State is global: React components receive only props as "state", even granular information as whether an element is focused. Persist and feed global state to the app and it will behave accordingly.
function setFocus(index) {
fixtures.people[index].isFocused = true;
render();
}
function clearFocus(index) {
fixtures.people[index].isFocused = false;
render();
}
function render() {
ReactDOM.render(
<App people={fixtures} />,
document.getElementById('yourid')
);
}
class Person extends Component {
static propTypes = {
name: PropTypes.string.isRequired,
isFocused: PropTypes.bool,
index: PropTypes.number.isRequired
}
static defaultProps = {
isFocused: false
}
constructor(...args) {
super(...args);
this.onFocus = this.onFocus.bind(this);
this.onBlur = this.onBlur.bind(this);
}
static propTypes = {
name: PropTypes.string.isRequired
}
onFocus() {
setFocus(this.props.index);
}
onBlur() {
clearFocus(this.props.index);
}
render() {
let borderColor = this.props.isFocused ? '#ff0' : '#000';
style = {
border: `1px solid ${borderColor}`
}
return (
<li>
<input
style={style}
type="text"
value={this.props.name}
onFocus={this.onFocus}
onBlur={this.onBlur}
/>
</li>
);
}
}
class App extends Component {
static propTypes = {
people: PropTypes.array.isRequired
}
render() {
let people = this.people.map((person, index) => {
<Person name={person.name} index={index} isFocused={person.isFocused} />
});
return (
<ul>
{people}
</ul>
);
}
}
render();
I think the solution to your problem really lies in how your application is modelled.
Ideally what you would need (depending on complexity) would be a single (flux/redux) store upon which you could subscribe to changes, if it diffs then save it to localStorage.
You would then need to determine a way to bootstrap this data into your single store.
Their is no API per se (that I know of) to do specifically what you want.
Don't try to get the state from outside of React -- pass the state from React to wherever it needs to be.
In your case, I would do this componentDidUpdate.
var SampleRootComponent = React.createClass({
componentDidUpdate: function() {
localStorage.setItem(JSON.stringify(this.state))
}
})
You're right in that there is no way to get the state from outside; you have to set it from React. But there is no reason to view this as a bad thing -- just call an external function from inside of React if you need to pass the info to something else, as shown. No functionality is lost.

Resources