Child not updating after Parent State changed - reactjs

I am quite new with React and I have problem bellow
I have a parent component like this:
class Parent extends React.Component {
constructor(props) {
super(props);
this.state = {count:1};
}
shouldComponentUpdate(nextProps, nextState, nextContext) {
return false;
}
setCount = () => {
this.setState({
count: 2
});
};
render() {
const {name, running, onRun, onStop} = this.props;
return (
<div>
<Test count={this.state.count}/>
<p><a href="#" onClick={this.setCount}>SetCount</a></p>
</div>
);
}
}
And here is Test component
class Test extends React.Component {
constructor(props) {
super(props);
}
shouldComponentUpdate(nextProps, nextState, nextContext) {
return true;
}
render() {
const {count} = this.props;
return (
<div>
{console.log("Counting")}
<p>{count}</p>
</div>
);
}
}
I have method "shouldComponentUpdate" returns "false" in Parent component because I don't want to re-render it.
My understanding is React know which part of DOM need to be re-rendered. And in this case, the state of Parent changes will re-render "Test" component
But when I run above code, "Test" component does not redender.
Is there anything wrong in my code?
Thanks a lot for your help

You need to return true from your parent's shouldComponentUpdate method.
If you return false, after the initial render it won't update, even if you call a function that calls setState.

Is the refresh of the whole page are you talking about? If thats the case, probably you wanna change your <a> tag to button or use e.preventDefault();.
If not, I am not sure if that is possible. If you setState in the parent, it will rerender parent as well as the children. If you dont want to render the parent then you have to manage individual state management in the child level.
For example,
class Parent extends React.Component {
constructor(props) {
super(props);
}
render() {
const {name, running, onRun, onStop} = this.props;
return (
<div>
<Test/>
</div>
);
}
}
class Test extends React.Component {
constructor(props) {
super(props);
this.state = {count:1};
}
setCount = (e) => {
e.preventDefault();
this.setState({
count: 2
});
};
render() {
const {count} = this.state;
return (
<div>
{console.log("Counting")}
<p>{count}</p>
<p><a href="#" onClick={this.setCount}>SetCount</a></p>
</div>
);
}
}

Related

Render child component in parent after re-rendering sibling component

