React: Specify Location of Nested Children Components? - javascript

How I want to setup my component hierarchy to be setup like this:
<Page>
<Page.Section>
<Page.Item/>
</Page.Section>
</Page>
How do I reference the nested children of children components (aka Page.Item) to put in a different location because I don't want specific styling to effect it? See Code Comments. Due to the styling, I do not want to change it.
How do you only allow Page.Section and Page.Item to be used in Page Component and not standalone?
Page.js
const Page = ({ children }) => {
return (
<>
<div
className='bg-gray-100 border-b-2 flex flex-col top-0 fixed w-screen transition-height duration-300 mt-16'
>
<SomeComponent/>
// Section Children I want here so it takes styling
{children}
</div>
// Item Children I want here so it does not take styling, but I want the Item Children to always be nested in The Page.Section Component.
{children.children?}
</>
);
};
// I want his Section element to be
const Section = ({ children }) => {
return (
<>
<ConfiguratorTab/>
{children}
</>
);
};
const Item = () => {
return (
<>
<Form/>
</>
);
};
Page.Section = Section;
Page.Item = Item;
export default Page;

children returns an object with the following keys and values.
type: ƒ Section() {}
key: null
ref: null
props: Object
_owner: FiberNode
_store: Object
The props object holds the prop values passed to the current(parent) component. So the children of children component could be accessed using props. And since Item is a child of Page (although not a direct child), you need not include it again in the Section component, if you don't want certain styles to be applied to it. This could be accomplished by removing children from Section.
const Section = ({ children }) => {
return (
<>
<ConfiguratorTab/>
{//Don't render any children elements here.
}
</>
);
};
const Page = ({ children }) => {
return (
<>
<div
className='bg-gray-100 border-b-2 flex flex-col top-0 fixed w-screen transition-height duration-300 mt-16'
>
<SomeComponent/>
// Section Children I want here so it takes styling
{children}
</div>
// Item Children I want here so it does not take styling, but I want the Item Children to always be nested in The Page.Section Component.
{children.props.children}
</>
);
};

Related

How to access a child component (from props.children) ref using functional components

I'm trying to use an IntersectionObserver.observe() on every single child component from a parent component, but for that I need their refs.
This is what my main component looks like:
// App component
<Nav>
<Section ref={useRef(null)} {/* other props */} />
<Section ref={useRef(null)} {/* other props */} />
<Section ref={useRef(null)} {/* other props */} />
<Section ref={useRef(null)} {/* other props */} />
</Nav>
I need the Nav component to access the ref of each Section component. I created the observer and got all children elements on a list:
// Nav component
const observer = new IntersectionObserver(observerCallback, options);
const sectionList: ReactElement<SectionProps>[] = [];
childrenToList(props.children, sectionList);
return (
<>
<nav className="nav">
{/* ... */}
</nav>
{sectionList}
</>
)
// Section component
const Section = React.forwardRef<HTMLDivElement, SectionProps>((props, ref) => {
return (
<section ref={ref} {/* other attributes */}>{props.children}</section>
);
});
This is what the childrenToList function is doing:
function childrenToList(children: ReactNode, listToPush: any[]) {
React.Children.forEach(children, (element) => {
if (!React.isValidElement(element)) return;
listToPush.push(element);
});
}
Then I need a way to do something like that (not an actual working code):
sectionList.forEach((section) => {observer.observe(section.ref.current)})
The problem is that I couldn't find a way to access the ref. I already tried having the ref as a prop, but it said it's value is null, or if I used with an if statement (like
const ref = section.props.ref
if (ref) {
observer.observe(ref.current)
}
) the page simply wouldn't render and would remain blank.

How can I add props to a compound child component without using top level api methods?

