I am running into a strange issue where a component is updating a variable in the parent component that was passed to it as a prop.
The structure looks vaguely like so:
class ParentComponent extends Component {
const toPassToChild = [{ name: 'name', val: 0 }];
...
render() {
return(<ChildComponent p={toPassToChild} />);
}
}
class ChildComponent extends Component {
constructor(props) {
this.state = {
...
arrayOfObjects: this.props.p
}
}
modifyState() {
let aCopy = [...this.state.arrayOfObjects];
let member = aCopy.find(element => {
return element.name === 'name';
});
member.name = 'foo';
this.setState({
arrayOfObjects: aCopy
)};
}
}
When modifyState() is called, the value of toPassToChild is changed in ParentComponent to [{name: 'foo', val: 0}]. Is there any way to stop this? The issue does not occur with other props that are used as initial state, only the prop which is an array of objects.
When you modify member.name in modifyState, you're mutating the original object, since [...this.state.arrayOfObjects] still contains the references to the original objects.
Here's how you can update the array without mutating the original:
modifyState() {
const arrayOfObjects = this.state.arrayOfObjects.map(obj => {
if (obj.name === 'name') {
return { ...obj, name: 'foo' };
}
return obj;
});
this.setState({ arrayOfObjects });
}
I have change the way you copy in the child component. [...] only makes copy of first level elements.
class ParentComponent extends Component {
const toPassToChild = [{ name: 'name', val: 0 }];
...
render() {
return(<ChildComponent p={toPassToChild} />);
}
}
class ChildComponent extends Component {
constructor(props) {
this.state = {
...
arrayOfObjects: this.props.p
}
}
modifyState() {
let aCopy = JSON.parse(JSON.stringify(this.state.arrayOfObjects));
let member = aCopy.find(element => {
return element.name === 'name';
});
member.name = 'foo';
this.setState({
arrayOfObjects: aCopy
)};
}
}
Check the link for working version https://stackblitz.com/edit/react-kxtvoi?file=index.js
Related
This question already has answers here:
When should I use a return statement in ES6 arrow functions
(6 answers)
Closed 4 years ago.
React doesn't render the element when I use this form of code:
class ListaPratitelja extends React.Component {
constructor(props) {
super(props);
}
render() {
const items = this.props.pratitelji;
const listItems = items.map((name, index) => {
e(Pratitelj, { key: index, name: name });
});
return listItems;
}
}
I've used a debugger to see what's going on, and the e function (which is just React.createElement) returns undefined.
But it works just fine when I use it like this:
class ListaPratitelja extends React.Component {
constructor(props) {
super(props);
}
render() {
const items = this.props.pratitelji;
const listItems = items.map((name, index) => e(Pratitelj, { key: index, name: name }));
return listItems;
}
}
The question is why? Is this a bug or I did something wrong in the first example. Also this is the full code:
'use strict';
const e = React.createElement;
class ListaPratitelja extends React.Component {
constructor(props) {
super(props);
}
render() {
const items = this.props.pratitelji;
const listItems = items.map((name, index) => e(Pratitelj, { key: index, name: name }));
return listItems;
}
}
class Pratitelj extends React.Component {
constructor(props) {
super(props);
this.state = { deleted: false };
this.handleDeleteChange = this.handleDeleteChange.bind(this);
}
handleDeleteChange(deletedState) {
this.setState({ deleted: deletedState });
}
render() {
console.log("rendered");
if (this.state.deleted) {
return '';
}
return e(
'div',
null,
e(PratiteljIme, { name: this.props.name }),
e(PratiteljDeleteButton, { handleDeleteChange: this.handleDeleteChange})
);
}
}
class PratiteljIme extends React.Component {
constructor(props) {
super(props);
}
render() {
return e(
"span",
null,
this.props.name)
}
}
class PratiteljDeleteButton extends React.Component {
constructor(props) {
super(props);
}
render() {
return e(
"button",
{ type: "button", "onClick": this.props.handleDeleteChange},
"X"
)
}
}
function loadListaPratitelja(pratitelji) {
const lista = pratitelji.split(",");
const domContainer = document.querySelector('#listaPratitelja');
ReactDOM.render(e(ListaPratitelja, {pratitelji: lista}), domContainer);
}
The input variable "pratitelji" is just a string with a couple of CSV (for ex. p1,p2,p3,p4).
The versions of react I use are these:
https://unpkg.com/react#16/umd/react.development.js
https://unpkg.com/react-dom#16/umd/react-dom.development.js
The browser I tested it on is the latest version of firefox for development.
with using { and } you are creating a function that doesn't return anything, unless you return something. Hence .map will return an array filled with undefined.
Update your code to this:
const listItems = items.map((name, index) => {
return e(Pratitelj, { key: index, name: name });
});
If you open up a function after the arrow, you must state what you're returning.
I want to keep some functions outside of my component for easier testing. However, I cannot change state with these functions because they cannot reference the component's state directly.
So I currently have the hacky solution where I set the function to a variable then call this.setState. Is there a better convention/more efficient way to do this?
Example function code in Tester.js:
const tester = () => {
return 'new data';
}
export default tester;
Example component code in App.js (without imports):
class App extends Component {
constructor() {
super();
this.state = {
data: ''
}
}
componentDidMount(){
let newData = tester();
this.setState({ data: newData })
}
render() {
return(
<div>{this.state.data}</div>
)
}
}
You could bind your tester function like this (this approach doesn't work with arrow functions):
function tester() {
this.setState({ data: 'new Data' });
}
class App extends Component {
constructor() {
super();
this.state = {
data: '',
};
this.tester = tester.bind(this);
}
componentDidMount() {
this.tester();
}
render() {
return (
<div>{this.state.data}</div>
);
}
}
But I would prefer a cleaner approach, where you don't need your function to access this (also works with arrow functions):
function tester(prevState, props) {
return {
...prevState,
data: 'new Data',
};
}
class App extends Component {
constructor() {
super();
this.state = {
data: '',
};
}
componentDidMount() {
this.setState(tester);
}
render() {
return (
<div>{this.state.data}</div>
);
}
}
You can pass a function to setState() that will return a new object representing the new state of your component. So you could do this:
const tester = (previousState, props) => {
return {
...previousState,
data: 'new data',
};
}
class App extends Component {
constructor() {
super();
this.state = {
data: ''
}
}
componentDidMount(){
this.setState(tester)
}
render() {
return(
<div>{this.state.data}</div>
)
}
}
The reason being that you now have access to your component's previous state and props in your tester function.
If you just need access to unchanging static placeholder values inside of your app, for example Lorem Ipsum or something else, then just export your data as a JSON object and use it like that:
// testData.js
export const testData = {
foo: "bar",
baz: 7,
};
...
// In your app.jsx file
import testData from "./testData.js";
const qux = testData.foo; // "bar"
etc.
I have an object defined like this:
import React, { Component } from 'react';
import './App.css';
class App extends Component {
constructor() {
super();
this.state = {
lists: ["Dogs", "Cats"],
items: {Dogs:[], Cats:[]}
};
}
handleAddItem(item) {
console.log(item);
}
I have the variable
console.log(item);// output {Dogs:[{name: "lofi"}]}
I don't know how to verify which property is in the item (Dogs or Cats) so that I can update the object items{} to make it becоme in my example like this:
items{Dogs:[{name: "lofi"}], Cats:[]}
You can use Object.keys method.
items[Object.keys(item)[0]] = item[Object.keys(item)[0]]
Working solution
let state = {
lists: ["Dogs", "Cats"],
items: {Dogs:[], Cats:[]}
};
let item = { Dogs:[{name: "lofi"}] };
state.items[Object.keys(item)[0]] = item[Object.keys(item)[0]]
console.log(state.items);
You can use Object.assign function on the state.items, which will add/update the given object with the properties given at the second parameter.
let state = {
lists: ["Dogs", "Cats"],
items: { Dogs:[], Cats:[] }
};
let item = { Dogs: [ {name: "lofi"} ] };
Object.assign(state.items, item);
console.log(state);
I have an object defined like this:
import React, { Component } from 'react';
import './App.css';
class App extends Component {
constructor() {
super();
this.state = {
lists: ["Dogs", "Cats"],
items: {Dogs:[], Cats:[]}
};
}
handleAddItem(item) {
console.log(this.props.idName);
console.log(item);
}
I have the variable
console.log(this.props.idName)// output: Dogs
console.log(item);// output {name: "lofi"}
I don't know how to update the object items{} to make it becоme like this:
items{Dogs:[{name: "lofi"}], Cats:[]}
To update a nested Array substate, you can use the spread operator to append elements
handleAddItem = item => {
this.setState((prevState, props) => ({
items: {
...prevState.items,
[props.idName]: [
...prevState.items[props.idName],
item
]
}
}))
}
What about something like that
handleAddItem(item) {
this.setState((s, p) => ({
items: {
...s.items,
[p.idName]: s.items[p.idName].concat([item])
}
}))
}
Few comments:
setState can take function, as parameter you got old state value
[idName]: value dynamically updates prop idName
You can do something like
import React, { Component } from 'react';
import './App.css';
class App extends Component {
constructor() {
super();
this.state = {
lists: ["Dogs", "Cats"],
items: {Dogs:[], Cats:[]}
};
}
handleAddItem(item) {
console.log(this.props.idName);
console.log(item);
let oldItems = this.state.items;
oldItems[this.props.idName].push(item);
// Update new state for items
this.setState({
items: {...this.state.items}
})
}
import React, { Component } from 'react'
import { connect } from 'react-redux';
export default function(strategies = []) {
class Authentication extends Component {
constructor() {
super()
this.state = {
allGreen: true
}
}
componentWillMount() {
const { history } = this.props
strategies.map(strategy => {
if (!this.props.auth[strategy]) {
this.setState({ allGreen: false })
history.replace('/')
}
})
}
componentWillUpdate(nextProps, nextState) {
const { history } = this.props
strategies.map(strategy => {
if (!nextProps.auth[strategy]) {
this.setState({ allGreen: false })
history.replace('/')
}
})
}
render() {
if (!this.state.allGreen) return (<div></div>)
return this.props.children
}
}
function mapStateToProps(state) {
return {
auth: state.auth
}
}
return connect(mapStateToProps)(Authentication)
}
I'm using 'destructor object' usually in ES6
For example, const { history } = this.props like.
However, I want to know whether there is an efficient way to have just one object destruction, and use it in all of component's method.(componentWillMount, componentWillUpdate ...)
Above picture, I used object destruction twice in componentWillMount method and componentWillUpdate method. ( const { history } = this.props )
I want to destruct object just once! Is there any solution ?
There's not performance issues on using destructurization multiple times (you are not expanding the whole object if this is your fear).
An ES 6 code like::
const aaa = {
a: 5,
b: 'ffff'
}
const { bbb } = aaa;
... is translated to...
var aaa = {
a: 5,
b: 'ffff'
};
var bbb = aaa.bbb;
Try it to https://babeljs.io
So use it, or simply use this.props.history