React: pass value from parent to child does not work - javascript

I need to pass a valueindex from parent to child. Both parent and child need have function to revise index. When parent revise index, child can not get the update. is there anything I can fix it?
parent class:
constructor(props) {
super(props);
this.state = {
index: 0
}
}
parentFunction(dir) {
this.setState({
index: 10
});
}
render() {
return(
<Child index={this.state.index}/>
);}
childclass:
constructor(props) {
super(props);
this.state = {
index: this.props.index
};
}
childFunction(dir) {
this.setState({
index: this.props.index+1
});
}
render() {
return(
<div>{this.state.index}</div>
);}

You don't need to keep the updater function in both the class. You can pass the updater function from parent down to child and let the parent handle the state updates. Also setting a state based on props in constructor is an anti-pattern. You should use the prop in child directly for your usecase. In case you need to update the child state from props make sure to do that in the componentWillReceiveProps as well, since the constructor is only called the first time and the componentWillReceiveProps on every parent re-render
componentWillReceiveProps(newProps) {
if (newProps.index!== this.props.index) {
this.setState({
index:newProps.index
})
}
}
However what you need is
parent class:
constructor(props) {
super(props);
this.state = {
index: 0
}
}
parentFunction(dir) {
this.setState({
index: 10
});
}
updaterFunction(dir) {
this.setState((prevState) => ({
index: prevState.index+1
}));
}
render() {
return(
<Child updaterFunction={(val) => this.updaterFunction(val)} index={this.state.index}/>
);}
childclass:
updateProps = () => {
this.props.updaterFunction();
}
render() {
return(
<div>{this.props.index}</div>
);}

If you read the article about the lifecycle of react react life cycle then u will understand why,you pass the value from the parent to the children,but you didn't tell the react the it should be updated,add the code like beblow:
//add this life cycle in the child component
componentWillReceiveProps(newProps) {
if (newProps.index!== this.props.index) {
this.setState({
index:newProps.index
})
}
}

You have (at least) 2 options.
The best way to do this is to pass the parent function that changes
the index to the child via props. Then you can call that function in
the child class and it will change the parents state. In the render
method of the child class just use the index from props rather than
local state of the child.
The bad way to do this would be in the render method of child to
have a call to set state that sets state of index equal to the value
passed in props.

Related

shouldComponentUpdate() is not being called

