List component continually re-renders - reactjs

I feel like this is likely due to me not understanding the flow of React, but I've been struggling to figure it out. Have an infinite scroll list.
if Row is outside of Scroller it works fine.
However, if Row is inside of Scroller, it results in the component constantly re-rendering. I was hoping to pass a list into Scroller from the parent component with around a thousand items, but in order to do that, I need Row in Scroller to be able to access the props. Whats the best way to go about this?
import React from "react";
import { FixedSizeList as List } from "react-window";
import AutoSizer from "react-virtualized-auto-sizer";
const Scroller = (randomArray) => {
const Row = ({ index, style }) => (
<div className={index % 2 ? "ListItemOdd" : "ListItemEven"} style={style}>
{randomArray[index]}
</div>
);
return (
<AutoSizer>
{({ height, width }) => (
<List
className="List"
height={height}
itemCount={1000}
itemSize={35}
width={width}
>
{Row}
</List>
)}
</AutoSizer>
);
};
export default Scroller;

I'm not 100% sure what you're asking, but I think I have an idea...
Render props can be a bit confusing, but they're essentially children components that are functions. Meaning, the direct child of List must be a function that accepts an object of parameters and returns JSX. See this react-window gist for more information regarding passing in data to List and accessing data from within the child function.
Here's a working demo:
By design, this child function is supposed to be re-rendered to add/remove items from the DOM as the user scrolls up/down. To view this behavior, right click on an element, like Season Id, within in the codesandbox window and click on "Inspect" -- you may need to do this twice to focus on the targeted element -- then while the mouse is hovered over the codesandbox render window, scroll down. What you'll notice is that items are dynamically added/removed based upon the scroll direction. So if you're expecting this child function to NOT be re-rendered when the window is scrolled, then you probably shouldn't be using a virtualized list and, instead, should be using pagination.
Example.js
import React from "react";
import { FixedSizeList as List } from "react-window";
import AutoSizer from "react-virtualized-auto-sizer";
const Example = ({ dataList }) => (
<AutoSizer>
{({ height, width }) => (
<List
className="List"
height={height}
itemCount={dataList.length}
itemData={dataList}
itemSize={265}
width={width}
>
{({ data, index, style }) => {
const dataItem = data[index];
return (
<div
className={index % 2 ? "ListItemOdd" : "ListItemEven"}
style={style}
>
<h1>Season Id: {dataItem.seasonId}</h1>
<h2>Form Id: {dataItem._id}</h2>
<h2>Start Month: {dataItem.startMonth}</h2>
<h2>End Month: {dataItem.endMonth}</h2>
<h2>Send Reminders: {dataItem.sentReminders.toString()}</h2>
</div>
);
}}
</List>
)}
</AutoSizer>
);
export default Example;
index.js
import React from "react";
import { render } from "react-dom";
import Example from "./Example";
import dataList from "./data.json";
import "./styles.css";
render(<Example dataList={dataList} />, document.getElementById("root"));

Related

Get the Gatsby Link destination and conditionally render exit animation with Framer Motion

