useEffect of children component called before useEffect of parent - reactjs

I am trying to understand why the useEffect of a children component gets called before the Parent component useEffect.
From my understanding, useEffect shoulde be called in the order they are defined based on React's documentation:
React will apply every effect used by the component, in the order they were specified.
This would mean, a Parent's useEffect should be called before a Children's useEffect, but this is not the case.
Example:
const MainComponent = () => {
return {
<ParentComponent />
}
const ParentComponent = () => {
useEffect(() => {
console.log('parent');
}, []);
return <div>Parent <ChildrenComponent /></div>;
}
const ChildrenComponent = () => {
useEffect(() => {
console.log('children');
}, []);
return <div>Children</div>;
}
If you check the console, you should see first children and then parent
Live Code: https://codesandbox.io/s/crazy-butterfly-yn046?file=/src/App.js
My gut tells me this has to do with how react does Layout and Paint of the Parent-Children components?

This:
React will apply every effect used by the component, in the order they were specified.
Would be more precisely stated as:
React will apply every effect used by the component, in the order they were specified in that component.
For example:
const SomeComponent = () => {
useEffect(() => {
console.log('This will run first');
});
useEffect(() => {
console.log('This will run second');
});
// ...
is guaranteed to run in order.
It's not saying anything about the order that effects in different components run.

Related

When does useEffect call when I use it with useContext?

I call useEffect inside useContext and I want to know when this useEffect is called.
[settingContext.tsx]
// create context object
export const ColorContext = createContext<ColorContextType>(null);
export const ProductsProvider = (props) => {
const { query } = useRouter();
const [data, setData] = useState<ColorContextType>(null);
useEffect(() => {
async function fetchAPI() {
const res = await fetch(`${env.API_URL_FOR_CLIENT}/page_settings/top`);
const posts = await res.json();
setData(posts);
}
fetchAPI();
}, []);
return <ColorContext.Provider value={data}>{props.children}</ColorContext.Provider>;
};
export const useColorContext = () => {
const colors = useContext(ColorContext);
let themeColor: string = '';
let titleColor: string = '';
if (colors !== null) {
const colorData = colors.response.result_list[3].value;
themeColor = JSON.parse(colorData).theme_color;
titleColor = JSON.parse(colorData).title_color;
}
return { themeColor, titleColor };
};
[_app.tsx]
export default function MyApp({ Component, pageProps }: AppProps) {
return (
<LayoutInitial>
<ProductsProvider>
<Component {...pageProps} />
</ProductsProvider>
</LayoutInitial>
);
}
I use useColorContext on multiple components.
It seems like useEffect is only called on '/' page, which is fine but I'm curious that useEffect should be called every time pages are rendered but it seems that it doesn't.
is this because I use useContext?
The useEffect call is done in the ProductsProvider component which appears to only be rendered once, on page load/refresh. This is because components generally only re-render when a state they subscribe to is changed. If the useEffect were called directly within the <Component> component, it would be called every time the component is mounted (not in re-renders). useEffect is only called multiple times after mounting if one of its dependencies changes, which in your case, there are none.
For example: this sandbox
It's composed of the App component, containing a Router to a homepage, a ComponentA route, and a ComponentB route. When each component mounts, its useEffect is called, creating an alert box. You'll only see the App useEffect alert once per page refresh.
ComponentA will have its useEffect called when the component mounts (every time you hit the /a route from a different route), and when the state in the component changes, since it's in the useEffect dependency array.
ComponentB will only have its useEffect called when the component mounts, and not when its state changes, because the state isn't included in the useEffect dependency array.
EDIT: To clarify, your useColorContext hook is not actually part of the ProductsProvider component, so the useEffect call is not "inherited" by any components that call the hook. Also, keep in mind when experimenting that using Strict Mode will cause components to render twice, allowing react to gather information on the first render, and display it on the second render.

Callback in props of React component - correct way to fire

Suppose I have a component which is like
function Child(props: { onSelect: () => void }) {
...
useEffect(() => {
// want to fire onSelect here
}, [...]);
...
}
Since props.onSelect might change every render (e.g. arrow function), I can't add it to the dependency list of useEffect and call it directly. I used a reducer instead:
const [, dispatch] = useReducer((state: undefined, action: T) => {
props.onSelect(action);
return undefined;
}, undefined);
useEffect(() => {
dispatch(...);
}, [...]);
But now I get the error "Warning: Cannot update a component (Parent) while rendering a different component (Child)."
What's the correct way to fire the parent's onSelect inside some useEffect?
You mention
Since props.onSelect might change every render (e.g. arrow function), I can't add it to the dependency list of useEffect and call it directly
You can, but you should make sure that it does not change if there is no reason.
You should use a useCallback for it on the parent component, so that it remains the same.
function Parent (){
...
const onSelect = useCallback(() => {
// set local state here
}, []);
...
return ... <Child onSelect={onSelect} />
}

functional component rerender on state change

I've implemented a window resize event on initial component load. The event detects the window inner width and saves the value in a hook. Based on the width hook there is a second useEffect function, triggered on width change:
export const AppRouter = (props) => {
const [width, setWidth] = useState(window.innerWidth);
useEffect(() => {
const handleResize = () => setWidth(window.innerWidth);
window.addEventListener('resize', handleResize);
return () => {
window.removeEventListener('resize', handleResize);
};
}, []);
useEffect(() => {
setAppLayoutForViewportWidth();
}, [width]);
}
Now to the unexpected behavior: the entire component rerenders on width hook change and not only the useEffect based on the width hook.
Can someone name the reason, why the entire component rerenders? Can I only rerender the width-based useEffect?
Why?
setState({}) always forces to re-render. (unless you return false in: shouldComponentUpdate(nextProps, nextState)) You can check this by putting a console log in
componentDidUpdate(prevProps, prevState) {
console.log("Component did update")
}
Your setWidth(window.innerWidth);will change state due to it being: useState(window.innerWidth); which will force a rerender.
How to prevent it:
If you want to control when it rerenders, create a logic in shouldComponentUpdate to return false when you want to prevent rerender.
If you are in a functional component have a look at React.Memo. More on that in this thread: How can I prevent my functional component from re-rendering with React memo or React hooks?
React.Memo functions similarly to pure component. However, you can
also adjust its behavior by passing it a function which defines what
counts as equal. Basically, this function is shouldComponentUpdate,
except you return true if you want it to not render.
const areEqual = (prevProps, nextProps) => true;
const MyComponent = React.memo(props => {
return /*whatever jsx you like */
}, areEqual);

Unmount cycle behavior in react hooks

I have parent and child components like below:
Parent
import React, { useEffect, useState } from "react";
import Cmp1 from "./Cmp1";
const App = () => {
console.log("render App");
const [parentState, setParentState] = useState(null);
const updateParent = state => {
setParentState(state);
};
useEffect(() => {
console.log("mount App");
}, []);
useEffect(() => {
console.log("update App");
return () => {
console.log("unmount App");
};
}, [parentState]);
return (
<div>
<h1>{parentState}</h1>
<Cmp1 updateParent={updateParent} />
</div>
);
};
export default App;
Child
import React, { useEffect } from "react";
const Cmp1 = ({ updateParent }) => {
console.log('render Cmp1')
useEffect(() => {
console.log("mount Cmp1");
updateParent('From Cmp1')
}, []);
return <h1>Cmp1</h1>;
};
export default Cmp1;
When rendering these two components, I added some life cycle hooks and here is the output
render App
render Cmp1
mount Cmp1
mount App
update App
render App
render Cmp1
unmount App // <= no idea why this came out
update App
Here is the codesanbox.
I didn't unmount the component and just did a simple state update from child to parent, I was not expecting that line and I have no idea why this unmount cycle is running here.
Since I'm new to react hooks, could someone please explain this?
Your hook is dependent on parentState, so the returned function is not only running on component unmount. Its a normal clean up function that gets called every time before the hook is called again and again on unmount.
To log when the component unmounts, you need to use an effect with [] dependencies.
useEffect(() => {
return () => {
console.log('unmount');
}
}, [])
From the docs (my bold):
When exactly does React clean up an effect? React performs the cleanup when the component unmounts. However, as we learned earlier, effects run for every render and not just once. This is why React also cleans up effects from the previous render before running the effects next time. We’ll discuss why this helps avoid bugs and how to opt out of this behavior in case it creates performance issues later below.

How to specify a constructor with a functional component (fat arrow syntax)?

Given this component:
import React from 'react'
import ReactDOM from 'react-dom'
import PropTypes from 'prop-types'
const NewGoalInput = props => {
return (
<input type="text" onKeyUp={handleKeyUp}/>
)
}
const handleKeyUp = (e) => {
if (e.key === "Enter") {
// TODO Add goal
}
}
export default NewGoalInput
How do I add a constructor where I can define the state without using the extends React.Component syntax?
Since it's a stateless component it doesn't have the component lifecycle.
Therefor you can't specify a constructor.
You have to extend React.Component to create a stateful component which then will need a constructor and you'll be able to use the state.
Update
Since React 16.8.0 and Hooks got introduced there are more options.
Hooks are a new feature proposal that lets you use state and other React > features without writing a class. They are released in React as a part of > v16.8.0
Stateless:
import React from "react"
const Stateless = ({name}) => (
<div>{`Hi ${name}`}</div>
);
Stateful:
Has access to component lifecycle methods and local state.
class Stateful extends React.Component {
constructor(props) {
super(props);
this.state = {
count: 0
};
}
componentDidMount() {
const { count } = this.state;
document.title = `You've clicked ${count} times.`;
}
componentDidUpdate() {
const { count } = this.state;
document.title = `You've clicked ${count} times.`;
}
render() {
const { count } = this.state;
return (
<div>
<p>You've clicked {count} times.</p>
<button onClick={() => this.setState({ count: count + 1 })}>
Click me
</button>
</div>
);
}
}
Using Hooks:
Able to use State Hook and Effect Hook.
If you’re familiar with React class lifecycle methods, you can think of useEffect Hook as componentDidMount, componentDidUpdate, and componentWillUnmount combined.
import React, { useState, useEffect } from "react";
const UsingHooks = () => {
const [count, setCount] = useState(0);
// Similar to componentDidMount and componentDidUpdate:
useEffect(() => {
// Update the document title using the browser API
document.title = `You've clicked ${count} times.`;
});
return (
// <> is a short syntax for <React.Fragment> and can be used instead of a wrapping div
<>
<p>You've clicked {count} times.</p>
<button onClick={() => setCount(count + 1)}>
Click me
</button>
</>
);
}
Now that we have useState and hooks the answers are kind of out of date. I came across this question because I was doing something wrong. Here's some simplified code of what I was doing.
// set an initial state
const [ value, setValue ] = useState(0)
// gets called after component is re-rendered
useEffect(() => {
// callback to parent that set props
props.update()
})
// if we have an existing value passed in
if (props.value) {
setValue(props.value)
}
This code was converted from a stateful class to a function using hooks, originally setting the default props in the constructor - but functions don't have constructors and that check happens every time the component re-renders:
calls useState
triggers re-render
useEffect is triggerd
parent is called which sets the props
props update so child renders again
GOTO 1
As you can see this results in an infinite loop. The solution is really quite simple. Here's a mock diff from the original.
- const [ value, setValue ] = useState(0)
+ const [ value, setValue ] = useState(props.value || 0)
- if (props.value) {
- setValue(props.value)
- }
Basically, just initialise the state from the props and don't do silly things like calling useState except in response to an event or callback of some type.
You can use useMemo hook (as below) to demonstrate as constructor for functional component. Somebody suggested to use useEffect but it will be invoked after render.
useMemo(() => {
console.log('This is useMemo')
}, []);
you could set a useState as the first line inside of your functional component and add a function as "initial value":
const MyComponentName = props => {
useState(() => {
console.log('this will run the first time the component renders!');
});
return <div>my component!</div>;
};
You don't. The kind of component in your example is called "stateless functional component". It has no state and no lifecycle methods. If you want your component to be stateful you'll have to write it as a class component.
To simulate constructor in FC use useEffect.
useEffect(() => {
... here your init code
}, []);
That's it! EZ! This useEffect runs only once when the component loads and never runs after, just don't forget to add square brackets at the end.
For those who want to run a function once before the component is mounted, here is a hook (written in TypeScript).
Normally useEffect and useLayoutEffect suffice, but they run after the component is mounted, and sometimes you want to run code before that happens (like a constructor).
import React, { useRef } from "react";
function useOnce<Type>(callBack: () => Type): Type {
const result = useRef<Type | null>(null);
if (result.current !== null) {
return result.current;
}
result.current = callBack();
return result.current;
}
const Component: React.FC<{}> = () => {
const result = useOnce(() => {/* Code you would normally put in a constructor */});
return <div />
}
Alternatively, you can use react-afc
import { afc, reactive } from 'react-afc'
function heavyCalc() {/*...*/}
const Conponent = afc(props => {
// Called once, before the first render
const state = reactive({
name: 'Stack',
inputsCount: 0
})
// Without useMemo(..., [])
const result = heavyCalc()
// The function is created once and does not cause
// a re-render of child components
function onInput(e) {
state.inputsCount++
state.name = e.currentTarget.value
}
// Saved between renders (no longer need useRef)
let rendersCount = 0
// Must return the render-function
return () => {
// The function works like a regular react-component
// Here you can use the usual hooks
rendersCount++
return (
<input onChange={onInput} value={state.name}/>
)
}
})
The package has the necessary methods for working with state (including redux), react-hooks, lifecycle methods and context

Resources