I have a parent component housing two children components(AddPersonForm and PeopleList). When I submit a name via the AddPersonForm, I expect it to be rendered in the PeopleList component, but it doesn't.
Here is my AddPersonForm:
class AddPersonForm extends React.Component {
state = {
person: ""
}
handleChange = (e) => this.setState({person: e.target.value});
handleSubmit = (e) => {
if(this.state.person != '') {
this.props.parentMethod(this.state.person);
this.setState({person: ""});
}
e.preventDefault();
}
render() {
return (
<form onSubmit={this. handleSubmit}>
<input type="text" placeholder="Add new contact" onChange={this.handleChange} value={this.state.person} />
<button type="submit">Add</button>
</form>
);
}
My PeopleList component:
class PeopleList extends React.Component {
constructor(props) {
super(props);
const arr = this.props.data;
this.state = {
listItems: arr.map((val, index) => <li key={index}>{val}</li> );
}
}
render() {
return <ul>{this.state.listItems}</ul>;
}
}
Now the parent component, ContactManager:
class ContactManager extends React.Component {
state = {
contacts: this.props.data
}
addPerson = (name) => {
this.setState({contacts: [... this.state.contacts, name]});
render() {
return (
<div>
<AddPersonForm parentMethod={this. addPerson}×/>
<PeopleList data={this.state.contacts} />
</div>
);
Please what I'm I doing wrong, or not doing?
The issue is in your PeopleList component. The state object which renders your list is created in the constructor when the component mounts, but you have no way of updating it when it recieves new values. It will always give you the initial value.
You could introduce a lifecycle method, componentDidUpdate, which would allow you to compare the previous props to the new props when they arrive, and update the state accordingly. I would recommend you not do this for two reasons:
Storing props directly in a components state is not good practice. You are just creating a copy of the state in the component above and that creates opportunities for confusion and stale values when one of them updates. Ideally, each piece of data should live in only one place.
If all PeopleList is doing is rendering your data, then it doesn't need any state at all. It can act as a display component that maps your props in place and doesn't have to worry about updating itself or managing its own data. This would actually make it a good candidate for conversion into a functional component.
class PeopleList extends React.Component {
render() {
return (
<ul>
{this.props.data.map((val, index) => (
<li key={index}>{val}</li>
))}
</ul>
);
}
}
You are initializing PeopleList with props when its created and mounted but then you are not using new values of props for updating it.
To fix your issue use current value of prop when rendering:
class PeopleList extends React.Component {
render() {
return <ul>{ this.props.data.map((val, index) => <li key={index}>{val}</li>) }</ul>;
}
}

LocalStorage in state of the component

I have two components Parent and Children. I want to see on my screen actual value of localStorage.getItem("myEl"). Parent state is storage:localStorage.getItem("myEl"). I change the "myEl" in localeStorage in Children component. Unfotunately Parent component not re-renders after "myEl" is changed but it works after I perform some action, such as changing the state again. I know that the problem is that setState is asinc but i don't know how to fix the problem.
For example,
Parent:
class Parent extends React.Component {
constructor() {
super();
this.state = {storage:localStorage.getItem("myEl")};
}
render(){
return <div>
<Child/>
<p>{this.state.storage}</p>
</div>
}
}
Child:
let i=0;
class Child extends React.Component {
render() {
return (
<button onClick={() => {
localStorage.setItem("myEl",i);
i++;
}}>click me</button>
);
}
}
react is not listening to changes in localStorage that is why parent component don't know when child component changes the value in localStorage.
To fix this you have to path your child component onClick function from parent this way:
class Parent extends React.Component {
constructor() {
super();
this.state = {storage:localStorage.getItem("myEl")};
}
handleChildClick = (count) => {
localStorage.setItem("myEl", count);
this.setState({ storage:localStorage.getItem("myEl") });
}
render(){
return <div>
<Child onClick={this.handleClick} />
<p>{this.state.storage}</p>
</div>
}
}
let i=0;
class Child extends React.Component {
render() {
return (
<button onClick={() => {
this.props.onClick(i);
i++;
}}>click me</button>
);
}
}
in case you need this value in other components consider using redux with react-redux containers to have a global storage available to you in any place of the react app.
Component should receive an state or prop in order to rerender itself, in your case it receive none of them. You should not update the localStorage and expect that your component is going to be reRendered with a new value from local storage, you could write a handler for your button in order to save the incremented value into your localstorage. Like below:
class App extends React.Component {
constructor() {
super()
this.state = { _val: 0 }
}
componentDidMount = () => {
const valFromLocalStorage = localStorage.getItem("myEl") || this.state._val
this.setState({ _val: valFromLocalStorage })
}
handleINC = e => {
const _valFromState = this.state._val
const _val = _valFromState++
localStorage.setItem("myEl", _val)
}
render() {
return(
<div>
<button onClick={this.handleINC}>increment value!</button>
</div>
)
}
}
By the way, in componentDidMount you get the value from localStorage or if it was falsy you get the default value from your state. Then in button handler function you get the value from state and increment it and set it in your localStorage in case of component use cases in future, when user closes the tab and opens our website after a while the localstorage data is not been cleared, then this component will get the value from there.

React.js, why my class component doesn't rerender the element, but functional component can work?

I'm new to react.js, just follow the tutorial. Here is my code. At first, i tried to use the class Component 'Greeting' to let it show different words after
clicked the button, but i don't know what's wrong, it doesn't rerender the element, and the construtor() method of Greeting only called once. The commented out code functional Component 'Greeting' works well. Not sure what's the difference :(
class GreetingGuest extends React.Component {
render() {
return (
<h3>hello Guest, Click login button !!! </h3>
);
}
}
class GreetingUser extends React.Component {
render() {
return (
<h3>You have logged in, welcome !!!</h3>
);
}
}
class Greeting extends React.Component {
constructor(props) {
super(props);
console.log('Greeting.state.is_logon = ', props.is_logon);
this.state = {is_logon: props.is_logon};
}
render() {
let welcome_msg = null;
if (this.state.is_logon) {
welcome_msg = <GreetingUser />;
}else {
welcome_msg = <GreetingGuest />;
}
return welcome_msg;
}
}
//function Greeting(props) {
// const is_logon = props.is_logon;
// if (is_logon) {
// return <GreetingUser />;
// }
// return <GreetingGuest />;
//}
class LoginComponent extends React.Component {
constructor(props) {
super(props);
this.state = {is_logon: false};
this.handleClick = this.handleClick.bind(this);
}
handleClick() {
this.setState(prevState => ({
is_logon: !prevState.is_logon
}));
}
render() {
let button = null;
let greeting = null;
if (this.state.is_logon) {
button = (
<button onClick={this.handleClick}>Logout</button>
);
greeting = <Greeting is_logon={this.state.is_logon} />
}else {
button = (
<button onClick={this.handleClick}>Login</button>
);
greeting = <Greeting is_logon={this.state.is_logon} />
}
return (
<div>
{greeting}
{button}
</div>
);
}
}
ReactDOM.render(
<LoginComponent />,
document.getElementById('Login')
)
HTML:
<html>
<body>
<div id="Login"></div>
</body>
<html>
The reason the class component doesn't re render, is because you have stored the logged_in prop in state from the constructor, and the constructor is only called once. Also state can only be modified from within the component.
To fix this you have 2 options;
Use componentWillReceiveProps, and update the local state with the new logged_in prop.
componentWillReceiveProps(nextProps) {
if (nextProps.logged_in !== this.state.logged_in) {
this.setState({ logged_in: nextProps.logged_in });
}
}
Or; do not use state but use the prop directly.
render() {
let welcome_msg = null;
if (this.props.is_logon) {
welcome_msg = <GreetingUser />;
}else {
welcome_msg = <GreetingGuest />;
}
return welcome_msg;
}
Where I think you should use the latter, since the parent component already maintains state.
Well to be honest the answer which I posted previously was wrong. It was because the way you posted the question telling that everything works fine when function based component is added. Then I created a project using your code and figured out few issues in your code.
Firstly you are maintaining state locally outside redux here. You are passing down the state of the login from the parent LoginComponent to the child component called Greeting like this.
greeting = <Greeting is_logon={this.state.is_logon} />
This gets passed as a props to the child component which is Greeting in this case. Remember React uses one way data flow.
Now from that child component you can access the data using this.props as shown below. You don't need to maintain any local state what so ever there.
Do the following changes in your Greeting component.
constructor(props) {
super(props);
}
Then make sure you access the values from this.props instead of any local state object like this.
render() {
let welcome_msg = null;
if (this.props.is_logon) {
welcome_msg = <GreetingUser />;
}else {
welcome_msg = <GreetingGuest />;
}
return welcome_msg;
}
}
This solved the issue. Happy Coding !

React child component can't get props.object

My parent component is like this:
export default class MobileCompo extends React.Component {
constructor(props) {
super(props);
this.state = {
data: null,
datasets: {}
};
this.get_data = this.get_data.bind(this);
}
componentWillMount() {
this.get_data();
}
async get_data() {
const ret = post_api_and_return_data();
const content={};
ret.result.gsm.forEach((val, index) => {
content[val.city].push()
});
this.setState({data: ret.result.gsm, datasets: content});
}
render() {
console.log(this.state)
// I can see the value of `datasets` object
return (
<div>
<TableElement dict={d} content={this.state.data} />
<BubbleGraph maindata={this.state.datasets} labels="something"/>
</div>
)
}
}
child component:
export default class BubbleGraph extends React.Component {
constructor(props) {
super(props);
this.state = {
finalData: {datasets: []}
};
console.log(this.props);
// here I can't get this.props.maindata,it's always null,but I can get labels.It's confusing me!
}
componentWillMount() {
sortDict(this.props.maindata).forEach((val, index) => {
let tmpModel = {
label: '',
data: null
};
this.state.finalData.datasets.push(tmpModel)
});
}
render() {
return (
<div>
<h2>{this.props.labels}</h2>
<Bubble data={this.state.finalData}/>
</div>
);
}
}
I tried many times,but still don't work,I thought the reason is about await/async,but TableElement works well,also BubbleGraph can get labels.
I also tried to give a constant to datasets but the child component still can't get it.And I used this:
this.setState({ datasets: a});
BubbleGraph works.So I can't set two states at async method?
It is weird,am I missing something?
Any help would be great appreciate!
Add componentWillReceiveProps inside child componenet, and check do you get data.
componentWillReceiveProps(newProps)
{
console.log(newProps.maindata)
}
If yes, the reason is constructor methos is called only one time. On next setState on parent component,componentWillReceiveProps () method of child component receives new props. This method is not called on initial render.
Few Changes in Child component:
*As per DOC, Never mutate state variable directly by this.state.a='' or this.state.a.push(), always use setState to update the state values.
*use componentwillrecieveprops it will get called on whenever any change happen to props values, so you can avoid the asyn also, whenever you do the changes in state of parent component all the child component will get the updates values.
Use this child component:
export default class BubbleGraph extends React.Component {
constructor(props) {
super(props);
this.state = {
finalData: {datasets: []}
};
}
componentWillReceiveProps(newData) {
let data = sortDict(newData.maindata).map((val, index) => {
return {
label: '',
data: null
};
});
let finalData = JSON.parse(JSON.stringify(this.state.finalData));
finalData.datasets = finalData.datasets.concat(data);
this.setState({finalData});
}
render() {
return (
<div>
<h2>{this.props.labels}</h2>
<Bubble data={this.state.finalData}/>
</div>
);
}
}

React how to check props before passing in another component

Lets say I have a component <MyComponent check={this.props} />. This will call MyComponent class. Here I am doing something like this:
render(){
const {user} = this.props
return (){
<div>Welcome {user.name}</div>
}
}
In my container initially there is no user.name when the user is logged in then only user.name is available in props. In componentWillMount I have checked for isLoggedIn and if not I'm redirecting it, but in render method it checks for user.name property which is not available before login.
If isLoggedIn is true then <MyComponent check={this.props} /> which calls my MyComponent should return a redirection or other.
Does anyone have any idea how to implement this?
Your are able to check your props in componentWillReceiveProps method. It takes nextProps as a parameter.
class Parent extends React.Component {
constructor(props){
this.state = {
increment: 0
}
this.myClick = this.myClick.bind(this)
}
myClick(){
this.setState({
increment: this.state.increment + 1
})
}
render(){
return <div>
<Child number={this.state.increment}/>
<button onClick={this.myClick}>Click And Get Props</button>
</div>
}
}
class Child extends React.Component {
constructor(props){
console.log(props)
super(props)
this.state = {
count: props.number
}
}
componentWillReceiveProps(nextProps){
console.log(nextProps.number)
if(nextProps.number % 2) this.setState({count: nextProps.number})
}
render(){
return <div>
<span>Props: {this.props.number}</span><br/>
<span>State: {this.state.count}</span>
</div>
}
}
React.render(<Parent />, document.getElementById('container'));
Fiddle Example
Also please have a look at this link i hope it will help you.
Thanks
Use check inside render method, in your case
componentWillMount(){
this.state = {
isLoggedIn: this.props.isLoggedIn
}
}
componentWillReceiveProps(nextProps){
this.setState({isLoggedIn: nextProps.isLoggedIn})
}
render(){
return (
this.props.isLoggedIn && <MyComponent check={this.props} />
)
}

Resources