I've built a small website to learn more about page transition with Gatsby and Framer Motion and Styled Components.
[SPOILER]: My problem to be solved is at the end of the code blocks
The way it's currently working is simple:
An homepage with a list of projects
export default function Home() {
return (
<Layout>
<Welcome />
<WorkList />
<Footer />
</Layout>
)
}
A project page template that generate each project thanks to createPages (here is a simplified version)
import React, { useState, useRef, useContext, useEffect } from "react"
import { Link } from "gatsby"
// Components
...
// Data
import Projects from "../data/works.json"
// Styles
...
// Variants
...
const Project = ({ pageContext }) => {
const project = Projects.find(({ id }) => id === pageContext.id)
// lots of functions here
return (
<Layout>
<ProjectWrapper>
<Container>
<ProjectContent>
<BackgroundLines />
<ProjectContentInner>
<ProjectHeader>
<!-- here the header logic -->
</ProjectHeader>
<ProjectBlocks>
<!-- here the content logic -->
</ProjectBlocks>
</ProjectContentInner>
<ProjectFooter>
<!-- here the footer logic -->
</ProjectFooter>
</ProjectContent>
</Container>
</ProjectWrapper>
</Layout>
)
}
export default Project
The Layout component is holding the navigation
// Components
import Header from "./header"
// Styles
import { GlobalStyle } from "../styles/globalStyles"
const Layout = ({ children }) => {
return (
<div className="app">
<GlobalStyle />
<Header />
<main>{children}</main>
</div>
)
}
export default Layout
and last but not least, the gatsby.browser.js wrapped with the AnimatePresence and the Context Provider
import React from "react"
import { AnimatePresence } from "framer-motion"
import { LocationProvider } from "./src/context/locationContext"
export const wrapPageElement = ({ element }) => (
<LocationProvider>
<AnimatePresence exitBeforeEnter>{element}</AnimatePresence>
</LocationProvider>
)
export const shouldUpdateScroll = () => {
return false
}
So what I want to do seemed easy but it turned out that is not (at least for me).
I've currently made a beautiful transition between 2 projects, similar to the one you could see in here.
If you scroll to the bottom of the page, you can see that the next project's header is shown as a preview and once you click on it, it will smoothly transition to the next project page.
Awesome.
BUT, but this transition is a problem when the user clicks on the link in the navigation that takes him to the home or to another page.
I don't want to have the same exit transition, where some elements disappear while others overlaps, and I don't want the same timing. I want to do something completely different, based on where I'm headed to.
What I thought of as a solution, is to conditionally render exit transition in framer motion, to have different exit animation based on some variables.
I want to be able to track the Link Destination before the component unmount in order to be able to conditionally render an exit transion in Framer Motion
Since, as you may have seen, the navigation isn't inside the project.js I tried with createContext and useContext, getting the location.pathname to have an origin state and a e.target.pathname on Link to have a destination state. This doesn't actually works because everything seems to get a rerender.
I just provided the pieces of codes that seemed crucial to understand the overall structure, but I can go deeper with the way I've built variants or the current exit animations.
I'm not sure if it will help but you can get the props as any React component in the wrapPageElement:
export const wrapPageElement = ({ element, props }) => {
console.log("props", props)
if(props.location.pathname==='/'){
//return some other animation stuff
}
return <LocationProvider {...props}>
<AnimatePresence exitBeforeEnter>{element}</AnimatePresence>
</LocationProvider>
}

How to filter and sort product in ReactJs by Dropdown component of React bootstrap

