Gatsby production build is different from local build - reactjs

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.

Related

Conditionally render component views based on browser screen size

I have three different components representing different views for screen sizes. I want to render one of them based on the browser view width. I've managed to get this working using ternary operators, but it'll only work with two elements.
Can someone let me know how to accomplish this without having to use display: hidden on one of the elements?
Here is my code so far:
import React, { useState, useEffect } from "react";
import Navbar from "../../shared/components/Navbar/Navbar";
import Page from "../../shared/interface/Page/Page";
{/* elements */}
import DesktopView from "../../home/components/DesktopView/DesktopView";
import MobileView from "../../home/components/MobileView/MobileView";
import TabletView from "../../home/components/TabletView/TabletView";
function Home() {
const [isDesktop, setDesktop] = useState(window.innerWidth > 650);
const [isTablet, setTablet] = useState(window.innerWidth > 768);
const [isMobile, setMobile] = useState(window.innerWidth > 640);
const updateMedia = () => {
setDesktop(window.innerWidth > 1024);
setTablet(window.innerWidth > 768);
setMobile(window.innerWidth > 640);
};
useEffect(() => {
window.addEventListener("resize", updateMedia);
return () => window.removeEventListener("resize", updateMedia);
});
return (
<Page>
<Navbar />
{/* Elements should go here */}
</Page>
);
}
export default Home;
You can try following Code instead of ternary
isDesktop && <DesktopView />
isTablet && <TabletView />
isMobile && <MobileView />
So if isDesktop is true then only DesktopView will render and similarly isTablet will control TabletView and isMobile will control MobileView rendering

React lazy load won't work inside React perfect scroll bar