Problem
I've parent class which contains list of items and renders component for each item of the list. When some item has been changed (even only one), all items in the list are being rerendered.
So I've tried to implement shouldComponentUpdate(). I am using console.log() to see if it is called but I can't see the log. I've found question shouldComponentUpdate is not never called and tried to return return (JSON.stringify(this.props) !=JSON.stringify(nextProps)); but component still renders itself again. So I've tried just to return false (like do not ever update) but it still does. As the last try I've used PureComponent but it is still being rerendered.
Question
How can I stop children re-rendering if the parent list changes and why is ShouldComponentUpdate never called?
Edit
I've noticed something what I didn't mention in question, I'm sorry for that. I am using context. If I don't use context -> it's ok. Is there any chance to stop re-render while using context? (I'm not using context on updated item - values of context didn't change).
Example
I've parent class which iterates list and renders TaskPreview component for each item of list:
class Dashboard extends React.Component
{
constructor(props) {
this.state = {
tasks: {},
};
}
onTaskUpdate=(task)=>
this.setState(prevState => ({
tasks: {...prevState.tasks, [task._id]: task}
}));
// ... some code
render() {
return (
<div>
{(!Object.entries(this.props.tasks).length)
? null
: this.props.tasks.map((task,index) =>
<TaskPreview key={task._id} task={task} onChange={this.onTaskUpdate}/>
})}
</div>
)
}
}
and I've children TaskPreview class:
class TaskPreview extends React.Component
{
shouldComponentUpdate(nextProps) {
console.log('This log is never shown in console');
return false; // just never!
}
render() {
console.log('task rendered:',this.props.task._id); // indicates rerender
return(<div>Something from props</div>);
}
}
TaskPreview.contextType = TasksContext;
export default TaskPreview;
As #Nicolae Maties suggested I've tried to use Object.keys for iteration instead of direct map but it still doesn't call "shouldComponentUpdate" and still being re-rendered even if there is no changes.
Updated code:
render() {
return (
<div>
{(!Object.entries(this.props.tasks).length)
? null
: Object.keys(this.props.tasks).map((key,index) => {
let task = this.props.tasks[key];
<TaskPreview key={task._id} task={task}/>
}
})}
</div>
)
}
Component is being re-rendered because of .contextType.
TaskPreview.contextType = TasksContext;
Also as is mentioned in documentation:
The propagation from Provider to its descendant consumers (including .contextType and useContext) is not subject to the shouldComponentUpdate method, so the consumer is updated even when an ancestor component skips an update. Source: reactjs.org/docs/context
You have to use context somehow else or do not use it at all.
You can use Context.Consumer which won't force re-render of current component but it might force re-render of its children.
<TasksContext.Consumer>
{value => /* render something based on the context value */}
</TasksContext.Consumer>
Instead of return (JSON.stringify(this.props) != JSON.stringify(nextProps)); in your shouldComponentUpdate() life cycle, try specifying tasks object like this return (JSON.stringify(this.props.tasks) != JSON.stringify(nextProps.tasks));
Maybe react is creating new instances of your component and replaces the old instances with them. That's why you're probably not getting your lifecycle method invoked. That can happen if the key property you're assigning in the map always changes.
use from pureComponent and array as state:
class Dashboard extends React.PureComponent
{
constructor(props) {
this.state = {
tasks: this.props.tasks
}
}
onTaskUpdate=(task)=>
this.setState(prevState => ({
tasks: [...prevState.tasks, task] // render only new task
}));
render() {
const {tasks} = this.state
return (
<div>
{tasks.map(task => <TaskPreview key={task._id} task={task} />)}
</div>
)
}
}
class TaskPreview extends React.PureComponent
{
render() {
console.log('task rendered:',this.props.task._id); // indicates rerender
return(<div>Something from props</div>);
}
}
In the shouldComponentUpdate() method of your TaskPreview component, you should check if the next props have changes in comparison to the current props. Then if there are changes, return true to update the component, otherwise false.
The following example compares all the fields of props object with the new props object. But you can only check the props you are interested in.
shouldComponentUpdate(nextProps) {
return !!(Object.keys(nextProps).find(key => nextProps[key] !== this.props[key]));
}
I tried with below code snippet, shouldComponentUpdate worked as I expected. Could you share your Dashboard initial props ?
class Dashboard extends React.Component {
constructor(props) {
this.state = {
tasks: {}
};
}
onTaskUpdate = task =>
this.setState(prevState => ({
tasks: { ...prevState.tasks, [task._id]: task }
}));
// ... some code
render() {
return (
<div>
{!Object.entries(this.props.tasks).length
? null
: Object.keys(this.props.tasks).map((key, index) => {
let task = this.props.tasks[key];
return (
<TaskPreview
key={task._id}
task={task}
onChange={this.onTaskUpdate}
/>
);
})}
</div>
);
}
}
class TaskPreview extends React.Component {
shouldComponentUpdate(nextProps) {
console.log("This log is never shown in console");
return nextProps.task._id != this.props.task._id;
}
render() {
console.log("task rendered:", this.props.task); // indicates rerender
return (
<button onClick={() => this.props.onChange(this.props.task)}>
Something from props
</button>
);
}
}
my initial props for Dashboard component is :
<Dashboard tasks={{test:{_id:'myId', description:'some description'}}}/>

Make a copy of state to edit it indirectly in children

