Why React.Memo() keeps rendering my component - reactjs

I made an example to get to know more deeply the React.Memo().
I tried to pass a string from the parent to the child. then the component wasn't rendred except the first default rerending.
But When I passed an array from the parent to the child the components get re-render.
So why the component keeps re-render when passing arrays and objects and not re-render when passing string.
Is that related to the reference values and primitive values of javascript?
here's the code for what was I trying to do.
the parent:
import Child from "./Child";
export default function App() {
const name = {
firstName: "Amr"
};
return <Child name={name} />;
}
The Child
import React from "react";
const Child = ({ name }) => {
const name1 = "Amr";
const name2 = "Amr";
// console.log(name1 === name2);
const obj1 = {
name: "Mohamed"
};
const obj2 = {
name: "Mohamed"
};
const arr1 = ["Amr"];
const arr2 = ["Amr"];
console.log(arr1 === arr2);
// console.log(obj1 === obj2); => obj1 => refrence number 1
// obj2 => refrence number 2
// 1 === 2;
// false
areEqual(name, {
firstName: "Amr"
});
return <h2>{name.firstName}</h2>;
};
// javascript [] {} => refrence value
// javascript '' or anythinng primtive value;
// obj1 = 1, obj2 = 2;
// obj1 === obj2 result false 1 === 2
function areEqual(prevProps, nextPops) {
if (prevProps === nextPops) {
console.log("equal");
} else {
console.log("not equal");
}
}
export default React.memo(Child, areEqual);
I used a function also areEqual that returns true or false based on if the prevProps and nextProps are equal or not.
This function already returns for me true when I pass a String and false when I pass an array.
So, am I right or there's something missing.
Thanks
UPDATE:
If what I mentioned is right, so the React.memo() will be useless.
Is that Right?

By default React.memo() does a shallow comparison of props and objects of props - and this is why you were experiencing rerenders when props were of type 'object', because reference recreates on each rerender.
React.memo receives two argument(2nd one is optional), and second one is custom equality function which you can use in order to stabilize rerenders when having props as non primitive values. So, for example, you can do something like this: React.memo(SomeComponent, (prevProps, currentProps) => isDeepEqual(prevProps.someObject, currentProps.someObject)).
Meaning that React.memo is not useless, it just uses shallow comparison by default, but also offers you posibillity to provide it with custom equality checker which you might wanna use in your example. Either you shall use deep equality check within React.memo, or you shall stabilize props in parent component by wrapping their value with useMemo.

It depends on their parent components some times it is necessary React.memo
But in your case, as you said, its reference changes every time you define an array or object. I recommend you to use React.useMemo. For example:
const user = React.useMemo(() => {
return {
firstName: "Amr"
}
}, []); // Second parameter works same as useCallback
React.memo wont re-render the component because user props didn't change

Related

How to control when a component renders? React js for adding a comment to a post

https://codesandbox.io/s/busy-paper-w39kvw?file=/src/components/Comments.js:141-200
const initialState = {
comments: [
{
id: 1,
username: "Kevin",
date: "3 hours ago",
text: "Hello",
votes: 12,
upvoted: false,
downvoted: false,
comments: []
}
]
}
In my comments code I have a useSelector
const { comments } = useSelector(state => state.comments)
this renders all the comments everytime a new comment is added
as shown here using react dev tools add on to highlight when a component renders: https://i.gyazo.com/43a93b6d07a5802d91d9f68684e5ded5.mp4
I tried to use React.memo to memoize the comment but im not sure why that didn't work. Is there anyway to stop rendering all the comments when a comment gets added?
When a component is wrapped in React.memo(), React renders the component and memoizes the result. Before the next render, if the new props are the same, React reuses the memoized result skipping the next rendering.
In your code below, you are passing the allComments function to the comment component.
const allComments = (comments, bg) => {
const output = comments.map((comment) => {
return (
<Comment
key={comment.id}
comment={comment}
allComments={allComments} // the function get's passed as new function object on each rerender
bg={bg}
/>
);
});
return output;
};
what is the problem then and why this behavior?
because of the function equality check, functions in javascript are treated as firstclass citizens, in other word, functions are objects.
function factory() {
return (a, b) => a + b;
}
const sum1 = factory();
const sum2 = factory();
sum1(1, 2); // => 3
sum2(1, 2); // => 3
sum1 === sum2; // => false
sum1 === sum1; // => true
The functions sum1 and sum2 share the same code source but they are different function objects. Comparing them sum1 === sum2 evaluates to false. this is how Javascript works.
In your code, a new function object allComments gets created in each render by react, which is eventually gets passed as a new prop to the React.memo(). By default React.memo() does a shallow comparison of props and objects of props. which is why it triggers the a new rerender.
Now we understand deeply what was the problem and what causes this behavior.
The solution is to wrap your allComments with useCallback
What is the purpose of useCallback? it maintain a single function instance between renderings. and thus React.memo() will work.
const allComments = useCallback((comments, bg) => {
const output = comments.map((comment) => {
return (
<Comment
key={comment.id}
comment={comment}
allComments={allComments}
bg={bg}
/>
);
});
return output;
},[]);

