React parent is unmounted for child? - reactjs

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.

Related

React: Mutating instance of Ref object in child components

class Parent extends React.Component {
constructor(props) {
super(props)
this.data = React.createRef()
}
componentDidMount() {
this.data.current = []
}
render() {
return <Child data={data} />
}
}
const Child = ({ data }) => {
const onDelete = (idx) => {
data.splice(idx, 1)
}
return (
<button onClick={(index) => onDelete(index)}>Delete</button>
)
}
I need this ref to be an array for some complicated to explain reason, although the code is doing the job I am not sure if it is right to mutate the ref prop in the child component. My question is: If the prop I am passing to the child component is not a state in the parent, then is it okay to mutate it? Or in refs case does that work differently? Thanks in advance for any help!

React Unmount Base Component In Render-Prop Pattern

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>
}

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);
}

React State not available from Parent?

I have a form with a child component that renders as a table.
ParentComponent extends React {
state = {
anArray: []
}
<ParentComponent>
<table>
map ( thing => <ChildComponent {someFunction= this.updateFunction;} />
When ChildComponent maps the data to individual TD's. In my onChange in the ChildComponent, I'm invoking
onChange = this.props.someFunction();
and the code is hitting my breakpoint which is great. It calls someFunction in the ParentComponent. In someFunction, I'm trying to access the parent's state so I can match the onChanged TD with the proper index in the array but I'm getting undefined.
someFunction(id) {
const index = this.state.anArray.findIndex( x => x.id === id) ;
if (index === -1)
// handle error
console.log("DIDN'T FIND ID: " + id);
});
}
Why wouldn't I have access to state on the function invocation from the ChildComponent? I expected to be able to access it.
It's not clear from the posted code, but I guess you haven't bind the someFunction and you have the context of the child, instead of parent's.
ParentComponent extends React {
constructor(props){
super(props)
this.someFunction = this.someFunction.bind(this)
}
someFunction(){
...
}
render(){
...
}
}
If you have the necessary babel plugins you can even do
ParentComponent extends React {
someFunction = () => {
...
}
render(){
...
}
}

React - passing props to another component whenever component is updated

I have 2 components, one is Grid component where I show list of some data. Another one is "functional" component which contains logic like sorting, paging, etc which will be in every Grid component. Grid component can contain some own specific logic also.
My question is how I can pass props to functional component whenever something was changed in Grid component. Right now it only pass props for the first time Grid component is initialized, after that functional component works with old props which are nevew actualized.
Grid component is connected to redux store but I dont think it does matter here.
I am not sure if initializing class component via state is good approach, is there another way how to do it?
Code to grid component
type ProcessListProps =
ProcessState.IProcessesStateType
& typeof ProcessState.actionCreators
& RouteComponentProps<{}>;
type State = {
sharedFunctionality: SharedGridFunctions;
}
class ProcessList extends React.Component<ProcessListProps, State> {
constructor(props) {
super(props);
this.state = {
sharedFunctionality: new SharedGridFunctions(this.props)
}
}
componentDidMount() {
this.props.requestData(this.props.dataQuery);
}
componentDidUpdate(oldProps): void {
if ((oldProps.dataQuery.context !== this.props.dataQuery.context)) {
this.props.requestData(this.props.dataQuery);
}
if (oldProps.sort !== this.props.sort) {
this.state.sharedFunctionality.buildSortExpression();
}
}
..render - removed for brevity
}
Code for Functional component
interface ISharedGridFunctions extends IGridBase, IBaseFilterActionCreator, IBaseActionCreator {
}
export default class SharedGridFunctions extends React.PureComponent<ISharedGridFunctions,{}>{
constructor(props) {
super(props);
}
componentDidUpdate() {
console.log("shared component updated");
}
pageChange = (event) => {
this.setPaging(event.page.skip, event.page.take);
};
setPaging = (skip: number, take: number) => {
this.props.setPaging(take, skip);
};
sortChange = (event) => {
const dataQuery = this.props.dataQuery;
this.props.setOrder(dataQuery, event.sort);
};
//build sort expression in dataQuery object which is sent to api calls
buildSortExpression = () => {
let expression = "";
this.props.sort.map((sort) =>
expression += `${sort.field} ${sort.dir}, `
);
expression = expression.substring(0, expression.length - 2);
const dataQuery = this.props.dataQuery;
if (expression.length > 0) {
dataQuery.sort.orderBy = expression;
}
else {
dataQuery.sort.orderBy = undefined;
}
this.props.setOrder(dataQuery, this.props.sort, true)
}
removeByKey = (myObj, deleteKey) => {
return Object.keys(myObj)
.filter(key => key !== deleteKey)
.reduce((result, current) => {
result[current] = myObj[current];
return result;
}, {});
}
render() {
return null;
}
}

Resources