Conditionally render component views based on browser screen size - reactjs

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

Related

How to separate two swiper instance hooks on the same page?

I have two React Swipers on the same page.
I'm using useSwiper to create my own navigation buttons.
It works great for one swiper. But now that I have two instances on the same page, when I click the next on one instance, both of them go to the next item.
In other words, their navigations are tied together.
These swiper instances are inside two separate components.
const Home = () => {
return <div>
<FirstSection /> // This has a `useSwiper` in it
<SecondSection /> // This also has a `useSwiper` in it
</div>
}
How can I make these instances separate from each other?
I have searched the docs and I can't find what to do.
you need to add a controller for each one take a look to the docs https://swiperjs.com/react#controller and check the example below
import React, { useState } from 'react';
import { Controller } from 'swiper';
import { Swiper, SwiperSlide } from 'swiper/react';
const App = () => {
// store swiper instances
const [firstSwiper, setFirstSwiper] = useState(null);
const [secondSwiper, setSecondSwiper] = useState(null);
return (
<main>
<Swiper
modules={[Controller]}
onSwiper={setFirstSwiper}
controller={{ control: secondSwiper }}
>
{/* ... */}
</Swiper>
<Swiper
modules={[Controller]}
onSwiper={setSecondSwiper}
controller={{ control: firstSwiper }}
>
{/* ... */}
</Swiper>
</main>
);
};

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

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.

Update multiple states in React Native (increase counter for each component individually)

I've found a few similar questions on thit topic but they're usually about React and not React Native specifically and I'm finding it a bit difficult to translate them as very new to both coming from an Android/Java background. I've a component that holds a plus and a minus icon to increase/decrease their counter. This component is used multiple times however, and I don't want to have a total and a setTotal for each instance of this component so that each can have their own total updated independently of any of the other components. At the moment, they all update when I click any of the plus/minus signs. I'm using hooks.
const [total, setTotal] = useState(0)
const increase = () => {
setTotal(total + 1)
}
const decrease = () => {
setTotal(total - 1)
}
...
<Reportable
title={'First'}
decrease={decrease}
increase={increase}
total={total}
onPress={handleChange}
/>
<Reportable
title={'Second'}
decrease={decrease}
increase={increase}
total={total}
/>
Thanks very much.
The problem is you are passing to both component the same state (total). So, it doesn't matter who had incremented it or decremented it... they will share the values because both are using same state.
If each component needs to be aware of how many times it was incremented, you should create a state for the component itself like so:
import React from 'react';
import { View, Button } from 'react-native';
export default function Reportable() {
const [total, setTotal] = useState(0)
const increase = () => {
setTotal(total + 1)
}
const decrease = () => {
setTotal(total - 1)
}
return (
<View>
<Button onPress={increase} >Increment</Button>
<Button onPress={decrease} >Decrement</Button>
</View>
);
}
Now import Reportable in the App.js like so:
import React from 'react';
import { View } from 'react-native';
import Reportable from './Reportable';
export default function App () {
return (
<View>
{/* This is the first Reportable */}
<Reportable />
{/* This is the second Reportable */}
<Reportable />
</View>
);
}
Now, if you need to get each count in the App.js give us more detail about what you're trying to implement so we can come up with a solution that fits your problem.
As per #edilsomm217's answer, this is a sample for what worked for me.
import React from 'react';
import { View, Button } from 'react-native';
export default function Reportable() {
const [total, setTotal] = useState(0)
const increase = () => {
setTotal(total + 1)
props.increase()
}
const decrease = () => {
setTotal(total - 1)
props.decrease()
}
return (
<View>
<Button onPress={increase} >Increment</Button>
<Button onPress={decrease} >Decrement</Button>
</View>
);
}
Now import Reportable in the App.js like so:
import React from 'react';
import { View } from 'react-native';
import Reportable from './Reportable';
export default function App () {
return (
<View>
{/* This is the first Reportable */}
<Reportable
increase={increase}
decrease={decrease}
/>
{/* This is the second Reportable */}
<Reportable
increase={increase}
decrease={decrease}
/>
</View>
);
}
So I can both update the total individually as also getting the amount combined for all the items together when doing something like <Text>{total}</Text> under either the Reportable.js file or App.js respectively.

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