Making the state of a component affect the rendering of a sibling when components are rendered iteratively

I have the following code:
export default function Parent() {
const children1 = someArrayWithSeveralElements.map(foo => <SomeView />);
const children2 = someArrayWithSeveralElements.map(foo => <SomeCheckbox />);
return (<>
{children1}
{/*Some other components*/}
{children2}
</>)
};
For a given element foo, there is a SomeView component that is conditionally rendered based on the state of a SomeCheckbox. I'm having trouble figuring out a way to have the state from the checkbox affect the rendering of the sibling view component.
Normally the solution would be to just declare the state hook in the parent component and pass them down to each child, but since the siblings are rendered via foreach loops it's impossible to do so.
My current solution is to also generate the state hooks for each foo in a loop as well, but that feels a bit hacky since it's better to avoid creating hooks inside of loops (it's worth nothing that someArrayWithSeveralElements is not intended to change after mounting).
Is there a more elegant alternative to solve this?
The solution is what you side, you need to create a state in the parent component and pass it to the children. and this will work for single component or bunch of them, the difference is just simple: use array or object as state.
const [checkboxesStatus, setCheckboxesStatus] = useState({// fill initial data});
const children1 = someArrayWithSeveralElements.map(foo =>
<SomeView
visibile={checkBoxesStatus[foo.id]}
/>);
const children2 = someArrayWithSeveralElements.map(foo =>
<SomeCheckbox
checked={checkBoxesStatus[foo.id]}
onChange={// set new value to foo.id key}
/>)
export default function Parent() {
const [states, setStates] = React.useState([]);
const children1 = someArrayWithSeveralElements.map((foo, i) => <SomeView state={states[i]} />);
const children2 = someArrayWithSeveralElements.map((foo, i) => {
const onStateChange = (state) => {
setStates(oldStates => {
const newStates = [...(oldStates || [])]
newStates[i] = state;
return newStates;
})
}
return <SomeCheckbox state={states[i]} onStateChange={onStateChange} />;
});
return (<>
{children1}
{/*Some other components*/}
{children2}
</>)
};
Use states in the parent componet.
Note: the element of states may be undefined.

How to get the closest previous different prop in ReactJS

I have a functional component with props:
function MyComponent({propA, propB, propC}) {
...
}
This component is rendered few times with different props values. I need to get the closest previous prop value which is not equal to current value. For example:
First render. propA is 1. previous value must be undefined
Second render. propA is 2. previous value must be 1
Third render. propA is 2. previous value must be 1 (not 2, because
current value is 2)
There is a similar "usePrevious" hook:
export const usePrevious = <T>(value: T): T | undefined => {
const ref = useRef<T>();
useEffect(() => {
ref.current = value;
});
return ref.current;
};
But it returns any previous value even if it has not changed. And it looks like I should modify this hook, but I don't know how
code example: https://codesandbox.io/s/cranky-snow-mkile?file=/src/index.js (props values are logged in console)
Try this:
const usePrevious = newValue => {
const refPrev = useRef()
const refCurr = useRef()
if (newValue !== refCurr.current) {
refPrev.current = refCurr.current;
refCurr.current = newValue;
}
return refPrev.current;
}

React useState is not keeping the changes

Current Situation
I am improving performance of the component called ProductMain using React.memo(). The ProductMain is containing ProductComponent that is list. And I am passing every single product information such as name and qty also handleChangeQty method to the ProductComponent as a props. To render ProductComponent separately, I am using React.memo with custom comparator method areEqual
const areEqual = (prevProps, nextProps) => {
const prevLocation = prevProps.product.locations
const nextLocation = nextProps.product.locations
let isEqual = true
if(prevLocation.length !== nextLocation.length) return false
else
for (let i = 0; i < nextLocation.length; i++) {
if (nextLocation[i].adjustedQty !== prevLocation[i].adjustedQty)
isEqual = false
}
return isEqual
}
const ProductComponent: React.FC<Props> = ({ product, productIndex, onQtyChange, onFocusChange }) => {
const handleQtyChange = newValue => {
onQtyChange(locationIndex, location.onHandQty, newValue)
}
return(<React.Fragment>
//some button that can change the qty 'onClick'={handleQtyChange}
</React.Fragment>)
}
export default React.memo(ProductComponent, areEqual)
From the ProductComponent when I change the qty, handleChangeQty function will be working in the ProductMain. ProductMain has a useState hook visibleProduct. Every time handleQtyChange method works, I am updating the visibleProducts using setter function.
Problem is
In the areEqual method, I am comparing previuos props with next props. If these are identical, function returning true and ProductComponent won't be re-rendering. And Its working as expected, re-rendering only changed component. But visibleProducts is not consistently keeping the changes. For example at the very beginning
`visibleProduct = [{productId: 001, qty:0},{productId: 002, qty: 0}]`
After the handleQtyChange works, visibleProduct state changed to
`visibleProduct = [{productId: 001, qty:1},{productId: 002, qty: 0}]`
then I again, I changed the
`visibleProduct = [{productId: 002, qty: 1}]`
the previous value set back to initial value like {productId: 001, qty:0},{productId: 002, qty: 1}
It causes the re-rendering in the child component, and not keeping the previous changes.
Hope you understand well.
I tried to explain more detailed. Sorry for my language.

How to write Test cases for useEffect Hook in React using Jest & Enzyme?

import React, { useEffect, useState } from 'react';
import PropTypes from 'prop-types';
const InputComponent = ({ item, data }) => {
const [value, setValue] = useState('');
// Binding values for Edit based on unique Key
useEffect(() => {
if (data && data[item.field] && data[item.field] !== 'undefined') {
setValue(data[item.field]);
}
}, [data,item.field]);
//On change setting state
const setState = e => {
setValue(e.target.value);
};
return (
<div className='Input-Containter' data-test='InputBox-tbl-evt'>
<input
data-test='input-ele'
type='text'
value={value}
onChange={e => setState(e)}
/>
</div>
);
};
InputComponent.propTypes = {
item: PropTypes.object,
data: PropTypes.object
};
InputComponent.defaultProps = {
data: {
id: '1',
name: 'd'
},
item:{
field: 'id',
}
};
export default InputComponent;
Can someone help me How to test for setValue() in useEffect
-> Updated complete code for this Component
-> Component will take some data for binding values into input element & in the same
component we can edit values in it as-well.
First, let's take closer look onto useEffect section. What does it mean?
if any of prop is changed
and combination of new values are given some meaningful value(not undefined)
we initialize input's value based on those props even if we have to override custom value
How to test that? Changing prop(s) and validating input's value.
Based on that we may have up to 3(changed only first prop, only second or both) * 2 (if result is undefined or not) * 2 (if there has been already custom value provided and stored in useState or not) = 12. But I believe exhaustive testing is not a good way. So I'd put most checks in single test case:
it('re-init value for nested input after props changes', () => {
const wrapper = mount(<InputComponent />);
function getInput(wrapper) {
return wrapper.find("input").prop("value");
}
expect(getInput(wrapper).props("value")).toEqual("1"); // based on default props
getInput(wrapper).props().onChange({ target: {value: "initial"} }); // imitating manual change
expect(getInput(wrapper).props("value")).toEqual("initial");
wrapper.setProps({data: {a: "some-a", b: "undefined"} });
expect(getInput(wrapper).props("value")).toEqual("initial");
wrapper.setProps({ item: { field: "c" } }); // data[item.field] is undefined
expect(getInput(wrapper).props("value")).toEqual("initial");
wrapper.setProps({ item: {field: "b" } }); // data[item.field] is "undefined"
expect(getInput(wrapper).props("value")).toEqual("initial");
wrapper.setProps({ item: {field: "a" } }); // data[item.field] is meaningful
expect(getInput(wrapper).props("value")).toEqual("some-a");
});
As for getValue helper - it's needed cause we cannot just memoize input element itself like:
const wrapper = mount(...);
const input = wrapper.find("input");
...
expect(input.prop("value")).toEqual();
...
expect(input.prop("value")).toEqual();
Details can be found in Enzyme's docs. Or just know we need to re-run find after any update.
Also beware Enzyme's setProps does not replace current props but update them by merging(as well as React's setState does with state).

Resources