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

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.

Related

Life cycle of props with state in react

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.

React useCallback() function in Parent is only working fine with Child if React.memo is used, without it not working

Parent Component
The parent component contains input which on change sets the state "local" and a button which on click takes this "local" state's value and sets it to "sendLocal"
Functions
changehandler : triggers on input type change.
sendLocalValue : takes "local" value puts it into "sendLocal" variable triggers on button click.
sendValue : this memoized function with dependeny "sendLocal" is passed on as a prop in child component triggers once the child is rendered.
import React, { useState, useCallback } from "react";
import ChildComponent from "./ChildComponent";
function ParentComponent() {
const [local, setLocal] = useState();
const [sendLocal, setsendLocal] = useState();
const changehandler = (e) => {
console.log("parent");
setLocal(e.target.value);
};
const sendLocalValue = () => {
setsendLocal(local);
};
const sendValue = useCallback(() => {
return sendLocal;
}, [sendLocal]);
return (
<>
<input type="text" onChange={changehandler} defaultValue={local} />
<button onClick={sendLocalValue}>send</button>
<ChildComponent getValue={sendValue} />
</>
);
}
export default ParentComponent;
Child Component
getValue prop calls the memoized "sendValue" function of parent which returns the value of sendLocal.
Problem
Everything works fine,the child component renders only when the "sendLocal" value changes on button click but if i remove React.memo() in child both the component render on input type change even with useCallback() used, why?
import React, { useEffect, useState } from "react";
function ChildComponent({ getValue }) {
console.log("child");
return <div>child's {getValue()}</div>;
}
export default React.memo(ChildComponent);
There is a general misconception that React components rerender when props or state change. while this is partially true, can lead to misunderstoods: a react component rerenders when its state changes or when its parent rerenders (because its state changed or because its parent rerendered, and so on).
So this means that every time ParentComponent rerenders, all its (non memoized) children will rerender.
To avoid this, you can use React.memo or React.PureComponent.
You can verify that by removing React.memo and not passing any props to the ChildComponent. Even with no props, it will rerender every time its parent rerenders.
when the parent re-renders, the whole props object for the child is created as a new object, which leads to the child re-render.
it does not matter that you have memoized all the different individual props, you have to memoize the component itself so that it does not re-render when the whole props object changes.
In this case, it also means that you still have to memoize the function as well as the child component, because React.memo only does a shallow comparison of individual props.

React: How to pass state to child component and call function in child to use this state

I am implementing a component in functional components where there are several other child components in it passing data to each other. I need to pass data from parent to child component and call some function there to use it.
In class componenets we use componentdidupdate but could not understand how to do in functional component.
One idea is to use useEffect hook but could not do with it.
Im going to take a stab here, because we dont have context or code to go with.
useEffect accepts a dependency array to which it will react when a value or object reference changes
const ChildComponent = (props) => {
const {
valuePassedFromParent
} = props;
const actionFunction = (value) => {
//perform some tasks with value passed from parent when it changes
}
//this is similar to componentDidUpdate, but it reacts to changes in valuePassedFromParent though props since its in the useEffect dependency array
useEffect(() => {
//do something with valuePassedFromParent
actionFunction(valuePassedFromParent);
},[valuePassedFromParent]);
return (
<div>
</div>
)
}
You cas use useEffect to reproduce the behavior of componentdidupdate like this :
const [myState,setMyState] = useState();
useEffect(() => {
...
},[myState]);
The function use effect will run every time myState will be updated, like componentdiduptate would have do.
In your case, the state is given by the parent component if I understand well, so just replace the myState in the array dependency by the state given through the prop of your child component.

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

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