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.
Related
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.
I have a wrapper component in my App as follows. I have logic in the wrapper component that gets the DOM nodes with props.children that have a specific data attribute.
When the DOM elements are direct children, this works fine. When they are children of any nested component they're not found. How can I iterate through the entire structure and get all instances of the DOM nodes by attr?
I'm new to react and sure this should be straightforward, however I've not been able to implement from a number of examples / SO answers. I've tried to implement useRef and useContext hooks but can't get this?
// App.js
<Wrapper>
<div data-elem></div> // Get's found
<Component {...pageProps} />
</Wrapper>
// index.js
const Page = () => {
return (
<>
<div data-elem></div> // Not found
</>
);
}
// Wrapper.js ( simplified version )
export default function Wrapper(props) {
const detectedElements = props.children.filter((item) => item.props['data-elem'] === true);
console.log(detectedElements.length)
return (
<div className="wrapper">
{children}
</div>
)
}
Ultimately I managed to achieve this simply by using querySelectorAll within the useEffect hook. I'm not sure if this is the 'correct' way to do it, but is definitely the most straightforward of the the variations I tried, and seems to do the job, regardless of where the corresponding elements are.
useEffect(() => {
const detectedElements = document.querySelectorAll('[data-elem]')
}, []);
I have seen some articles about forwardRef, however couldn't be able to find an example that have a specific approach to it, basically when we are dealing with the children props of React and we need to forward multiple ref to this children props.
I will give an example to clarify any doubt.
Let image we have a parent component List, which will look like it:
const List = ({ children }) => (
<div>
{children}
</div>
);
And we have its children component Tab:
const Tab = ({ children }) => (
<div>
{children}
</div>
);
They are being used like that:
<List>
<Tab />
<Tab />
<Tab />
</List>
Therefore my question is, how I would be able to create multiple refs at List, be able to forward them to Tab, properly set them at each Tab, and them finally get its reference at List to work with.
If there still any doubts I'm happy to clarify.
I guess you can use something like React.cloneElement and pass the references as props to the children.
{React.Children.map(children, childElement =>
React.cloneElement(childElement)
)}
This is the current answer I came with:
const tabReferences = React.useRef<HTMLDivElement>([]);
...
{React.Children.map(children, (child, index) => {
return React.cloneElement(child, {
ref: tabReferences.current[index],
});
})}
Thanks Lucas!
Ok so here is a simplification of my real-world use-case. Basically I have a complicated layout and I pass the layout component to the child so that it renders itself within it and then the parent layout displays it as its children. Works great, except I use this in a loop and I cannot figure out how to set the key on the children from the parent.
It's strange because I can set other properties without any issues. In the example below, the "id" property is correctly set from the list, but the console emits the warning:
Warning: Each child in a list should have a unique "key" prop.
const Child = Parent => (
<Parent>
The child is reponsible
<br />
for writing the contents.
</Parent>
);
const present = (title) => {
const Tag = ({ children }) => (
<article id={title} key={title}>
<h1>{title}</h1>
{children}
</article>
);
return Child(Tag);
};
const app = (
<div>
{[
'hello',
'this',
'is',
'a list'
].map(present)}
</div>
);
ReactDOM.render(
app,
document.getElementById('root')
);
here's a demo of that code on codepen
Is there any way to accomplish what I'm trying to do?
This may seem like an over-complicated way to render a template, I know, but I have - I think - a good real-world use-case.
Actually I figured it out.
I need to use a React.Fragment around the return value of "present",
like so:
const present = (title) => {
const Tag = ({ children }) => (
<article id={title}>
<h1>{title}</h1>
{children}
</article>
);
return (
<React.Fragment key={title}>
{Child(Tag)}
</React.Fragment>
);
};
the problem is you are providing the key to the child & not the parent of what is being rendered from the map items.
you can set key to the parent component (instead of <article>) as well as sending the Tag parameter:
const Child = (Parent, key) => (
<Parent key={key}>
The child is reponsible
<br />
for writing the contents.
</Parent>
);
//...
const present = (title) => {
const Tag = ({ children }) => (
<article id={title}>
<h1>{title}</h1>
{children}
</article>
);
return Child(Tag, title);
};
your solution by using a React.Fragment also does a similar job (setting the key for the outer wrapper (here being fragment) in rendering the list using the map method). the difference being you are creating a fragment for each iteration in the map method, but by passing the key you can avoid that as well.
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