I am attempting to create a Panel component that has the option to be wrapped in a PanelGroup component. The PanelGroup components main function is the query the children Panel components and determine if certain conditions so it can add a z-index prop to each of them according to the conditions.
Is it a left, right, top or bottom positioned Panel
Is it a nested Panel
Each condition will require a z-index that may be higher than the others and also placed in front of the highest z-index found on the page. I would like to be able to iterate though the children inside a useEffect and after logic is ran to determine the hierarchy, add the z-index to each child as a prop. I have attempted to use React.Children mapping through and using cloneElement to add the z-index, however the issue is this method must be called inside the render/return and when a second panel is opened it causes any other panel that is open to close and open again. Is there a way to update the props without using the top level React API?
PanelGroup API
<PanelGroup>
<Panel
id={'1'}
open={open1}
onClosed={handleClose1}
controller={true}
renderPortal={true}
position={PanelPosition.RIGHT}
>
<div>
<h2>Panel Slide Left</h2>
<button onClick={handleClose1}>Close</button>
</div>
</Panel>
<Panel
id={'2'}
open={open2}
onClosed={handleClose2}
controller={true}
renderPortal={true}
position={PanelPosition.LEFT}
>
<div>
<h2>Panel Slide Right</h2>
<button onClick={handleClose2}>Close</button>
</div>
</Panel>
</PanelGroup>
This is what I tried which does not work
const PanelGroup = ({
children
}: PanelGroupProp): JSX.Element => {
const [highestZIndex, setHighestZIndex] = React.useState(null);
/**
* Handles finding the highest index on the page after render. This value
* will be used as the benchmark to set the z-index of the panel
* components managed by this component.
*/
React.useEffect(() => {
const highestIndex = getHighestZIndex();
setHighestZIndex(highestIndex);
}, []);
const renderGroupPanelChildren = (children): React.ReactElement => {
let incrementor = highestZIndex + 1;
return (
<React.Fragment>
{React.Children.map(children || null, (child) => {
const newProps = {...child.props, ZIndex: incrementor };
incrementor++;
return <child.type {...newProps } key={getGuid()} />;
})}
</React.Fragment>
);
};
return (
<>
<PanelOverlay visibility={false} />
{renderGroupPanelChildren(children)}
</>
);
};
I would like to find a way to do something like:
const PanelGroupComponent = ({
children
}: PanelGroupProp): JSX.Element => {
const [highestZIndex, setHighestZIndex] = React.useState(null);
const [elements, setElements] = React.useState(null);
/**
* Handles finding the highest index on the page after render. This value
* will be used as the benchmark to set the z-index of the panel
* components managed by this component.
*/
React.useEffect(() => {
const highestIndex = getHighestZIndex();
setHighestZIndex(highestIndex);
}, []);
React.useEffect(() => {
// logic to set z-index's ....
const elements ....
setElements(elements);
}, [highestIndex]);
return (
<>
<PanelOverlay visibility={false} />
{elements}
</>
);
};

react-window stop unnecessary rendering of child FixedSizeList rows when scrolling parent FixedSIzeList

In my react project I am using react-window package to render nested lists. Each parent FixedSizeList row renders a component which uses another FixedSizeList. Parent List doesn't have more than 14 rows at the moment. But the child List may contain upto 2000 rows. Now my problem is, when I try to scroll through the parent List, all the child list items in the viewport seem to re rendering. This is a little bit problematic for me because in my child list item I am using d3js to draw bar chart with transition effect. So these unnecessary re rendering is giving a overall weird UI. Can anyone help me how can I stop these unnecessary renders.
Here is codesandbox link to a very simple example of my problem.
Please open the console log. After initial load the topmost log should be like this: initial console log.
Then if you clear the console and scroll the parent list, you will see log like this: console log after parent scrolling. Here you can see that the child list items of child list 0 is re rendering which is not needed for me.
Can anyone give me a solution that can stop these re rendering?
*P.S. I am not using memo since every row is updating the dom on its own.
Edit
I think this problem would solve if the parent list would stop propagating scroll event to child. I tried to add event.stopPropagation() and event.stopImmediatePropagation() in the parent list row but the output was the same as earlier.
We can use memo to get rid of components being re-rendered unnecessarily for same set of props. And use useCallback to prevent re-creation of a function and thus secure child components being re-rendered. Applying those, we can get this solution:
import "./styles.css";
import { FixedSizeList as List } from "react-window";
import { memo, useCallback } from "react";
const Row = memo(({ index: parentIndex, style: parentStyle }) => {
console.log("rendering child list", parentIndex);
const InnerRow = useCallback(({ index, style }) => {
console.log("rendering child list item", index, "of parent ", parentIndex);
return <div style={style}>Child Row {index}</div>;
}, []);
return (
<div style={parentStyle}>
<List height={200} itemCount={1000} itemSize={35} width={270}>
{InnerRow}
</List>
</div>
);
});
const Example = () => {
console.log("rendering parent list");
return (
<List height={400} itemCount={16} itemSize={300} width={300}>
{Row}
</List>
);
};
export default function App() {
return (
<div className="App">
<Example />
</div>
);
}
although the above code works fine, it can be optimized more if we use areEqual method from react-window as react memo dependency. And for more if we want to use other react hooks inside InnerRow component, we must add a middleware component of InnerRow. The full example is given below:
import { FixedSizeList as List, areEqual } from "react-window";
import { memo, useCallback } from "react";
const Row = memo(({ index: parentIndex, style: parentStyle }) => {
console.log("mounting child list", parentIndex);
const data = new Array(15).fill(new Array(500).fill(1));
const InnerRowCallback = useCallback(
({ index, style }) => {
return <InnerRow index={index} style={style} />;
},
[data]
);
const InnerRow = ({ index, style }) => {
console.log("mounting child list item", index, "of parent ", parentIndex);
return <div style={style}>Child Row {index}</div>;
};
return (
<div style={parentStyle}>
<List height={200} itemCount={1000} itemSize={35} width={270}>
{InnerRowCallback}
</List>
</div>
);
}, areEqual);
const Example = () => {
console.log("mounting parent list");
return (
<List height={400} itemCount={16} itemSize={300} width={300}>
{Row}
</List>
);
};
export default function App() {
return (
<div className="App">
<Example />
</div>
);
}
Here I am passing data array as useCallBack dependency because I want to re render the InnerRow component if data gets changed.

