Life cycle of props with state in react - reactjs

I couldn't find any clear information or video about when the props are received in the react component life-cycle. Let's make an example:
export const App = () => {
const [count, setCount] = useState(0);
console.log(A, count)
return (
<Component count={count} justText={'hello'} onClick={() => setState(state => state + 1)}/>
);
}
const Component = ({count, justText}) => {
const [count2, setCount2]=useState(0)
console.log("B ",count)
console.log("C ",justText)
console.log("D",count2)
useEffect(()=> {
console.log("F", count)
console.log("G",count2)
});
return (
<>
{console.log(E,count)}
</>
)
}
I would like to know the execution order and why is that, because sometimes I found that when passing props with state down, the component which received them gets undefined but in the component where it is declared it is already initialized. Sometimes I need to wait the useEffect to get the value, so I am lost in what happens with the props during the lifecycle when mounting, and updating. I tried to find information about it but couldn't see anywhere where they explain the props lifecycle.
Thank you!
PD: Just setted random values for A,B,C...they don't follow any order.

You won't get undefined while console logging the props in the function or console logging the state :
import React from "react";
function Component() {
const [state, setState] = React.useState(false);
console.log(state);
return <Other state={state}></Other>;
}
export default Component;
import React from "react";
function Other({ state }) {
console.log(state);
React.useEffect(() => {
console.log(state);
}, []);
return <div>Other component</div>;
}
export default Other;
React components gets states and props before mounting so they will never be undefined when you want to use them .

TLDR
Lifecycle of a props starts when it gets mount and stays until it gets called or re-rendered by the parent that is using it. When it re-rendered by the parent it gets the props again but it can be unchanged or changed.
Full story
The lifecycle of a props is very straight forward. When your component is being called from a Parent it's props is also passed at that time. Think props as the function parameter. Suppose you have a function like below
const fn = (param1, param2) => {
// your function implementation
}
Here when you call the function you call it with the params like this
fn(val1, val2)
So each time you call the function you pass the params. If any time calling the function any of the param is not passed it will get null
A component and it's props is also same. Here the Component is the function and the props are the params. Every time the components gets called or rendered it is called or rendered with the props.
Now, If your parent that is using the component, pass all the props then the component will get the value. But if it doesn't pass all the props it will get null.
Like function you can also define the initial value of a props like this
const Component1 = ({props1='val1', props2='val2', ...rest}) => {
return()
}
In the code above even if you do not pass props1 and props2 from the parent. Still it will not be null, it will have the initial value.

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.

How to make functional component rerender properly on parents state chage?