I am using react-perfect-scrollbar to show images list.
Inside the perfect scroll bar, I am going to lazy load images. But it won't work.
import React, { useState, useCallback, useEffect } from 'react';
import PerfectScrollbar from 'react-perfect-scrollbar';
import useIsMountedRef from 'src/hooks/useIsMountedRef';
import LazyLoad from 'react-lazyload';
import {
Box,
Button,
Link,
List,
ListItem,
ListItemIcon,
ListItemText,
CircularProgress,
Typography,
makeStyles
} from '#material-ui/core';
const isMountedRef = useIsMountedRef();
const [images, setImages] = useState([]);
const getImages = useCallback(async () => {
try {
setLoading(true);
const response = await axios.get(`${backendUrl}/images/get/images`);
if (isMountedRef.current) {
setImages(response.data.projectImages);
}
} catch (err) {
console.error(err);
} finally {
if (isMountedRef.current) {
setLoading(false);
}
}
}, [isMountedRef]);
<PerfectScrollbar options={{ suppressScrollX: true }}>
<List className={classes.list}>
{images.map((image, i) => (
<ListItem
divider={i < images.length - 1}
key={i}
className={classes.listItem}
>
<ListItemIcon>
<LazyLoad height={90} key={i} overflow>
<img
src={`${awsS3Url}/${image.Key}`}
className={classes.listImage}
onClick={() => onSelect(`${awsS3Url}/${image.Key}`)}
/>
</LazyLoad>
</ListItemIcon>
<ListItemText
primary={GetFilename(image.Key)}
primaryTypographyProps={{ variant: 'h5' }}
secondary={bytesToSize(image.Size)}
className={classes.listItemText}
/>
<MoreButton
handleArchive={() => handleRemoveOne(image)}
/>
</ListItem>
))}
</List>
</PerfectScrollbar>
Some of images(the first view without scrolling) are showing.
When I scroll, the images won't load, only the list contents without images are showing.
What did I code wrong?
I had the same problem. The issue is that react-lazyload is trying to find a container that has its overflow property set to scroll or auto, but perfect-scrollbar sets its overflow property to hidden and handles the scrolling manually instead. So we have to tell react-lazyload manually which container it should monitor for scroll events.
This can, by documentation, be done in two ways; by passing an HTMLElement or a query selector string. Alas, there seems to be a bug in the library that causes the property to be ignored if it is not a string (https://github.com/twobin/react-lazyload/blob/055405125d0313014f0951cffc78345297f10a08/lib/index.js#L261) so currently the only way is to pass a query selector string.
But when I tried to pass a query selector string targeting the perfect-scrollbars container, it seems that the container might not always be there yet when react-lazyload attaches its event listeners, so we must check that the container is actually there before we initialize the LazyLoad-container.
So the relevant code is:
import React, { ReactElement, useRef, useState, useEffect } from 'react';
import PerfectScrollbar from 'react-perfect-scrollbar'
import LazyLoad from 'react-lazyload';
export default (): ReactElement => {
// Get a reference to the wrapper element so we know when it is created
const scrollbarWrapperRef = useRef(null);
// Initialize a state setter to notify the view when the scrollParent becomes available
const [scrollParent, setScrollParent] = useState<HTMLElement|null>(null);
// Adjust this selector to your liking
const scrollParentSelector = '#scrollbar-wrapper .scrollbar-container';
// Here we make sure that the PerfectScrollbar container is actually available before we let the content and the LazyLoads be created.
useEffect(() => {
const scrollParentElement = document.querySelector(scrollParentSelector);
if (scrollParentElement) {
setScrollParent(scrollParentElement);
}
}, [scrollbarWrapperRef.current]);
// The relevant DOM
return (
<div ref={scrollbarWrapperRef} id="scrollbar-wrapper">
<PerfectScrollbar>
{ scrollParent &&
<List>
{images.map((image, i) => (
<LazyLoad scrollContainer={scrollParentSelector}>
<img src="..." />
</LazyLoad>
))}
</List>
}
</PerfectScrollbar>
</div>
);
}

How can I change an image dynamically in Ionic React?

I'm pretty new to React and TypeScript, and I ran into this problem:
In my UI I'm using several decorative graphics as follows:
import Kitten from './img/Kitten.png';
<img className="Image" src={Kitten} />
Now, I have a dark-mode toggle. When it fires, I want to replace all images with their appropriate dark-mode version. I was thinking about something like this:
import Kitten from './img/Kitten.png';
import DarkKitten from './img/DarkKitten.png';
//gets called when dark mode is toggled on or off
const darkModeToggleFunc = () => {
document.querySelectorAll('.Image').forEach(element => {
if(element.src.includes("Dark")) {
element.src = element.src.replace("Dark", "");
} else{
element.src = "Dark" + element.src;
}
});
}
<img className="Image" src={Kitten} />
Now, in React I have two problems: the .src-attribute is unknown because element is not necessarily an image and the second problem is: I don't assign URIs as src but the variable from the import. So there isn't really a string I can change... If I'm informed correctly, React uses Base64 for images specified this way.
How could I achieve my goal in React?
Edit: App.tsx
//bunch of imports
const App: React.FC = () => {
return (
<IonApp>
<IonReactRouter>
<IonSplitPane contentId="main">
<Menu />
<IonRouterOutlet id="main">
<Route path="/page/:name" component={Page} exact />
<Redirect from="/" to="/page/Production" exact />
</IonRouterOutlet>
</IonSplitPane>
</IonReactRouter>
</IonApp>
);
};
export default App;
First things first when it comes to react you dont directly go and change things in the document level, you update the virtual DOM and let react take care of the rest.
You scenario is on changing the theme of the app, this answer is on using React context to change theme and use images appropriately.
First you create a Context which will hold the theme value
const AppContext = createContext({
theme: "light",
setTheme: (theme) => {}
});
Here we are going to use a state variable for simplicity, you can use anything you prefer.
Heres the app.js file
export default function App() {
const [theme, setTheme] = React.useState("light");
const themeState = { theme, setTheme };
return (
<AppContext.Provider value={themeState}>
<div className="App">
<h1>Hello CodeSandbox</h1>
<h2>Start editing to see some magic happen!</h2>
<ImageViewer />
<DarkModeSwitch />
</div>
</AppContext.Provider>
);
}
Here we set the theme value in the state and set the context to that, the setTheme can be used to update the theme from any component that is in the tree. in your case the darkmodeswitch, here we toggle the value
const DarkModeSwitch = () => {
const { theme, setTheme } = useContext(AppContext);
const darkModeToggle = () => {
setTheme(theme === "light" ? "dark" : "light");
};
return (
<div>
<input
type="checkbox"
checked={theme === "light"}
onChange={() => darkModeToggle()}
/>
</div>
);
};
Coming to your main requirement, the images, lets use a common files for images with the contents
export const Kitten ="image source 1";
export const KittenDark ="image source 2";
You simply set the image based on the theme like below
import { Kitten, KittenDark } from "./images";
export default function ImageViewer() {
const { theme } = useContext(AppContext);
return (
<img
alt="kitten"
style={{ height: 50, width: 100 }}
src={theme === "light" ? Kitten : KittenDark}
/>
);
}
as you can see everything is connected via the context and once you update the context you can see the images change.
You can see a working version here
https://codesandbox.io/s/react-theme-switch-3hvbg
This is not 'THE' way, this is one way of handling the requirement, you can use things like redux etc

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.

Gatsby/React - fade out section on scroll?

I've been trying to use gatsby-plugin-scroll-reveal which uses Sal.js to animate a hero section on my site. I'm trying to make it so that the text in the hero fades in then fades out as you scroll down the page. Right now, I can only get it to fade in. How can I make that happen with Sal.js or another way?
I also tried a different way by creating a component that uses IntersectionObserver DOM API but I couldn't get that to work really.
Here's the component:
import React from 'react';
import ReactDOM from 'react-dom';
function FadeInSection(props) {
const [isVisible, setVisible] = React.useState(true);
const domRef = React.useRef();
React.useEffect(() => {
const observer = new IntersectionObserver(entries => {
entries.forEach(entry => setVisible(entry.isIntersecting));
});
observer.observe(domRef.current);
return () => observer.unobserve(domRef.current);
}, []);
return (
<div
className={`fade-in-section ${isVisible ? 'is-visible' : ''}`}
ref={domRef}
>
{props.children}
</div>
);
}
export default FadeInSection
I figured out a solution from this article:
https://markoskon.com/scroll-reveal-animations-with-react-spring/
So, I'm using the react-spring to create reveal animations on scroll and react-visibility-sensor to see if the I want animated element is visible.
// App.js
import React from "react";
import { Spring } from "react-spring/renderprops";
import VisibilitySensor from "react-visibility-sensor";
<VisibilitySensor once>
{({ isVisible }) => (
<Spring delay={100} to={{ opacity: isVisible ? 1 : 0 }}>
{({ opacity }) => <h1 style={{opacity}}>Title</h1>}
</Spring>
)}
</VisibilitySensor>

Resources