Strange problem using conditional rendering of component - reactjs

I have a simple React component that takes in a single prop, if this prop is 0 show one component otherwise show another.
On page load it equals 0 so the first component loads, once my Redux store is updated from localstorage this value in the parent component updates its local state passing down the new prop to my component.
What happens is rather than switching to render my other component it puts the second component inside of the first... I have never seen this behaviour before and cannot for the life of me figure out why.
I have tried all sorts, switching between stateless and functional components, I have tried using componentWillReceiveProps to receive the updated prop and setState based on this and have my conditional logic based on state. All with the same results.
const ComponentThatGetsUpdated = (props) => (
props.PropThatGetsUpdated === 0 ? <ComponentOne /> : <ComponentTwo />
);
I expect on page load the output to be the contents of
<div>contents of component one</div>
then once my redux store has updated the new props are passed down the output should now be the contents of component two
<div>contents of component two</div>
The actual output once props have been updated is
<ComponentOne />
<ComponentTwo></ComponentTwo>
<ComponentOne />
FYI the parent component of ComponentThatGetsUpdated looks like the below:
class ParentComponent extends Component {
constructor(props) {
super(props);
this.state = {PropToPassDown: 0}
this.sub = this.sub.bind(this);
const sub = store.subscribe(this.sub);
}
sub() {
//when my redux store is updated this function runs and sets PropToPassDown to the new value, for this example it sets PropToPassDown in local state to 1
}
render() {
return (
<ComponentThatGetsUpdated PropToPassDown={this.state.PropToPassDown} />
)
}
}

Related

Updating parent component without a call back function in react js

I was doing a POC coding on React js with component which has a Child component.As far as I know, there was no other way to update the state of parent from child except through a call back function from the child to the parent component. In my case, I tried to pass the state of the parent to the child and set them on the child state directly as props (this.props.).I noticed that if I change the state of the child, the state of the parent is also getting updated. I'm bit confused. Could somebody please help ?
Here is my code.
index.js
ReactDOM.render(<App2/>,document.getElementById('root'));
App2.js - Parent component
import React from 'react'
import ScreenTest From './ScreenTest'
class App2 extends React.Component{
state={
address : {
houseName:'1234 House Name'
}
}
render(){
return(
<ScreenTest parentState={this.state} address={this.state.address} />
)
}
}
ScreenTest.jsx - Child Component
import React from 'react';
class ScreenTest extends React.Component{
state={
parentState: this.props.parentState,
address : this.props.address
}
clickButton = () =>{
let addressTemp = this.state.address;
addressTemp.city= "Kerala";
this.setState({
address:addressTemp
})
}
render(){
console.log("To view the state when the screen renders",this.state)
return(
<a onClick={this.clickButton}>Click me to update the state and re render </a>
)
}
}
Code explanation:
I am invoking App2 component which has a child Component ScreenTest. I pass the currentState of App2 to ScreenTest. In ScreenTest i set the state from the values passed as props. In ScreenTest I have an anchor tag when clicked, updates the "address" state of ScreenTest and re render Screen. When the screen is re rendered , i check the state to see that parentState is also getting updated with new Address (i.e city is added to it).
Please tell me how the parentState is also getting affected by it. I'm bit confused.
You must note that when docs say that in order to update parent state from child, you must make use of callback and let the parent update its state, its the ideal and the correct way of doing it
In your code you are accidently updating the parent state you mutate the state by calling
let addressTemp = this.state.address;
addressTemp.city= "Kerala";
In Javascript, object are used by reference and updating a property in object directly will update it for anyone using that reference
So when you assign props to state in constructor like below
state={
parentState: this.props.parentState,
address : this.props.address
}
The state properties hold the reference of props object and hence the props also get updated when you mutate the state property by updating addressTemp state
The ideal way to update state is to clone it and then make changes so that you avoid unexpected issues
clickButton = () =>{
let addressTemp = {...this.state.address}; // clone state
addressTemp.city= "Kerala";
this.setState({
address:addressTemp
})
}

React class component inside react hook - DOM not updating

I have below composition:
const HookComponent = (props)=> {
let [month, changeMonth] = useState(moment());
return (
<ChildComponent
month={month}
onChange={m => changeMonth(m)}
minStep={5}
prevMonthIcon="ion-ios-arrow-left"
nextMonthIcon="ion-ios-arrow-right"
/>
)
}
The ChildComponent is a class component which updates the month using setState. Problem with above is that the change is not reflecting on DOM but the state in parent component is changing (via user input - button which changes the state in ChildComponent). I logged it and confirm the month in parent is changing. Is this some limitation of react when using class components within hooks?
When I convert HookComponent to class component and change month using setState, it works as expected and DOM changes on input change.
It seems that the InputMoment component does not use a month prop, but a moment one.
Also, it seems that InputMoment is returning the same moment instance that is passed as moment prop. This causes that when you execute the changeMonth statement, as the reference does not change, the element is not re-rendered.
You can solve this by storing an object in the state. When you call changeMonth you pass a new object, and the InputMoment is then re-rendered correctly:
const HookComponent = (props)=> {
let [month, changeMonth] = useState({ m: moment() });
return (
<ChildComponent
moment={month.m}
onChange={m => changeMonth({ m })}
minStep={5}
prevMonthIcon="ion-ios-arrow-left"
nextMonthIcon="ion-ios-arrow-right"
/>
)
}