please help me figure out why the component works like that.
I have a functional component something like this:
function MyComponent(props) {
const { dataArr } = props;
[ownDataArr, setOwnDataArr] = useState([...dataArr]);
// doesn't change after changing in the state of the parent component
console.log(ownDataArr);
return (
// ownDataArr is used here
);
It receives dataArr from parent component via props (parent component contains this in state). And when changed in parent component after MyComponent rerenders, ownDataArr stays the same. What am I doing wrong?
P.S. The child component needs the state, since it must be able to change the received data without constantly sending it to the parent.
You can do this to update the state on props change
useEffect(() => {
setOwnDataArr(dataArr)
}, [dataArr])
This is because state initialize on the first render of component with the props and when the props change, we have to update the state using useEffect
function MyComponent(props) {
const { dataArr } = props;
[ownDataArr, setOwnDataArr] = useState([...dataArr]);
// add useEffect wich depend on dataArr changing,
// because initial state is memoized by useState
useEffect(() => setOwnDataArr(dataArr), [dataArr])
return (
// ownDataArr is used here
);
}

Child re-rendering because of function?

I'm having trouble navigating these concepts where a child keeps re-rendering because I'm passing it a function from the parent. This parent function references an editor's value, draftjs.
function Parent() {
const [doSomethingValue, setDoSomethingValue] = React.useState("");
const [editorState, setEditorState] = React.useState(
EditorState.createEmpty()
);
const editorRef = useRef<HTMLInputElement>(null);
const doSomething = () => {
// get draftjs editor current value and make a fetch call
let userResponse = editorState.getCurrentContent().getPlainText("\u0001");
// do something with userResponse
setDoSomethingValue(someValue);
}
return (
<React.Fragment>
<Child doSomething={doSomething} />
<Editor
ref={editorRef}
editorState={editorState}
onChange={setEditorState}
placeholder="Start writing..." />
<AnotherChild doSomethingValue={doSomethingValue}
<React.Fragment>
}
}
My Child component is simply a button that calls the parent's doSomething and thats it.
doSomething does its thing and then makes a change to the state which is then passed to AnotherChild.
My problem is that anytime the editorState is updated (which is every time you type within the editor), my Child component re-renders. Isn't that unnecessary? And if so, how could I avoid this?
If I was passing my Child component a string and leveraged React.Memo, it does not re-render unless the string changes.
So what am I missing with passing a function to the child? Should my child be re-rendering everytime?
React works on reference change detection to re-render components.
Child.js: Wrap it under React.memo so it becomes Pure Component.
const Child = ({doSomething}) => <button onClick={doSomething}>Child Button Name</button>;
export default React.memo(Child);
Parent.js -> doSomething: On every (re)render, callbacks are also recreated. Make use of useCallback so that your function is not recreated on every render.
const doSomething = React.useCallback(() => {
let userResponse = editorState.getCurrentContent().getPlainText("\u0001");
setDoSomethingValue(someValue);
}, [editorState]);
Side Note
On broader lines, memo is HOC and makes a component Pure Component. useMemo is something which cache the output of the function. Whereas useCallback caches the instance of the function.
Hope it helps.
If you do not want your component to be re-rendered every time your parent is re-rendered, you should take a look at useMemo.
This function will only recalculate its value (in your case, your component) whenever its second argument changes (here, the only thing it depends on, doSomething()).
function Parent() {
const [editorState, setEditorState] = React.useState(
EditorState.createEmpty()
);
const editorRef = useRef<HTMLInputElement>(null);
const doSomething = () => {
// get draftjs editor current value and make a fetch call
let userResponse = editorState.getCurrentContent().getPlainText("\u0001");
// do something with userResponse
}
const childComp = useMemo(() => <Child doSomething={doSomething} />, [doSomething])
return (
<React.Fragment>
{childComp}
<Editor
ref={editorRef}
editorState={editorState}
onChange={setEditorState}
placeholder="Start writing..." />
<React.Fragment>
}
}
If doSomething does not change, your component does not re-render.
You may also want to use useCallback for your function if it is doing heavy calculations, to avoid having it re-compiled every time your component renders: https://reactjs.org/docs/hooks-reference.html#usecallback
Take a look into PureComponent, useMemo, or shouldComponentUpdate
I would also add, instead of passing down the function to do the rendering of a top level component, pass the value and define the function later down in the component tree.
if you want to avoid unnecessary re-renders, you can use React.memo and the hook useCallback. Take a look at the following sandbox.
The button1 is always re-rendered because it take a callback that is not memoized with useCallback and the button2 is just rendered the first time even if the state of the parent has changed (take a look at the console to check the re-renders). You have to use React.memo in the Child component that is the responsible to render the buttons.
I hope it helps.

subcomponent is re-rendering on prop change, but prop is a function

I am using react 16.12.0 with functional components and typescript 3.7.2.
Problem:
My subcomponent B is re-rendering every time when a variable is changed in component A, although these variables are not passed to the subcomponentB. I am only passing a function from componentA to componentB.
I have two components (Component A and component B). Component A is the parent of component B.
Component A is holding one variable and a function. The function is delivered to component B but NOT the value. In component A there is also a timer running, changing the variable "value" in X-seconds.
Component A:
const [value, setValue] = React.useState<number>(1);
function timer(): void {
//Running all 8 seconds
setValue(value => value + 1);
}
function doSomethingInComponentA(): void {
//some logic, like send some data to the server
//or change the value
}
Now the subcomponent B:
interface Props {
doSomethingInComponentA: Function;
}
const ComponentB: React.FC<Props> = (props) => {
useEffect(() => {
//every time when the timer changes the value in component A useEffect will be fired.
console.log("Has something changed?);
});
function doSomething(): void {
props.doSomethingInComponentA();
}
return (
<>
//ERROR - THIS COMPONENT WILL BE RE-RENDERER EVERY TIME WHEN THE TIMER IS CHANGING THE VALUE
Button onClick=doSomething() CLICK ME;
</>
);
};
export default ComponentB;
Now when the timer-function is changing the variable value, a rerendering is fired in the subcomponentB, although only the function doSomethingInComponentA is passed to the subcomponent.
Why do we have this behavior? In my understand the re-rendering is only fired when the variable-value would be passed to subcomponentB. The function is never changing, it is a function, not a variable.
Hopefully I could explain it correctly.
Your question is very confusing but try using useCallback in the function your are passing to the child component.
const yourFunction = React.useCallback(
() => {
// your function body
},
[a, b], // only the values that will change and make yourFunction change
);
The function is never changing, it is a function, not a variable
Because your parent component is a functional component, the function will be recreated on every parent render, which will make the child component render.

Does React renders Component due to props?

According to that link: http://projects.wojtekmaj.pl/react-lifecycle-methods-diagram/
render() may be triggered with new props. Could someone give me a code example for that? I cannot see how props change invoke rendering! Please not by changing the props via the state; then it is setState() that invokes render()...
Look at shouldComponentUpdate() - this is it's signature - it returns a boolean. Props is there so you can compare and manually say whether the component should update.
shouldComponentUpdate(nextProps, nextState)
For function components React.memo is the replacement for shouldComponentUpdate which is used with class components.
const myComponent = React.memo(props => {
...
code of React.FunctionComponent
...
},
(prevProps, nextProps) => prevProps.propA === nextProps.propA
);
React.memo gets two arguments shown above: a React.FunctionComponent which will be wrapped around by memo and an optional function that returns a boolean.
When the function returns true, the component will not be re-rendered. If the function is omitted then its default implementation in React.memo works like the implementation of shouldComponentUpdate in React.PureComponent. E.g. it does shallow comparison of props, the difference is that only props are taken into account because state doesn’t exist for functional components.
Using hooks is neater to show. The new props data passed to ComponentB causes a re-rendering of ComponentB:
import React, { useState } from 'react'
import ComponentB from '...'
const ComponentA = props => {
const [data, setData] = useState(0) // data = 0
handleChangeProp = item => setData(item) // data = 1
return(
<div>
<button onClick{() => handleChangeProp(1)}
<ComponentB props={data} />
</div>
)
}
Yes, when you do a setState(newState) or when you pass in changed props the component will re render, this is why you can't mutate. The following will not work because you set state with mutated state.
export default function Parent() {
const [c, setC] = useState({ c: 0 });
console.log('in render:', c);
return (
<div>
<button
onClick={() =>
setC(state => {
state.c++;
console.log('state is:', state);
return state;
})
}
>
+
</button>
<Child c={c.c} />
</div>
);
}
That code "won't work" because pressing + will not cause a re render, you mutated state and then set state with the same object reference so React doesn't know you changed anything.
This is how React detects changes, you may think that comparing {c:0} to {c:1} is a change but because you mutated there actually is no change:
const a = {c:1};
a.c++;//you "changed" a but a still is a
To indicate a change in React you have to create a new reference:
const a = {c:1};
const b = {...a};//b is shallow copy of a
a===b;//this is false, even though both a and b have same internal values
This means you can also have unintended renders because you create an object prop that may have the same value but still is a different reference than the last time you created it.
Note that even <Child prop={1} will cause Child to render if Child is not a pure component (see links at the end).
What you want to avoid is doing <Child prop={{c:value}} because every time you pass prop it'll force Child to render and React to do a virtual DOM compare even if value didn't change. The virtual DOM compare will probably still detect that Child virtual DOM is the same as last time and won't do an actual DOM update.
The most expensive thing you can do is <Child onEvent={()=>someAction(value)}. This is because now the virtual DOM compare will fail even if value and someAction did't change. That's because you create a new function every time.
Usually you want to memoize creating props in a container, here is an example of doing this with react-redux hooks. Here is an example with stateful components passing handlers.

Resources