I am creating a filter function for the product list in UI for the user. But I have an issue that, I do not know and never ever try with this function before, so I really difficult to resolve it. I have only 1 day left to do that, so I was very confused
This is my Dropdown Component
import React from "react";
import { Dropdown as BootstrapDropdown } from "react-bootstrap";
import PropTypes from "prop-types";
import "../Dropdown/index.css";
const Dropdown = ({ items }) => {
return (
<BootstrapDropdown className="sort-dropdown">
<BootstrapDropdown.Toggle
className="sort-dropdown-toggle"
variant="success"
id="dropdown"
>
<span className="toggle-text">Selection</span>
</BootstrapDropdown.Toggle>
<BootstrapDropdown.Menu className="sort-dropdown-menu">
{items.map((name, index) => (
<BootstrapDropdown.Item
className="sort-dropdown-item"
key={index}
href={`#/action-${index}`}
>
{name}
</BootstrapDropdown.Item>
))}
</BootstrapDropdown.Menu>
</BootstrapDropdown>
);
};
Dropdown.propTypes = {
items: PropTypes.array,
};
Dropdown.defaultProps = {
items: [],
};
export default Dropdown;
And this is my page, which the place I get the Dropdown component
import React from "react";
import { Row } from "react-bootstrap";
import Group from "../../../components/Group/index";
import Dropdown from "../../../components/Dropdown/index";
import "../GroupBar/index.css";
const GroupBar = () => {
return (
<Row className="group-bar">
<Group
title="Product group"
element={<Dropdown items={["Milk Tea", "Juice"]} />}
/>
<Group
title="Sort by price"
element={<Dropdown items={["Low to hight", "Hight to low"]} />}
/>
</Row>
);
}
export default GroupBar;
I would like to filter (by category) and sort (by price) my product page by items of the dropdown. When I select that item, the product will be filtered according to the item I chose.
This is my product list page
import React, { useEffect } from "react";
import { Container, Row, Col } from "react-bootstrap";
import ProductItem from "../../../components/ProductItem/index";
import Loading from "../../../components/Loading";
import PropTypes from "prop-types";
import "../../../common/index.css";
import "../ProductList/index.css";
const ProductList = ({ products, loading, fetchProductRequest }) => {
useEffect(() => {
fetchProductRequest();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
if (loading) {
return (
<Container>
<Row>
<Col>
<Loading />
</Col>
</Row>
</Container>
);
}
return (
<Container>
<Row>
{!!products && products.length > 0 ? (
products.map((product, index) => {
return (
<ProductItem
key={index}
image={product.image}
name={product.name}
price={product.price}
/>
);
})
) : (
<h4 className="center-title">Product list is empty!</h4>
)}
</Row>
</Container>
);
};
export default ProductList;
This is the page for that,
The list of product and the filter/sort are located in the same folder but different files. Like this
The Group bar it is contain the filter/sort. I get all values by redux, saga
The main page, contain all of them is here
import React from "react";
import { Container } from "react-bootstrap";
import GroupBar from "./GroupBar";
import ProductContainer from "../../containers/ProductContainer";
import Carousel from "../../components/Carousels";
import "../Product/index.css";
const Product = () => {
return (
<Container fluid className="p-0">
<Carousel />
<Container>
<GroupBar />
<ProductContainer />
</Container>
</Container>
);
};
export default Product;
How can I filter related to my list product when it different file like that.
Please anyone help me with this my problem, I just have one day to finish that function, I already research on the internet but it's doesn't make me understand more because it so different from my code and I can not apply that code for mine.
I really really need your support and help as well as you can, the full the better. It's not just helped me to understand also for others like me who are doesn't try it before also see the code is easy to understand too.
I always welcome all of your comments. That is my pleasure. Thank you so much.
You will need your components to keep track of some state. You can read about how to do that here and here.
Once you understand the concept of state, you need to keep track of which item in the list is selected. So for example, you need to keep a state variable that tracks whether "Milk Tea" is selected or "Juice" is selected.
Then, once you have that state, you can display your items using filter or sort on the items list.
Personally, I recommend using class components instead of function components, but here is a minimal working example using function components:
import React, { useState } from 'react';
import { Dropdown as BootstrapDropdown } from 'react-bootstrap';
import './App.css';
const Dropdown = (props) => {
return (
<BootstrapDropdown>
<BootstrapDropdown.Toggle variant='success' id='dropdown'>
<span>Selection</span>
</BootstrapDropdown.Toggle>
<BootstrapDropdown.Menu>
{props.items.map((name, index) => (
<BootstrapDropdown.Item
key={index}
onClick={(event) => {
console.log(event.target.text);
props.setSelected(event.target.text);
}}
value={name}
>
{name}
</BootstrapDropdown.Item>
))}
</BootstrapDropdown.Menu>
</BootstrapDropdown>
);
};
function App() {
const [typeFilter, setTypeFilter] = useState('');
const allItems = [
{ name: 'Coffee Milk Tea', type: 'Tea' },
{ name: 'Earl Gray Milk Tea', type: 'Tea' },
{ name: 'Orange Juice', type: 'Juice' },
{ name: 'Wheatgrass Juice', type: 'Juice' },
];
const itemsToShow = allItems
.filter((item) => {
if (typeFilter) {
return item.type === typeFilter;
}
return true;
})
.map((item, i) => {
return <li key={i}>{item.name}</li>;
});
return (
<div>
<Dropdown items={['Tea', 'Juice']} setSelected={setTypeFilter} />
<ol>{itemsToShow}</ol>
</div>
);
}
export default App;
Notice that the App component stores the state, and passes its state setter to the Dropdown component. The Dropdown gets the setter in its props and uses it to set the App's state when an option is clicked. The App then uses its state to determine which items to show (using items.filter).
This is an example of Lifting state up. Normally, we would think of tracking which item is selected as the job of the dropdown. But, since we need to access that state in another component, we have to "lift up" that state to something higher in the tree. In this small example case, it was App that stored the state. In general, if the tree looks like this:
A
B
C
D
E
F
G
H
and you want to share state between G and D, you need to put that state inside of A because A is the closest parent of both G and D. If you want to share state between C and D, then you need to put that state inside B, because B is the parent of C and D.
In reference to the comment below, you probably want to keep the state for which thing in the dropdown is selected inside of your Product component. Then you need to pass the state setter down the props chain all the way into the Dropdown component, which can call that setter and update the state.
Sorry to hear about your tight schedule. Hopefully this answer can be of some use to you.

backgroundColor and onClick Wont Take Effect React JS In Basic React App

I am going through a React Learning Textbook that is explaining me about Hooks. The hooks part is working fine.
The author is illustrating how to customize Hooks with some easy to use style and related components.
I have the following code.
import React from 'react';
// import logo from './logo.svg';
import './App.css';
import StarRating from './components/StarRating';
import StarRating2 from './components/StarRating2';
import Headline from './components/Headline';
function App() {
return (
<article>
<Headline/>
<StarRating/>
<StarRating2
style={{ backgroundColor: "blue" }}
onClick={e => alert(" click")}
/>
</article>
);
}
export default App;
The component code is like this.
import { useState } from "react";
import React from "react";
import { FaStar } from "react-icons/fa";
const createArray = length => [...Array(length)];
const Star = ({ selected = false, onSelect = f => f }) => (
<FaStar color={selected ? "red" : "grey"} onClick={onSelect} />
);
const numberOfStarts = 10;
const numberDefaultState = 7;
function StarRating2({ totalStars = numberOfStarts })
{
//will hold the user’s rating
// create this variable by adding the useState hook directly to the StarRating component:
const [selectedStars, setSelectedStars] = useState(numberDefaultState);
return (
<>
<p>
This is Star Rating 2 - and it has some imporvements
</p>
{createArray(totalStars).map((n, i) => (
<Star
key={i}
selected={selectedStars > i}
onSelect= {
() => {
setSelectedStars(i + 1);
}
}
/>
))}
<p>
{selectedStars} of {totalStars} stars
</p>
</>
);
}
export default StarRating2;
Unfortunately, neither does the component display change its back ground color. Nor does it respond to a click. The app continues to run with no errors or anything. and I can see that the style properties set are visible in the component tree in the react developer tools in Firefox. So, the code is reflecting on the app for sure.
I am in the 6th chapter now, and so far, every chapter code has worked exactly as it is in the book. This one though, is not. I am unsure if this a wrong code (and perhaps, I should reach out to the author) or this is something that is no longer allowed and the book is simply out of date.
StarRating2 is a React component, you are passing couple of props to StarRating2 but you aren't using those props inside StarRating2 component. CSS styles and event handlers work on native DOM elements.
What you need to do is make use of the props that are passed to StarRating2 in from App component. You can apply the styles prop on the wrapper element that wraps all the JSX code of StarRating2 component and use onClick prop on the element which should react to the click event in some way.
To apply the background color in StarRating2 component, wrap the JSX code in a wrapper element, for example a div and then use the value of style prop on this wrapper element.
function StarRating2({ totalStars = numberOfStarts, style }) {
...
return (
<div style={style}>
...
</div>
);
}
To use the click handler, you will need to use the onClick prop and add it on any native DOM element.
You need to spread the props from the parent to a native react component , styles and eventListeners can only be attached to native components like div , button etc , if the Star component supports adding color and eventListeners through its , you can do ... rest in props and spread it to star component , if you need any help , send me a codesandbox , I will explain in that

Gatsby production build is different from local build

I have a weird result in my production build that is not replicated in my local development build. I'm using Gatsby with ReactBootstrap to make Tabs and a Carousel. I've coded the app so that if the screen width is less than 576, the carousel component is loaded, otherwise, the tab component is loaded.
So here's the problem. The first two HTML nodes in service_carousel.js i.e the Container and Carousel tags, are added on page load and become the wrapper for the service_tabs.js code. I don't know why. The services_tabs.js code should be loaded since I'm viewing it from a laptop screen and should only have the nodes specified in services_tabs.js. If I inspect the code and change the device to a phone, the error is fixed and the tags are removed even if I switch back to a large screen. However, if you reload the page the error comes back.
Here's a code sandbox with the full code https://codesandbox.io/s/sad-glade-u8j9g
My code is as follows:
service_tabs.js
import React from 'react';
import styles from './service_tabs.module.scss';
import { TabContent } from '../component_barrel';
import {
Tab,
Tabs,
Row,
Col,
} from '../../utils/bootstrap_imports_barrel';
import useData from '../../utils/useData';
const tab_data = useData.tab_data;
const ServiceTabs = () => (
<Row className="justify-content-center p-4">
<Col lg={10} md={9} className="align-self-center">
<Tabs justify className={styles.custom_tabs} defaultActiveKey="item 1" id="uncontrolled-tab-example">
{
tab_data.map(({ tab_title, title, icon, image, content }, index) => {
const key = `item ${index + 1}`;
return (
<Tab eventKey={key} key={key} title={tab_title}>
<TabContent
icon={icon}
image={image}
title={title}
content={content}
/>
</Tab>
)
})
}
</Tabs>
</Col>
</Row>
);
export default ServiceTabs;
service_carousel.js
import React from 'react';
import {
Container,
Carousel,
} from '../../utils/bootstrap_imports_barrel';
import styles from './service_carousel.module.scss';
import { TabContent } from '../component_barrel';
import useData from '../../utils/useData';
const tab_data = useData.tab_data;
const ServiceCarousel = () => (
<Container className="p-0" fluid>
<Carousel className="py-4" controls={false} indicators={false} touch={true}>
{
tab_data.map(({ title, icon, image, content }, index) => {
const key = `item ${index + 1}`;
return (
<Carousel.Item key={key} className={styles.carousel_container}>
<TabContent
icon={icon}
image={image}
title={title}
content={content}
/>
</Carousel.Item>
)
})
}
</Carousel>
</Container>
);
export default ServiceCarousel;
and the main service.js
import React from 'react';
import {
ServiceTabs,
ServiceCarousel
} from './component_barrel'
import { useWindowWidth } from '#react-hook/window-size';
const Service = () => {
const width = useWindowWidth();
const componentLoaded = width > 576 ? <ServiceTabs /> : <ServiceCarousel />;
return (
<div className="service_container">
{componentLoaded}
</div>
);
};
export default Service;
Since at the initial render point your code is asking for the window's width at const width = useWindowWidth();, your code will only work in the first load since the width of your window is set only one time and it is locked to that value until it re-renders. Your width will only apply to the first render.
To achieve what you are trying to do, you must check for the window availability first to await all your logic before it is set. That will cause a blink of a few milliseconds until the code calculates the window's width and choose what component render but it's the only way to do with any static site generator and window calculations. So, in your service.js:
import React from 'react';
import {
ServiceTabs,
ServiceCarousel
} from './component_barrel'
import { useWindowWidth } from '#react-hook/window-size';
const Service = () => {
let width;
if (typeof window !== 'undefined') width = useWindowWidth();
const width = useWindowWidth();
const componentLoaded = typeof window !== 'undefined' ? width > 576 ? <ServiceTabs /> : <ServiceCarousel /> : null;
return (
<div className="service_container">
{componentLoaded}
</div>
);
};
export default Service;
Note the duplicity of typeof window !== 'undefined', should be refactored to avoid repetitions but as an initial approach, it will do the job. In addition, a chained ternary condition is not the best option in terms of readability but for now, it will work.
Basically you are awaiting for the window creation to make your calculations and display a component or another based on the width value, that will be conditioned for the availability of the window.
You can check for further information about the window (and global objects) at Gatsby's documentation.

Material-UI ListItem Component for Right Toggle

This seems so basic that I feel I must be misunderstanding how it works. I have a simple demo component that renders a material-ui List with three ListItems. Each list item has a toggle on the right hand side implemented using the rightToggle prop. For the purposes of demonstration each toggle is generated differently.
The first is a basic material-ui Toggle component. The second is a custom component wrapping a Toggle and the third is generated by a function call.
Some code:
import React from 'react';
import Paper from 'material-ui/Paper';
import { List, ListItem } from 'material-ui/List';
import Toggle from 'material-ui/Toggle';
import MyToggleComponent from './MyToggleComponent';
const myToggleFunction = id => <Toggle onClick={() => console.log(id)} />;
const TestPage = () =>
<div>
<Paper style={{ width: 500, padding: 15, margin: 25 }}>
<List>
<ListItem
primaryText="This is the first list item"
secondaryText="This toggle for this item is directly defined"
rightToggle={<Toggle onClick={() => console.log('1 - clicked')} />}
/>
<ListItem
primaryText="This is the second list item"
secondaryText="This toggle is generated from a component"
rightToggle={<MyToggleComponent text="2 - clicked" />}
/>
<ListItem
primaryText="This is the third list item"
secondaryText="This toggle is generated programatically"
rightToggle={myToggleFunction('3 - clicked')}
/>
</List>
</Paper>
</div>;
export default TestPage;
and the custom component - very basic
import React from 'react';
import PropTypes from 'prop-types';
import Toggle from 'material-ui/Toggle';
const MyToggleComponent = ({ text }) => <Toggle onClick={() => console.log(text)} />;
MyToggleComponent.propTypes = {
text: PropTypes.string.isRequired,
};
export default MyToggleComponent;
Results in:
All three toggles generate the expected console output. The first and third items render as I would expect with a Toggle to the right of the list item. But the second, using a custom component, renders the Toggle above the list item. Can anyone explain why?
Material-UI is cloning these elements under the hood and is adding/injecting a prop style. In the first and third example the actual values are the Material UI defined components that accept a property style as documented here. Your own defined component however only passes the text property and does nothing with style.
So comes down to that all 3 examples get passed a style prop but only the first and third do something with it. To bad this wasn't well documented.
It kinda does say it needs to be a Toggle element and your own component isn't one because it wraps the Toggle component.
pushElement(children, element, baseStyles, additionalProps) {
if (element) {
const styles = Object.assign({}, baseStyles, element.props.style);
children.push(
React.cloneElement(element, { // element is your own defined component
key: children.length,
style: styles, // here the style property is passed
...additionalProps, // your text property is passed here
})
);
}
}
source
So to fix this change:
const MyToggleComponent = ({ text }) => <Toggle onClick={() => console.log(text)} />;
to:
const MyToggleComponent = ({ text, style }) =>
<Toggle style={style} onClick={() => console.log(text)} />;

Resources