React Unmount Base Component In Render-Prop Pattern - reactjs

So Here is Some Sample Code Of What I Am Trying To Accomplish.
class NetworkCallHOC extends React.Component {
makeNetworkCall = () => {
this.setState({ isInNetworkCall: true });
// Make Some Network Call;
// On NetworkCall Complete;
this.setState({ isInNetworkCall: false });
}
state = {
isInNetworkCall: false,
makeNetworkCall: this.makeNetworkCall,
}
render() {
const { isInNetworkCall } = this.state;
if (isInNetworkCall) {
return // Some Loading screen;
} else {
const { children } = this.props;
return children(this.state)
}
}
}
This 'Higher Order Render Prop' takes a component as a child and passes down a function makeNetworkCall.
Now when makeNetworkCall is called the child component is unmounted and after the network call is completed a new child component is mounted back again and all the lifecycle methods of the child component are called.
Is there a way to avoid re-creating the the child component in such a scenario.
Thanks Regards.

A solution would be to always render the children, along with an optional loading screen that would display on top of it.
Something like:
render() {
const { isInNetworkCall } = this.state;
const { children } = this.props;
return <div>
{children(this.state)}
<Mask visible={isInNetworkCall}/>
</div>
}

Related

Child components not updating while in array React Native

I'm trying to update child component which has a prop with parent state. While changing state of parent with setState() to my knowlage chlid should rerender. But when child is in an array rerender does not occure. How can i fix that?
Thanks in advance.
My child class:
class Child extends Component {
render() {
return(
<Text>{this.props.state.isActive.toString()}</Text>
)
}
}
My parent class:
class Parent extends Component {
state = {
isActive: false
}
children = []
constructor(props) {
super(props)
this.createChildren()
this.changeState()
}
changeState() {
this.change = setInterval(() => {
if(this.state.isActive){
this.setState({isActive: false})
} else {
this.setState({isActive: true})
}
}, 1000)
}
componentWillUnmount() {
clearInterval(this.change)
}
createChildren() {
for(let i=0; i<5; i++) {
this.children.push(<Child key={i} state={this.state}/>)
}
}
render() {
return(
<View>
{this.children}
</View>
)
}
}
My app function:
export default function App() {
return (
<View style={style.conteiner}>
<Parent/>
</View>
);
}
React elements should always be created during render. Do not store them in a property or in component state:
renderChildren = () => {
children = [];
for(let i=0; i<5; i++) {
children.push(<Child key={i} state={this.state}/>)
}
return children;
}
render() {
return(
<View>
{this.renderChildren()}
</View>
)
}
You are basically rendering always the same elements that you created once in the constructor (which also is an anti-pattern).
What you are doing is not going to work, since you are pushing <Child/> components to a class attribute which is only called inside the constructor; so these <Child/> components will refer to only initial state, not the now updated state. You will have to call a getChildren() function inside the render. I've fixed the issue in react, you can translate to react native replacing the divs with Views, see codesandbox : https://codesandbox.io/s/pedantic-jackson-wgtnh?file=/src/App.js

React parent is unmounted for child?