I'm setting state in the parent component, then passing it as props to a child component. In the child, I would like to make a copy of that state object (received through props) to edit without updating the original state object yet. I will then display that information in the current child component and send the updated state object back to the parent to update the original state object. The now updated parent state would then be used to display information in other child elements.
In child component - where 'this.props.shifts' is the passed down state from the parent:
this.dowCopy = { ...this.props.dow };
this.state = {
dowCopy: this.dowCopy
}
addTempShift = (day) => {
const emptyShift = {arbitraryData};
const dowCopyCopy = {...this.state.dowCopy};
dowCopyCopy[day].shifts.push(emptyShift);
this.setState({ dowCopy: dowCopyCopy })
}
Everything works as intended, but when I set state here in the child component, it's also updating state in the parent. It was my understanding that the spread operator took a copy of the state object. What am I missing?
Spread syntax creates shallow copies as told by #Timothy Wilburn. So, if you change copied object property directly then you will mutate the original one. You can search for "deep copy" or you can again use spread syntax in the child component.
class App extends React.Component {
state = {
dow: {
monday: {
shifts: [{ name: "foo" }, { name: "bar" }],
},
}
}
render() {
return (
<div>
In parent: {JSON.stringify(this.state.dow)}
<Child dow={this.state.dow} />
</div>
);
}
}
class Child extends React.Component {
state = {
dowCopy: { ...this.props.dow }
}
addTempShift = (day) => {
const emptyShift = { name: "baz" };
this.setState( prevState => (
{
dowCopy: { ...prevState.dowCopy, [day]: { shifts:
[...prevState.dowCopy[day].shifts, emptyShift] } }
}
))
}
componentDidMount() {
this.addTempShift( "monday");
}
render() {
return (
<div>In Child: { JSON.stringify(this.state.dowCopy)}</div>
)
}
}
ReactDOM.render(<App />, document.getElementById("root"));
<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="root"></div>
Oh, boy.
But, I agree with #George Chanturidze, you are overdoing. Do not copy the state like that. If you want to show some changed data in the Child component, then change it using this state as its prop. Then, if you want to change the parent's state, gather this data and send back to the parent with a callback and set the state there.

React child state update based on parent's prop change

I am trying to implement minesweeper with React. I have a Header component (not shown) where the dimensions and the number of bombs can be changed so the App component state (sizeX, sizeY and bombNumber) will be updated, and these values will be sent down to the Table component as props.
My problem is that the Table component state is dependent on App component's props and when the createTable()method gets invoked on prop updates it uses the updated sizeX and sizeY props (which is good of course) but for some reason it uses the previous value of the bombNumber prop.
Is this the right approach? If so what did I miss?
class App extends Component {
constructor (props) {
super(props);
this.state = {
sizeX: DEFAULT_SIZE,
sizeY: DEFAULT_SIZE,
maxBombNumber: DEFAULT_BOMB_NUMBER,
bombNumber: DEFAULT_BOMB_NUMBER
}
updateSize (val, side) {
this.setState({[side]: val}, function () {
this.setState({maxBombNumber: this.calculateMaxBombNumber()}, function () {
if (this.state.maxBombNumber < this.state.bombNumber) {
this.setState({bombNumber: this.state.maxBombNumber}, function (){
});
}
});
});
}
render() {
return (
<div className="App">
<Table
sizeX={this.state.sizeX}
sizeY={this.state.sizeY}
bombNumber={this.state.bombNumber}
/>
</div>
);
}
}
Table.js
class Table extends Component {
constructor (props) {
super(props);
this.state = {
table: this.createTable()
}
componentWillReceiveProps(nextProps) {
this.setState({table: this.createTable()});
}
createTable() {
// function using sizeX, sizeY and bombNumber
}
}
Update:
The console log here prints the right values if I change the state, but the child will use the old value for bombNumber.
updateSize (val, side) {
this.setState({[side]: val}, function () {
this.setState({maxBombNumber: this.calculateMaxBombNumber()}, function () {
if (this.state.maxBombNumber < this.state.bombNumber) {
this.setState({bombNumber: this.state.maxBombNumber}, function (){
console.log(this.state);
});
}
});
});
}
Try to wrap all setState calls in one:
updateSize(val, side) {
const maxBombNumber = this.calculateMaxBombNumber();
const bombNumber = maxBombNumber < this.state.bombNumber ? maxBombNumber : this.state.bombNumber;
this.setState({
[side]: val,
maxBombNumber,
bombNumber
});
}
UPD:
componentWillReceiveProps(nextProps) {
this.setState({table: this.createTable()});
}
When Table component get new props from parent App –> componentWillReceiveProps execute and this.createTable() method use previous values not that in nextProps. In this way you should give it properly by arguments to createTable for example. Check this example.
UPD 2:
It will be better to make Table component – dump (stateless) and use state only in parent component App. Just move createTable method to App and it will be enough.
Or you can call createTable inside render method of Table.
I believe your issue is that setState is an asynchronous call, so the this.state that you're using is not updated. See this article for more information about using setState with a function. https://medium.freecodecamp.org/functional-setstate-is-the-future-of-react-374f30401b6b
I think this would get you what you want:
updateSize(val, side) {
this.setState({ [side]: val }, function () {
this.setState({ maxBombNumber: this.calculateMaxBombNumber() }, this.setState(function (state) {
if (state.maxBombNumber < state.bombNumber) {
this.setState({ bombNumber: state.maxBombNumber }, function () {
console.log(state);
});
}
}));
});
}

ReactJS: How to get state value into container?

I need to get data from DB depending on a search string value. Therefore I'm using an input field. The search string is stored as a state value.
The data for the component comes from a container (using npm meteor/react-meteor-data).
Now my problem is, how do I get the search string into the container to set the parameter for the publication?
container/example.js
export default createContainer((prop) => {
Meteor.subscribe('images', searchString) // How to get searchString?
return { files: Images.find({}).fetch() }
}, Example)
component/example.jsx
class Example extends Component {
constructor(props) {
super(props)
this.state = {
searchString: ''
}
}
searchImage(event) {
const searchString = event.target.value
this.setState({ searchString })
}
render() {
return (<Input onChange={ this.searchImage.bind(this) }/>)
}
}
export default Example
publication
Meteor.publish('images', function(search) {
return Images.find({ title: search }).cursor
})
Maybe you can create two different components: a parent and a child, and you can wrap child component with createContainer HOC like the following
childComponent.js
const Example = (props) => {
return <Input onChange={props.searchImage}/>
}
export default createContainer(({searchString}) => {
Meteor.subscribe('images', searchString)
return { files: Images.find({}).fetch() }
}, Example)
parentComponent.js
class ExampleWrapper extends Component {
constructor(props) {
super(props)
this.state = {
searchString: ''
}
}
searchImage = (event) => {
const searchString = event.target.value
this.setState({ searchString })
} // instead of binding this, you can also use arrow function that
// takes care of binding
render() {
return (<Example searchImage={this.searchImage} searchString={this.state.searchString} {...this.props} />)
}
}
export default ExampleWrapper
The idea is, since createContainer is a higher order component, it doesn't have access to the props of any component wrapped by it.
What we need to do is, passing the value of searchString from a parent component.
The way to do is the following:
ExampleWrapper has a state called searchString and Example component has a prop called searchString. We can set the value of searchString prop to state.searchString.
Since the default export corresponds to createContainer({..some logic…}, Example}), createContainer can make use of prop called searchString.
In order to change the value of state.searchString we also passed searchImage function as a prop to Example component. Whenever there is a change event, onChange triggers searchImage function that updates the value of state.searchString. And eventually, the minute the value of state.searchString changes searchString prop’s value changes thus your subscription result also changes
onChange={ (e)=> {this.setState({ searchString: $(e.target).val() }) } }
This is how we assign values to our internal state properties :)
EDIT: I appear to have misunderstood the question...

