Component Re-rendering issue - reactjs

I am working on the project in React Typescript.
I have created hierarchy of components as per requirement.
In one scenario I have to pass data from child component to parent component and I am passing function as props and it works.
Issue :
When passing data to parent component child component gets re-render it looks like. Mean to say Dropdown selection is get reset and tree control expanded nodes get collapsed and set to the position as first time rendered.
I have used useState,useEffects hooks.
I have also tried React.memo as a part of my search on internet.
What I need :
I want to pass data to parent component from child without re-render the child component as there is no change in the props of child component.

Try this approach:
Add useCallback hook to memoize your function which lift data to <Parent />.
Then use React.memo for <Child /> to control prop changes and avoid unwanted re-renders.
I prepare an example for you here.
UPD. I have uploaded an example, you can copy it and see how it works!
Here is Child component:
const Child = ({ onChange }) => {
console.log("Child re-render");
return (
<div className="App">
<h1>Child</h1>
<button onClick={() => onChange(Math.random())}>
Lift value to Parant
</button>
</div>
);
};
const areEqual = ({ onChange: prevOnChange }, { onChange }) => {
return prevOnChange === onChange; // if true => this will avoid render
}
export default React.memo(Child, areEqual);
And the Parent:
consn App = () => {
const [value, setValue] = useState("");
const onChange = useCallback((value) => setValue(String(value)), []);
console.log("Parant re-render");
return (
<div className="App">
<h1>Parent</h1>
<div>Value is: {value}</div>
<Child onChange={onChange} />
</div>
);
}
Best regards 🚀

Related

React children rerender

