React children rerender - reactjs

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.

Related

Component Re-rendering issue

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 🚀

How to create and render components inside a Vue component like in React?

In React, you can do the following:
export const Button = ({ icon, children, ...props }) => {
const Icon = () => (
icon ? <FontAwesomeIcon icon={["fal", "check"]} className={iconPositionClass}/> : null
);
return (
<BsButton children={children} {...props}>
{iconPosition === "left"
? <><Icon/>{children}</>
: <>{children}<Icon/></>
}
</BsButton>
);
};
I can create another component inside the first one, in this case, <Icon />, and then conditionally render it inside the first one, <Button />.
With Vue, however, it's not like that, because there is no return. There's just a template tag, and while you can create a function in the <script> tag, that function cannot return html.
I know that I can re-create the above code using v-if and v-else for the actual element that I want to be rendered, but that just means that I have to repeat the code inside the <template>. It feels too repetitive.
Is there a way to re-create the React way of doing it in Vue?

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

React: wrap child component in div if it does not return null

Using React, how can a child component be wrapped conditionally in another element if it is not returning null when rendered?
export const Parent = ({children}) => (
<div className="row">
{React.Children.map(children, child =>
<div className="col-6 col-s-4 col-md-3">
{child}
</div>
)}
</div>
);
This might be related to the discussions in React component children detect if empty / null before render however, none of the proposed approached seemed to work (ReactDOMServer.renderToStaticMarkup seem to have issues with SSR and React.isValidElement is persistently treating the component that returns null as a valid element).
I've got a feeling that this is sort of an anti-pattern, as it seems to be real hard to do. Only solution I can think of at the moment is moving the column div into the child component and clone a prop into the child to inform it that a wrapping column div is desired... Which seems way hacky.
If you don't want to use
const isChildNull = children => {
return !Boolean(ReactDOMServer.renderToStaticMarkup(children));
};
try with:
const isChildNull = children => {
return Boolean(children.type() === null); //--> will return the tag type
};
If child component receives props, don't forget to pass those props when calling type function:
const isChildNull = children => {
const {type, props} = children;
return Boolean(type(props)=== null);
};
EDIT: working example here: https://stackblitz.com/edit/react-a29daw

lazy render children

Take a look at this simple example:
const List = function({ loading, entity }) {
return (
<Layout loading={loading}>
<span>Name: {entity.name}</span>
</Layout>
);
};
Layout component is rendering its children only when loading is false. But the problem here is that React is resolving Layout children immediatelly. Since entity is null (while loading=true) I get error that it can't read name of null. Is there a simple way to avoid this error since this span will always be rendered when entity is not null?
Currently I know about 3 options:
Move this span to stateless function which receives entity as prop
Wrap whole children of Layout in function and then support function children in Layout
Just use {entity && <span>Name: {entity.name}</span>}
Why do I have to use one of these options and can I make React to consider those children as function and resolve block inside later on before the render?
I just stumbled upon the same problem.
What worked for me was passing children as a function:
ready: boolean;
children?: () => ReactNode,
}> = ({ ready, children }) => {
return (
<div>{
ready ?
children() :
(
<div>not ready</div>
)
}</div>
);
};
<Layout loading={loading}>
{() =>
<span>Name: {entity.name}</span>
}
</Layout>
Though it's still not perfect.

Resources