having difficulty in extracting out id in react component

I can see item and item id as it loops to render on the screen but i don't see the value of id when i click on any of the Tile where Tile is a div and react styled component.
class CategoryOffers extends React.Component {
passidtopointscreen =(id)=>{
console.log("id is", id);
localStorage.setItem('points_id',id);
this.props.history.push('/marketplacepoints')
debugger
}
render() {
debugger
return (
<Wrapper>
{this.props &&
this.props.cards_data &&
this.props.cards_data.map(item => {
return (
<Tile onClick={(item)=>this.passidtopointscreen(item.id)}>
<ImageWrapper>
<Image src={item.logo} height={'24px'} width={'73px'} />
</ImageWrapper>
<CardString>{item.offer_summary}</CardString>
</Tile>
)
})}
</Wrapper>
)
}
}
onClick={()=>this.passidtopointscreen(item.id)}
while adding item there you create new instance for this keyword for no reason
By having the same argument-name as your already decleared argument (item), you overwrite the outer argument. There should be no reason for you here to use the event-argument, if I have understood your question correctly.
I would also suggest avoiding localstorage and instead make use of the state.
I made the component into functional one here:
import React from "react";
const CategoryOffers = ({history,cards_data}) => {
const passidtopointscreen =(id)=>{
localStorage.setItem('points_id',id);
history.push('/marketplacepoints')
}
return (
<Wrapper>
{
cards_data?.map(item => {
return (
<Tile onClick={(event)=>passidtopointscreen(item.id)}>
<ImageWrapper>
<Image src={item.logo} height={'24px'} width={'73px'} />
</ImageWrapper>
<CardString>{item.offer_summary}</CardString>
</Tile>
)
})}
</Wrapper>
)
}
}

React-virtualized dynamic height list renders everything stacked initially

I am trying to use react-virtualized to virtualize a list where some rows have varying heights, and also the list takes up all the space in the parent. I am trying to accomplish this with the CellMeasurer, AutoSizer and List components.
My package versions are as follows:
react: "16.8.6"
react-dom: "16.8.6"
react-virtualized: "^9.21.1"
import React, { PureComponent } from 'react';
import 'react-virtualized/styles.css';
import AutoSizer from 'react-virtualized/dist/commonjs/AutoSizer';
import { CellMeasurer, CellMeasurerCache, List } from 'react-virtualized';
class Table extends PureComponent {
rowRenderer = ({ index, style, key }) => {
return (
<CellMeasurer
cache={this.cache}
columnIndex={0}
key={key}
parent={parent}
rowIndex={index}
>
<div style={style} key={key}>
content
</div>
</CellMeasurer>
);
}
cache = new CellMeasurerCache({
defaultHeight: 24,
fixedWidth: true,
});
renderAutoSizerContent = () => {
return this.RenderList;
}
RenderList = ({ height, width }) => {
return (<List
items={this.props.items}
width={width}
height={height}
rowCount={this.props.items.length}
rowHeight={this.cache.rowHeight}
rowRenderer={this.rowRenderer}
deferredMeasurementCache={this.cache}
/>
);
}
render() {
return (
<AutoSizer
items={ this.props.items}
>
{this.renderAutoSizerContent()}
</AutoSizer>
);
}
}
export default Table;
After the initial render everything looks like this. For some reason the top attribute is 0 for every element in the array:
After scrolling or triggering a rerender the items seem to get their top property and the following renders. In the actual code some of my elements have height variance, but the height seems to default to the defaultHeight I give to the CellMeasurerCache constructor anyway.
How do I get the initial render to have top property for each element, and how do I get the heights to calculate correctly? What am I doing wrong in the code I have shown here?
In your rowRenderer component, you provided parent prop to CellMeasurer but parent is undefined.
You get parent from rowRenderer as written in the docs: https://github.com/bvaughn/react-virtualized/blob/master/docs/List.md#rowrenderer
So your rowRenderer component should be:
rowRenderer = ({ index, style, key, parent }) => {
return (
<CellMeasurer
cache={this.cache}
columnIndex={0}
key={key}
parent={parent}
rowIndex={index}
>
<div style={style} key={key}>
{items[index]}
</div>
</CellMeasurer>
);
}
You can also check this code sandbox with working example: https://codesandbox.io/s/material-demo-yw1k8?fontsize=14

Categories

Resources