I have two layouts one for mobile second for desktop view. Thier structure is different however both inject the same childrens. I would like to prevent childrens rerender when the layout switch. Here is pseudocode which reflect the case(or check live https://playcode.io/1193034):
import React, { useState } from "react";
interface ParentProps {
children: React.ReactNode;
}
const Parent = ({ children }: ParentProps): JSX.Element => {
const [value, setValue] = useState(false);
if (value) {
return (
<>
<button type="button" onClick={() => setValue((val) => !val)}>
Turn into false
</button>
{children}
</>
);
}
return (
// some additional elements for desktop view
<div>
<div>
<button type="button" onClick={() => setValue((val) => !val)}>
Turn into true
</button>
{children}
</div>
</div>
);
};
const ChildrenComponent = () => {
console.log("rerender Children...");
return <p>children component</p>;
};
export const App = ()=> {
return (
<Parent>
<ChildrenComponent />
</Parent>
);
}
What could I do?
So far I have tried to wrap children component in Rect.memo and useMemo (inside Parent) both didn't work. Maybe it's just impossible to do
You will not be able to avoid rerendering in your scenario, as you are rendering different elements in different positions
The way React knows what elements to rerender is by creating a tree structure (the virtual DOM) and comparing it to the browser DOM. When a node in the virtual DOM changes in relation to the browser DOM, it will be replaced (and therefore rerendered), along with every child element of this node. This process is called reconciliation.
Because of this, even if some of the child components are the same, once you change the position or the type of their parent elements, there is no way to avoid their rerendering.
On a sidenote, you could avoid the rerendering if you restructure your component to always return the same types of elements in the same positions, and make them responsive by using CSS media queries.

Reactjs. Use Ref inside the component and outside

I wrote a custom input for passwords. He looks like this:
const InputPassword = ({placeholder}) => {
const inputRef = useRef()
const [isPasswordVisible, setPasswordVisible] = useState(false)
function setInputStatus() {
if (isPasswordVisible) {
inputRef.current.setAttribute('type', 'password')
} else {
inputRef.current.setAttribute('type', 'text')
}
setPasswordVisible(!isPasswordVisible)
}
return (
<div className={cl.main}>
<input
ref={inputRef}
type={"password"}
placeholder={placeholder}
className={cl.input}
/>
<div className={cl.eyeContainer} onClick={setInputStatus}>
<Eye
isPasswordVisible={isPasswordVisible}
/>
</div>
</div>
);
};
export default InputPassword;
Now I want to get the value from my input in the parent component. The best way to do this is to use a forwardRef, but my component already has an internal Ref that is needed to change the type of the input. Surely you can somehow use one Ref to solve these two problems, but I did not find how.
I tried to pass a Boolean type state from the parent, so that when this state changes, call a method in the child component, this method would change another state in the parent, which stores the value from the child. And when this parent state changes, the necessary logic would work out for me. But the code turned out to be just as cumbersome and confusing as what is written above. Naturally, such code does not work well, it shows unpredictable behavior. I'm sure the solution is much simpler.
You can also pass a state variable to the child component, I am passing a ref variable, assuming that the parent component does not need to re-render based on the changes in the value of variable that is being passed as a prop.
Your parent component should have a inputRef, and pass it as a prop to child component, something like this:
const Parent = () => {
const inputRef = useRef()
return <InputPassword inputRef={inputRef} />
}
export default Parent;
const InputPassword = ({placeholder, inputRef}) => {
const [isPasswordVisible, setPasswordVisible] = useState(false)
function setInputStatus() {
if (isPasswordVisible) {
inputRef.current.setAttribute('type', 'password')
} else {
inputRef.current.setAttribute('type', 'text')
}
setPasswordVisible(!isPasswordVisible)
}
return (
<div className={cl.main}>
<input
ref={inputRef}
type={"password"}
placeholder={placeholder}
className={cl.input}
/>
<div className={cl.eyeContainer} onClick={setInputStatus}>
<Eye
isPasswordVisible={isPasswordVisible}
/>
</div>
</div>
);
};
export default InputPassword

How to trigger a parent and child re-rendering by updating a prop in a child?

I found the following answer to a question that I currently have (link). In in, he said "If you pass any props to the component from the parent component and you update that prop in children or that prop update in the parent component so both will re-render." Can anyone give me a brief code snippet that achieves the case where you pass a prop to a child component from a parent component, then you update it in the child component so that both components will re-render? I am not still sure if this is attainable...
(scenario)
Let's say I have a parent component which is a sidebar containing a list of items. When I click an edit button of one of items, a modal (child component) where I can change the item name pops up. As hitting a submit button with a new item name as an input, the previous item name gets updated with the new one behind the scenes and the modal gets closed. As this happens, I would like the parent component to re-render to reflect the change on UI.
const Parent () => {
const [isOpen, setIsOpen] = useState(true);
const [{ items }, dipatch} = useStateValue(); //Fetch items object through React Context API
return (
{items.map((item) => (
<p>{item}<p />
))}
<Child callback={() => setIsOpen(false)} />
)
}
const Child (callback) => {
const submitHandler = () => {
postRequest(data); //updates items' data + updates items in the context
callback(); //After closing the modal here, I would like the parent component re-render.
}
return (
<Modal
isOpen={isOpen}
submit={submitHandler}
onClose={callback}
>...
<Modal />
)
}
Code example, when values from the parent are passed to the react child component, and data changes come from the child component.
Note that there is no state in the child component and it takes data from the values passed to it
import {useState} from 'react'
const NumberIn = ({num, change}) => {
return (
<input type='number' onChange={(e) => {
change(e.currentTarget.value)
}} value={num} />
)
}
export default function App() {
const [a, setA] = useState(0);
const [b, setB] = useState(0);
return (
<div className="App">
<NumberIn num={a} change={setA} /> + {' '}
<NumberIn num={b} change={setB} /> = {' '}
<span>{Number(a) + Number(b)}</span>
</div>
);
}
We pass a value and a function (a kind of callback) to the child component. When an event occurs in a child component, the passed function will be called, but it will be called in the parent component, because it belongs to the parent component, but with the parameters passed by the child component.

Multiple instances of same react component passing values to parent component

We have a page that has a tab on the top. Both tabs show same components, except that on one tab some of the labels and other controls are different (some are hidden, some appear only on one, etc.) The changes are minimal enough that they don't warrant a different component altogether.
So what I am trying to figure out is how to embed multiple instances of the same child component into a parent, and yet be able to pass values from different instances of the child to the parent.
Here is my parent component for example
import React, { useState } from "react";
import Child1 from "./Child1";
const Parent1 = () => {
const changeNameFunc = (childName) => {
alert(childName);
};
return (
<div>
Parent
<div>
<Child1 id="1" changeName={changeNameFunc} />
<Child1 id="2" changeName={changeNameFunc} />
</div>
</div>
);
};
export default Parent1;
And here is my child component
import React, { useState } from "react";
const Child1 = (props) => {
const [name, setname] = useState();
const clicketyclick = () => {
const newName = document.getElementById("txtName").value;
setname(newName);
};
return (
<div>
<input type="text" id="txtName" name="name" />
<button onClick={clicketyclick}>Change Child Name</button>
<button onClick={() => props.changeName(name)}>Send To Parent</button>
</div>
);
};
export default Child1;
In this case the problem is obviously in my clicketyclick function because the document.getElementById picks up the first control that matches that ID in the DOM. In my code at work, we aren't picking up values using document.getElement, rather we are passing state object up to the parent via a props.function call.
So to make it random, I changed my child code in this example to following.
import React, { useState } from "react";
const Child1 = (props) => {
const [name, setname] = useState();
function getRandomInt(max) {
return Math.floor(Math.random() * max);
}
const clicketyclick = () => {
const newName = getRandomInt(100); // document.getElementById("txtName").value;
setname(newName);
};
return (
<div>
<input type="text" id="txtName" name="name" />
<button onClick={clicketyclick}>Change Child Name</button>
<button onClick={() => props.changeName(name)}>Send To Parent</button>
</div>
);
};
export default Child1;
Now this works just fine. If I click ChangeChildName on first instance of Child control, it sets a random number (say 54) to the state of that instance. If I then click SendToParent on first instance of Child control, it alerts 54. If I click ChangeChildName on second instance of Child control, it sets a different random number (say 98) to the state of that instance. If I then click SendToParent on second instance of Child control, it alerts 98. This is great. It means the state is owned by the instance of the child control.
So with this knowledge, are there any gotchas with nesting multiple instances of a child component into a parent? Is it better to just avoid it and create a separate component even if 98% of the functionality is same? I've read somewhere that in this case we should make sure different instances have different "id" property (though taking it out didn't make any difference; my code still worked). I've also read somewhere about passing in a context of some sort to prefix the field ids with (not sure how that would work).
What is the best way to do this i.e. have a parent render multiple instances of a child control, and then have each child send their respective data up to the parent without causing any collisions or other gnarly problems with state management.
Edit: My question really isn't about the getElementById part. My question is what is the proper way to have multiple instances of a control embedded inside a parent component. Are you supposed to pass some sort of prefix for the ids in the child control, and if so how? Are you supposed to have an "id" attribute on the child control definition inside the parent control to make react aware of it that they are unique? Or something else?
You can use useRef
import React, { useState, useRef } from 'react';
const Child1 = (props) => {
const [name, setname] = useState('');
const inputRef = useRef(null);
const clicketyclick = () => {
const newName = inputRef.current.value;
setname(newName);
};
return (
<div>
<input type="text" name="name" ref={inputRef} />
<button onClick={clicketyclick}>Change Child Name</button>
<button onClick={() => props.changeName(name)}>
Send To Parent
</button>
</div>
);
};