I have a React Parent and Child component. The Child has to be able to update the state of the Parent component. However, I have the issue that my Parent is unmounted when the Child wants to update the Parent, and I'm not sure why or how to fix that?
I'm pasting the simplified code that is relevant to the issue. In this case, the child wants to update the page Title that is rendered in the Parent. But sadly, in the componentDidMount() of the Child (where this update happens) the Parent is already unmounted.
The index.js loads the Child directly but it wraps the Parent component around it. I guess that has something to do with the Parent component unmounting?
Parent:
type Props = TranslationProps & {
Component: any
};
type State = {
setTitle: string
};
export class Layout extends Component<Props, State> {
_isMounted = false;
constructor(props:Props) {
super(props);
this.state = {
setTitle: ""
};
}
componentDidMount() {
this._isMounted = true;
}
componentWillUnmount() {
this._isMounted = false;
}
render() {
const { Component, ...restProps } = this.props;
return (
<Component
setTitle={title=> this._isMounted && this.setState({ setTitle: title})}
isMounted={this._isMounted}
{...restProps}
/>
);
}
}
Child:
type Props = TranslationProps & {
setTitle: (data: string) => void,
isMounted: boolean
};
componentDidMount() {
const { t } = this.props;
if(this.props.isMounted) {
this.props.setTitle("Test title");
}
}
Index:
const unwrap = component => {
if(component.WrappedComponent) {
return unwrap(component.WrappedComponent);
}
return component;
}
let reactMounts = document.querySelectorAll("[data-react]");
for (let reactMount of reactMounts) {
let Component = Components[reactMount.dataset.react];
let UnwrappedComponent = unwrap(Component);
console.log("Rendering", Component, " (", UnwrappedComponent, ") to ", reactMount);
let data = {
...reactMount.dataset,
Component
};
delete data.react;
render(
React.createElement(Components['Layout'], data),
reactMount
);
}
Reason: Accoring to React life-cycle, Child Component rendered before ComponentDidMount (where _isMounted = true). So, I guess the this._isMounted variable in setTitle props always = false => bug.
Solution: I recommend that you should create callback function in class, after that passing in setTitle props.

Where to keep reference to initial data for child component in ReactJS

I have a parent component which makes an API call after mounting. Part of the data it gets is display-initialization data for the child components, so I don't want to store it in its own state if it's not relevant to it. My issue is, where do i store the child data so that I can reference it and pass it to the child components' props in the parent's render method? I considered just having the child components fetch their own data after they mount, but it seems like it would be better to have at least some initial display, and then have them take care of updating themselves afterwards.
import React, { Component } from 'react';
import Game from './Game';
import GameSetup from './GameSetup';
let gameInit, setupInit;
class GameRoom extends Component {
id = this.props.match.params.id;
state = {};
boundHandlers = {
receiveGameRoomData: receiveGameRoomDataHandler.bind(this)
};
componentDidMount() {
this.props.socket.on('gameRoomData',
this.boundHandlers.receiveGameRoomData);
this.props.socket.send(JSON.stringify({event: 'getGameRoomData',
payload: this.id}));
}
componentWillUnmount() {
this.props.socket.off('gameRoomData',
this.boundHandlers.receiveGameRoomData);
}
render() {
const renderMainArea = () => {
if (this.state.gameInProgress) return <Game socket={this.props.socket}
initData={setupInit} />;
else if (this.state.gameInProgress === false) return <GameSetup
socket={this.props.socket} initData={gameInit} />;
return null; // this is only reached when gameInProgress is undefined,
meaning there's no data from the server yet
};
return (
<React.Fragment>
{renderMainArea()}
{this.state.scoreBoard ? <ScoreBoard scores={this.state.scoreBoard}
/> : null}
</React.Fragment>
)
}
}
function receiveGameRoomDataHandler(data) {
data = JSON.parse(data);
let inSetup = "setupData" in Object.keys(data);
if (inSetup) setupInit = data.setupData;
else gameInit = data.gameData;
inSetup ? delete data.setupData : delete data.gameData;
this.setState(data);
}
class ScoreBoard extends Component {
render() {
const scoreData = this.props.scores;
const playerNames = Object.getOwnPropertyNames(scoreData);
let tableData = '';
playerNames.forEach(name => {
tableData += `<tr><td>${name}<br/>${scoreData[name]}</td></tr>`;
});
return (
<table dangerouslySetInnerHTML={{__html: tableData}}></table>
)
}
}
export default GameRoom;

Called componentDidMount twice