componentWillReceiveProps on react does not pass the current property when ever it triggers

Hello guys I'm new to react
I'm working with react component passing a property from a state of parent component and I'm not sure why i get an undefined property error whenever i trigger and event from the parent component
You can visit the code here# https://codepen.io/private_ryan/pen/RVBdpO?editors=0011#live-view show console and click the edit button
SampleTable Component
constructor(props, context) {
super(props);
this.state = { UPD:[] };
}
updateRow(x) {
var array = this.state.TRs;
var index = array.findIndex(e => e.id == x);
this.setState({
UPD: this.state.TRs[index]
});
}
render() {
return (<AddFormData onAdd={ this.onAddForm }
upd={ this.state.UPD }
updcan={ this.cancelUpd }
propUpd= { this.propcessUpd } />
<button onClick={ this.updateRow} value={ some.id } >click me</button>
);
}
AddFormData Component
constructor(props) {
super(props);
this.state = { textName: '', textArea: '' };
}
componentWillReceiveProps(){
console.log( this.props ) // undefined no props when first click
// set the state here
}
New props are received as parameters to the function:
componentWillReceiveProps(nextProps)
https://facebook.github.io/react/docs/react-component.html#componentwillreceiveprops
componentWillReceiveProps will get called whenever you do any changes in props values in parent component, new values will get passed as parameter and after this lifecycle method this.props will get updated so if you do console.log(this.props) inside this it will log the previous value not the new one.
Use this:
componentWillReceiveProps(newProps){
console.log(this.props.upd.id, newProps)
}
Check the working example.

Categories

Resources