When react perform componentDidMount and componentWillUnmount

I played with React for several years, still confused with mount/unmount mechanism in some case.
Since mount/unmount is the place to perform side effect, I do not want them to be invoked randomly. So I need to figure out how they work. As far as I can understand currently, when the virtual dom do not present in real dom, it tend to be unmounted. However, it seems not the whole story, and I can not reason it about
function TestMount(props) {
useEffect(() => {
console.log("componentDidMount", props.name);
return () => {
console.log("componentWillUnount", props.name);
};
}, []);
return <h1>Test content {" " + JSON.stringify(props.name)}</h1>;
}
function Update({ click }) {
return <button onClick={click}>Update</button>;
}
function App() {
const [count, setCount] = useState(0);
const Component = name => <TestMount name={name} />;
return (
<div className="App">
<h1>{count}</h1>
<Component name="one" />
{Component("two")}
<Update click={() => setCount(x => x + 1)} />
</div>
);
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
Component One is remount overtime the app render while Component two not?Why this happen?
Component is a new function each time App is rendered, so <Component name="one" /> is remounted each time too, they are considered different components.
The result of Component("two") call is <TestMount name={"two"} />, TestMount stays the same each time App is rendered, so it's not remounted.
Component is invalid component for what it's used for, to pass name string as name prop to TestMount component because name parameter is not a string but props object when Component is used like <Component name="one" />. name => <TestMount name={name} /> is render function, it's preferable to name it accordingly like renderTestMount for clarity because components aren't supposed to be called directly like Component("two").
In case a function is supposed be used as component or render function interchangeably, the signature should be changed to ({ name }) => <TestMount name={name} />.
The expected behaviour could be achieved for <Component name="one" /> by memoizing Component:
const Component = useCallback(({ name }) => <TestMount name={name} />, []);
But since Component doesn't depend on App scope, a correct way is to define it outside:
const Component = ({ name }) => <TestMount name={name} />;
function App() {...}
For instance, this is the reason React Router Route has separate component and render props for a component and render function. This allows to prevent unnecessary remounts for route components that need to be defined dynamically in current scope.
The key to such issue is the difference between the React Component and React element, put shortly React is smart with element not Component
Component vs element
Component is the template used to create element using <> operation. In my prospective, <> is pretty much like new operator in OOP world.
How React perform update between renders
Every time the render method(or functional component) is invoked. The new element is created using <>, however, React is smart enough to tell the element created between renders are actually the same one, i.e. it had been created before and can be reused as long as the element is created by the same Component
How about different Component
However when the identity of the Component using to generate element changes(Even if the Components look same), React believes something new come though, so it remove(unmount) the previous element and add(mount) the new one. Thus componentDidMount or componentWillUnmount is invoked.
How is confusing
Think we got a Component and when we generate element using <Component /> react can tell the same elements because they are generated by the same Component
However, HOCComponent=()=><Component />; element= <HOCComponent />, every time element is generated, it used a different Component. it is actually a HOC constructed dynamically. Because the HOC is created dynamically inside render function, it can be confusing on the first glance.
Is that true
I never found any offical document about the idea above.However the code below is enough to prove
function TestMount(props) {
useEffect(() => {
console.log("componentDidMount", props.name);
return () => {
console.log("componentWillUnount", props.name);
};
}, []);
return <h1>Test content {" " + JSON.stringify(props.name)}</h1>;
}
function Update({ click }) {
return <button onClick={click}>Update</button>;
}
let _Component;
function cacheComponent(C) {
if (C && !_Component) {
_Component = C;
}
return _Component || null;
}
const CacheComponent2 = once(({ name }) => <TestMount name={name} />, []);
function App() {
const [count, setCount] = useState(0);
// can be used as a HOC of TestMount or a plain function returnnung a react element
const Component = name => <TestMount name={name} />;
const CacheComponent1 = cacheComponent(Component);
const CacheComponent3 = useCallback(
({ name }) => <TestMount name={name} />,
[]
);
return (
<div className="App">
<h1>{count}</h1>
{/* used as HOC */}
<Component name="one" />
{/* used as function returnning the element */}
{Component("two")}
<CacheComponent1 name="three" />
<CacheComponent2 name="four" />
<CacheComponent3 name="five" />
<Update click={() => setCount(x => x + 1)} />
</div>
);
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
Also the code above provide three different ways to avoid the undesired mount/unmount. All the solutions are cache the identity of the HOC somehow

Resources