I have a small react app. In App.js I have layout Sidenav and Content area. The side nav is shown on some page and hid from others. When I go to some components with sidenav, sidenav flag is set by redux and render the component again, in the componentDidMount I have api call, and it is executed twice.
class App extends Component {
renderSidebar = () => {
const {showNav} = this.props;
return showNav ? (
<TwoColumns>
<Sidenav/>
</TwoColumns>) : null;
};
render() {
const {showNav} = this.props;
const Column = showNav ? TenColumns : FullColumn;
return (
<Row spacing={0}>
{this.renderSidebar()}
<Column>
<Route exact path="/measurements/:id/:token/:locale/measure"
component={MeasurementPage}/>
</Column>
</Row>
)
}
}
const mapStateToProps = state => ({
showNav: state.sidenav.showNav
});
export default connect(mapStateToProps)(App);
I tried to use shouldComponentUpdate to prevent the second API call
class MeasurementPage extends Component {
constructor(props) {
// This update the redux "showNav" flag and re-render the component
props.toggleSidenav(false);
}
shouldComponentUpdate(nextProps) {
return !nextProps.showNav === this.props.showNav;
}
componentDidMount() {
// This is executed twice and made 2 api calls
this.props.getMeasurement(params);
}
render() {
return <h1>Some content here</h1>;
}
}
const mapStateToProps = state => ({
showNav: state.sidenav.showNav
});
export default connect(mapStateToProps)(MeasurementPage);
Did someone struggle from this state update and how manage to solve it?
This props.toggleSidenav(false) might cause side effect to your component lifecycle. We use to do this kind of stuff inside componentWillMount and it has been depreciated/removed for a reason :). I will suggest you move it inside componentDidMount
class MeasurementPage extends Component {
constructor(props) {
// This update the redux "showNav" flag and re-render the component
// props.toggleSidenav(false); // remove this
}
shouldComponentUpdate(nextProps) {
return nextProps.showNav !== this.props.showNav;
}
componentDidMount() {
if(this.props.showNav){ //the if check might not necessary
this.props.toggleSidenav(false);
this.props.getMeasurement(params);
}
}
render() {
return <h1>Some content here</h1>;
}
}
The comparison should be
shouldComponentUpdate(nextProps) {
return !(nextProps.showNav === this.props.showNav)
}
The problem is that !nextProps.showNav negate showNav value instead of negating the role expression value, and that is why you need an isolation operator.
It's No call twice anymore.
componentDidMount() {
if (this.first) return; this.first = true;
this.props.getMeasurement(params);
}

Passing value to props reactjs

I am trying pass value to my child components. The value that I am getting is coming from the an API that I called in my parent component and being called in the componentDidMount but the problem is the child components is not reading the props I am passing in his own componentDidMount, its only getting blank even in the reactdevtool it passing correct values. I solved this before but cannot remember what I did can you help. Thanks
Child:
componentDidMount() {
const {
events
} = this.props;
console.log(events)
}
Parent:
class App extends Component {
componentDidMount() {
let self = this;
GetAllMainItems().then(function(GetAllMainItemsResults) {
let MainObject = self.state.MainObject;
self.setState({
GetAllMainItemsResults
});
}
}
render() {
constructor() {
super();
this.state = {
MainObject: []
};
}
return ( <
div className = "App row" >
<
Calendar events = {
this.state.MainObject
}
/>
<
/div>
);
}
There are a few things you need to review.
constructor should be outside of render method.
You do not have to use let self = this. you can just do this.setState({...}) there.
Look at your GetAllMainItems callback. I don't know what you get
there. but you are definitely not setting mainObject in your state.
Instead, you will have this.state.GetAllMainItemsResults.
Recommendations
Try to understand object destructuring.
Use arrow functions
Hope it helps.
Parent Component
```
class App extends Component {
state = {
mainObject: ""
};
componentDidMount() {
GetAllMainItems().then(response => {
this.setState({
mainObject: response
});
});
}
render() {
const { mainObject } = this.state;
return (
<div className="App row">
<Calendar events={mainObject} />
</div>
);
}
}
The problem you are having is that your child component is re-rendering when it receives new events props.
Try adding a componentDidUpdate method to see these props updating:
componentDidUpdate(prevProps, prevState) {
console.log(prevProps, prevState);
console.log('events:', prevProps.events, this.props.events);
}

Resources