When does React create a new component instance?

I have a question about the constructor in React
Look at a example at https://codesandbox.io/s/lpr147kmyl
import React from "react";
import ReactDOM from "react-dom";
import "./styles.css";
class SimpleComponent extends React.Component {
constructor(props) {
super(props);
this.state = {
number: props.number,
}
console.log('constructor', this.state.number)
}
render() {
return (
<div>
{this.props.number}
</div>
)
}
}
class App extends React.Component {
constructor(props){
super(props)
this.state = {
showFirstComponent: true,
}
setInterval(() => (
this.setState(previousState => (
{ showFirstComponent: !previousState.showFirstComponent }
))
), 1000);
}
render(){
let c1 = <SimpleComponent number="1" />
let c2 = <SimpleComponent number="2" />
return (
<div className="App">
<h1>
{this.state.showFirstComponent ? c1 : c2}
</h1>
</div>
);
}
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
I checked the console log and saw only one log message.
I thought there should be two log messages there as I create two instances of SimpleComponent :
let c1 = <SimpleComponent number="1" />
let c2 = <SimpleComponent number="2" />
My question is:
When does React create new a component instance and calls the component's constructor?
Thanks,
According to the doc, constructor is only called before a component is mounted. If you add two more React Lifecycle functions to your code example: componentDidMount and componentWillUnmount, you'll see that the component only mounted once in the entire process, regardless of the times the state changes. If you then add shouldComponentUpdate to the component, you'll see that there is only one instance of SimpleComponent and the upper-level state change is shown as props change on SimpleComponent.
The reason why you don't see two instances mounting and unmounting here is that variables c1 and c2 are not instances of SimpleComponent as commonly and conveniently assumed. Instead, they are ReactElement, which is a description of the component instance.
The primary type in React is the ReactElement. It has four properties: type, props, key and ref. It has no methods and nothing on the prototype.
React (Virtual) DOM Terminology
Only when the element gets rendered, an instance of the component gets created and constructor and the Lifecycle functions get called at their respective time points.
So then why, as in your example, the element c2 never gets to be used to create a component? This goes back to how React decides when and what to update at a certain time (you can read more about it here:
When a component updates, the instance stays the same, so that state is maintained across renders.
In your example, when the App component renders c2 instead of c1, React sees that the new component element is the same as the old one so it reuses the component(i.e. only a single instance exists) and updates the props/states accordingly.
Here are some more readings that I find useful to understand this problem:
https://developmentarc.gitbooks.io/react-indepth/content/life_cycle/birth_mounting_indepth.html
Mark Amery's answer to this question is also very helpful.
If someone has landed here for react-native.
Assigning different keys for different instances will create new instances instead of reusing the same instance.
<MyComponent
key={this.state.uniqueInstanceKey}
myData={this.state.myData}/>
Your ternary is checking the state for showFirstComponent and rendering only one.
{this.state.showFirstComponent ? c1 : c2}
If you remove that all together, and just do
<SimpleComponent number="1" />
<SimpleComponent number="2" />
You'll get two logs. To further answer the question, constructor is called when the component is mounted, so usually once, unless you're dynamically mounting stuff. To get back at your ternary:
{this.state.showFirstComponent ? c1 : c2}
If you were to toggle this.state.showFirstComponent with a button lets say:
<button onClick={() => this.setState({
showFirstComponent: !this.state.showFirstComponent
})>
Toggle c1/c2
</button>
Every time you hit this button, component will be unmounted, and another one mounted, so you'll get a console log. Here's the order of react lifecycle methods between mount and unmount. However, if you do anything else, eg change the props passed, change the internal component state of <SimpleComponent />, it will NOT unmount, but rather, update, which calls the render method, and not the constructor. I suggest adding a console.log for some lifecycle methods in your <SimpleComponent /> and trying to trigger them, to better understand the component lifecycle. Or read an article about it.

Child component do not see Parent Component state updates

I'm new to React and i'm facing an issue related to state updates.
I have a Parent Component. In the Parent Component constructor, i create multiple instance of a Child Component.
Using the state of the Parent Component, i display one of the Child Component instance.
Instances of Child Component have some Parent Component state value passed as props.
The Parent Component state looks like this (i have simplify the code so it can be clearer)
displayedContainer: {...} // An Instance of Child Component
isLoading: false
The Parent Component constructor looks like this
constructor(props) {
super(props);
// Create state
this.state = {
isLoading: false,
displayedContainer: null
};
// Create all Child Component (Container)
this.defaultComponent = <Container
isLoading={this.state.isLoading}
></Container>
// Others Child Component are created the same way as above.
// To clearify the code i have removed them.
}
And here is the render method
render() {
return (
<div>
{this.state.displayedContainer}
<div className="container-left-bar"></div>
</div>
)
}
From there, i can switch from one Child Component display to another so the state.displayedContainer is working. But when the state.isLoading is getting updated, Child Components doesn't detect it. I think it's because i'm creating the Child Component in the constructor.
How should i do if i want to keep the logic of creating Child Components before rendered it but fix the issue of state updates not detected ?
Thanks for the help !
The problem is that you render the <Container /> only once, in the constructor. The rendered instance is in the memory (this.defaultComponent) and therefore when you call this.setState the child never gets updated - is not notified about the change of any prop. This code should go to render() method.
Think of it like this:
When React determines this.setState (e.g. you want to display other container then the current one), React calls render() method, and should rerender <Container .../> with updated props. But since the code for rendering the component is not in the render() method - code that tells the <Container .../> to use newest isLoading prop from the state, <Container /> never really gets updated with new value of the isLoading prop (or any other prop).
You should achieve something like this:
render() {
...
let renderCurrentContainer = null
if (...) {
renderCurrentContainer = <Container isLoading={this.state.isLoading} ...otherPropsHere... />
}
else if (...) {
renderCurrentContainer = ...
}
else if (...) {
renderCurrentContainer = ...
}
return <...>
{renderCurrentContainer}
</...>
}
If you're asking what to put into the if condition, you need to somehow mark which component to render currently, I'll leave that to your creativity, but you can use something like currentRenderedContainerIndex which can have values {0, 1, 2}, or currentRenderedContainer string from enum e.g. {'FIRST_COMPONENT', 'SECOND_COMPONENT', 'THIRD_COMPONENT'}
Then you would go with something like this:
if (currentRenderedContainer === 'FIRST_COMPONENT') {
renderCurrentContainer = <Container isLoading= {this.state.isLoading} ...otherPropsHere... />
}
else if (currentRenderedContainer === 'SECOND_COMPONENT') {
renderCurrentContainer = ...
}
else if (currentRenderedContainer === 'THIRD_COMPONENT') {
renderCurrentContainer = ...
}

Reactjs route for asterisk executes only once

I have a single page app, I have defined all the Routes in the app to execute the same react component (using *-wildcard) when navigating to them.
it seems that the component will only execute once upon navigation.
How can I call an execution/instantiation of the component upon any change in navigation?
this is my Route jsx:
<Route path="/" component={App}>
{<IndexRoute component={TVPage} />}
{<Route path="*" component={TVPage} />}
</Route>
I assume when you say "the component only executes once" you mean it mounts only once.
Since you didn't show your code, I can only assume you have used one of the lifecycle methods: componentWillMount | componentDidMount
These methods only trigger once on Component mount. Given your Route configuration, whenever you switch to a different URL, since it's using the same component, it will not unmount and mount again (thus your loading logic is only triggered once), but simply re-render if its props have changed. That's why you should plug on a lifecycle method that is triggered on every prop change (like componentWillReceiveProps).
Try this instead:
class TVPage extends Component {
constructor(props) {
super(props);
}
componentWillMount() {
// Load your data/state (initial)
this.props.loadMyData(this.props.whatever.data);
}
componentWillReceiveProps(nextProps) {
if (this.props.whatever.myStatus !== nextProps.whatever.myStatus) {
// Load your data/state (any page change)
nextProps.loadMyData(nextProps.whatever.data);
}
}
render() {
// Render whatever you want here
}
}
componentWillMount will trigger on mount (initial load), and componentWillReceiveProps will trigger at least every time your props change.
Look at this example for react router using query params : https://github.com/reactjs/react-router/blob/master/examples/query-params/app.js
In your componentDidMount function inside your TVPage component, I would get the data passed as params in the URL which then updates the state of the component. Every time the state changes within the component, it will reload itself.
Example component :
class TVPage extends Component {
constructor(props) {
super(props);
this.state = {
data: null
}
}
componentDidMount() {
// from the example path /TVPage/:id
let urlData = this.props.params.id;
this.setState({data: urlData})
}
render() {
return (
<div>
{this.state.data}
</div>
);
}
}

Resources