How to render lightbox with different url from image in view? - reactjs

I am getting images from unsplash api, and I am able to render them on the page. I am implementing a lightbox to be able to render the high res pic of the images in the page. I dont know how to go forward, I will show some code.
const ImageList = ({ image, isLoaded }) => {
// const [imageIndex, setImageIndex] = useState(0);
const [isOpen, setIsOpen] = useState('false');
if (isLoaded) {
return (
<div className="spinner">
<ReactLoading type="spin" color="blue" />
</div>
);
}
const onClickHandler = () => {
setIsOpen(true);
};
const imgs = image.map(img => (
<img
key={img.id}
src={img.urls.small}
onClick={onClickHandler}
/>
));
if (imgs.length === 0) {
return (
<p>No images</p>
);
}
if (isOpen === true) {
return (
<Lightbox
onCloseRequest={() => setIsOpen(false)}
mainSrc=
/>
);
}
return (
<React.Fragment>
{imgs}
</React.Fragment>
);
};
export default ImageList;

Related

Saving data to local storage in React

So I have those cards on the link right now. When the favorite button is clicked it will appear under FAVORITES. I wanted those cards under FAVORITES to be on the local storage, and when I "unfavorite" the cards, they should also be removed from the local storage. But how exactly do I do that? Should I use useEffect()?
Codesandbox: https://codesandbox.io/s/broken-fast-kgyzvc?file=/src/App.js
App.js
export default function App() {
const [all, setAll] = useState(true);
const [favorites, setFavorites] = useState(false);
const showAll = () => {
setAll(true);
setFavorites(false);
};
const showFavorite = () => {
setFavorites(true);
setAll(false);
};
const [dataSource, setDataSource] = useState(data);
const onFavorite = (cardId) => {
const newDataSource = [...dataSource];
const foundCardData = newDataSource.find((card) => card.id === cardId);
if (!foundCardData) return;
foundCardData.isFavorite = !foundCardData.isFavorite;
setDataSource(newDataSource);
};
return (
<div className="App">
<div className="button-group">
<button className={all ? "all active" : "all"} onClick={showAll}>
ALL
</button>
<button
className={favorites ? "favorites active" : "favorites"}
onClick={showFavorite}
>
FAVORITES
</button>
</div>
<br />
<Masonry
breakpointCols={3}
className="my-masonry-grid"
columnClassName="my-masonry-grid_column"
>
{all &&
dataSource.map((item) => (
<Cards
key={item.id}
text={item.text}
isFavorite={item.isFavorite}
onFavorite={() => onFavorite(item.id)}
/>
))}
{favorites &&
dataSource
.filter((item) => item.isFavorite === true)
.map((filtered) => (
<Cards
key={filtered.id}
text={filtered.text}
isFavorite={filtered.isFavorite}
onFavorite={() => onFavorite(filtered.id)}
/>
))}
</Masonry>
</div>
);
}
Cards.js
const Cards = ({ text, isFavorite, onFavorite }) => {
return (
<div className="cards">
<p>{text}</p>
<button onClick={onFavorite}>
{isFavorite ? "Added to Favorites!" : "Favorite"}
</button>
</div>
);
};

condition return statement Is not working with filter in rendering list of data in react.js

** As you can see i am taking input from the user and display and wanted data display on screen according to the year which you select( filter data according to year) and if there is no item i wanted to display found no expense **
this is my Expenses item code
const ExpenseAll = (props) => {
const [filteredYear, setFilteredYear] = useState("2020");
const filterChangeHandler = (selectedYear) => {
setFilteredYear(selectedYear);
};
const filteredExpenses = props.items.filter((expense) => {
return expense.date.getFullYear().toString() === filteredYear;
});
return (
<div>
<Card className="expenses">
<ExpensesFilter
selected={filteredYear}
onChangeFilter={filterChangeHandler}
/>
<ExpensesList items={filteredExpenses} />
</Card>
</div>
);
};
this is my condition filter code which is not working showing empty screen
if (props.items.length === 0) {
return <h2 className="expenses-list__fallback"> Found no Expense</h2>;
}
return (
<ul className="expenses-list">
{props.items.map((expense) => (
<ExpenseItem
key={expense.id}
title={expense.title}
amount={expense.amount}
date={expense.date}
/>
))}{" "}
;
</ul>
);
}
Try to store filteredExpenses in a state with default value:
const ExpenseAll = (props) => {
const [filteredExpenses, setFilteredExpenses] = useState([]);
const [filteredYear, setFilteredYear] = useState('2020');
const filterChangeHandler = (selectedYear) => {
setFilteredYear(selectedYear);
};
useEffect(() => {
const filtered = props.items.filter((expense) => {
return expense.date.getFullYear().toString() === filteredYear;
});
setFilteredExpenses(filtered)
}, []);
return (
<div>
<Card className='expenses'>
<ExpensesFilter
selected={filteredYear}
onChangeFilter={filterChangeHandler}
/>
<ExpensesList items={filteredExpenses} />
</Card>
</div>
);
};

Why is my function not working- pop up image gallery

I'm trying to make a standard effect, where clicking on an image in a gallery will enlarge the image, put it at the center of the screen (in front of everything else), and darken the background. I haven't set up the slideshow part yet (so it won't change images), but the aim is to create an index so that I can do that in the future. I'm following a tutorial and trying to adapt it to my backend, but I'm missing a beat. It's not registering which image has been clicked (and I'm getting two errors in the console- 404, and 500). I'm using Nextjs as my frontend, Sanity for my backend.
import React, { useState } from 'react';
import { client, urlFor } from '../lib/client';
import { Header, Footer, Modal } from '../components';
const sets = ({setData, imagesData}) => {
const [clickedImage, setClickedImage] = useState(null);
const [currentIndex, setCurrentIndex] = useState(null);
const handleClick = (imagesData, index) => {
setCurrentIndex(index);
setClickedImage(imagesData.image);
};
const handleRotationRight = () => {
const totalLength = imagesData.imageItems.length;
if(currentIndex + 1 >= totalLength){
setCurrentIndex(0);
const newData = imagesData.imageItems[0];
setClickedImage(newData);
return;
}
const newIndex = currentIndex + 1;
const newData = imagesData.imageItems.filter((image) => {
return imagesData.imageItems.indexOf(image) === newIndex;
});
const newItem = newData[0].image;
setClickedImage(newItem);
setCurrentIndex(newIndex);
};
return (
<div>
<Header />
<main className="slug-gallery">
<div className="title">
<div className="title-line-left"></div>
<h2>{setData.set_name}</h2>
<div className="title-line-right"></div>
</div>
<div className="images-container">
<ul className="overall-images">
{imagesData.imageItems && imagesData.imageItems.map((imagesData, index) => (
<li key={index}>
<img
src={urlFor(imagesData.image).auto('format').url()}
className="the_image"
alt='test a'
onClick={() => handleClick(imagesData, index)}
/>
</li>
))}
</ul>
</div>
{clickedImage && (
<Modal
clickedImage={clickedImage}
handleRotationRight={handleRotationRight}
setClickedImage={setClickedImage}
/>
)}
</main>
<Footer />
</div>
)
}
export default sets
export const getServerSideProps = async (pageContext) => {
const setSlug = pageContext.query.slug;
const setQuery = `*[_type == 'set' && slug.current == $setSlug][0]`;
const imagesQuery = `*[_type == 'set' && slug.current == $setSlug][0]{'imageItems':set_images[]{image{
asset->{_id, url}, alt, name, date, size, materials}}}`;
const setData = await client.fetch(setQuery, {setSlug});
const imagesData = await client.fetch(imagesQuery, {setSlug});
return {
props: {setData, imagesData}
}
}
Heres the Modal component:
import React from 'react'
const Modal = ({clickedImage, handleRotationRight, setClickedImage}) => {
const handleClick = (e) => {
if(e.target.classList.contains("dismiss")){
setClickedImage(null);
}
}
return(
<>
<div className="overlay dismiss" onClick={handleClick}>
<img src={clickedImage} alt='test b'/>
<span className="dismiss" onClick={handleClick}>x</span>
</div>
<div onClick={handleRotationRight} className="overlay-arrows_left">
<img src="/next_portfolio/public/images/cart.png" alt='test c'/>
</div>
</>
)
};
export default Modal;

React - How to prevent parent re-render on prop change

I am making a calculator using react.
Every time I press a number button, the whole application re-renders, instead of the <Display />.
To prevent it, I tried 2 different approaches for App, But neither of them worked.
Here is the sandbox link.
Any help would be appreciated.
Put clickHandler inside of useCallback()
const App = () => {
const [screen, setScreen] = useState("0");
console.log("render");
const clickHandler = useCallback(
(val) => {
if (val === "AC") {
setScreen("");
return;
}
screen === "0" ? setScreen(val) : setScreen(screen + val);
},
[screen]
);
return (
<div className="App">
<div className="display">{screen}</div>
<ButtonList clickHandler={clickHandler} />
</div>
);
};
Put Display component inside of React.memo
const App = () => {
const [screen, setScreen] = useState("0");
console.log("render");
const clickHandler = (val) => {
if (val === "AC") {
setScreen("");
return;
}
screen === "0" ? setScreen(val) : setScreen(screen + val);
};
const displayComponent = () => {
return (
<>
<div className="display">{screen}</div>
<ButtonList clickHandler={clickHandler} />
</>
);
};
const MemoizedComponent = React.memo(displayComponent);
return (
<div className="App">
<MemoizedComponent />
</div>
);
};
And here's the ButtonList & Button component.
export const ButtonList = ({ clickHandler }) => {
const arr = [...Array.from(Array(10).keys()).reverse(), "AC"];
return (
<div className="buttons">
<div className="numbersWrapper">
{arr.map((item) => (
<Button
key={item}
clickHandler={clickHandler}
value={item.toString()}
/>
))}
</div>
</div>
);
};
export const Button = ({ value, clickHandler }) => {
return (
<button
name={value}
onClick={() => {
clickHandler(value); //where the clickEvent happens
}}
>
{value}
</button>
);
};
If you don't want a component re-render,You would have to define the click handler in another component that you would like to re-render.
So do it like this:
const App = () => {
console.log("render");
return (
<div className="App">
<childComponent />
</div>
);
};
export const childComponent = () => {
const [screen, setScreen] = useState("0");
const clickHandler = (val) => {
if (val === "AC") {
setScreen("");
return;
}
screen === "0" ? setScreen(val) : setScreen(screen + val);
};
return (
<>
<div className="display">{screen}</div>
<ButtonList clickHandler={clickHandler} />
</>
);
}
This way you prevent a particular component from re-rendering. But note that if you update a state or do anything from which causes re-renders from the parent component, It would equally re-render the child component.

How to derive loading state when using lazy and suspense in React?

I am trying to build a lazy loading tabs component that fetches the code/bundle for the tab only when it is clicked. I am trying to use lazy+suspense for it. I would like to animate/color the tab the user clicked on when it is loading to indicate that a tab is being loaded. How can I do that best?
Here is some example code I have whipped up. The bug here is that the tab-header can sometimes get rendered twice when the code is being loaded. How can I avoid the issue and display a loading state on the new tab.
import React, {lazy, Suspense, useState, useReducer} from "react";
import "./App.css";
import classNames from "classnames";
import Spinner from "./Spinner";
const Tokyo = lazy(() => {
return import("./Tokyo");
});
const Mexico = lazy(() => {
return import("./Mexico");
});
const London = lazy(() => {
return import("./London");
});
const App = () => {
const [_, setTab] = useState("Tokyo");
return (
<div className="App">
<header className="App-header">
<Tabs initialTab="Tokyo" onTabChange={setTab}>
<Tab id="Tokyo" name="Tokyo">
<Tokyo />
</Tab>
<Tab id="Mexico" name="Mexico">
<Mexico />
</Tab>
<Tab id="London" name="London">
<London />
</Tab>
</Tabs>
</header>
</div>
);
};
const Tab = () => {
return null;
};
function genClickLog(log, current) {
const set = new Set([current]);
const newLog = [current];
log.forEach(l => {
if (!set.has(l)) {
log.push(l);
newLog.push(l);
}
});
return newLog;
}
function createSuspenseTree(targetTab, log, child, tabs, handleTabChange) {
const head = log.shift();
if (head !== targetTab) {
console.warn(`expect ${head} to be ${targetTab}`);
}
let current = child;
log.forEach(l => {
current = (
<Suspense
fallback={
<Fallback
tabs={tabs}
prevTab={l}
activeTab={targetTab}
onTabChange={handleTabChange}
/>
}
>
{current}
</Suspense>
);
});
return <Suspense fallback={<Spinner />}>{current}</Suspense>;
}
function reducer(state, action) {
switch (action.type) {
case "change":
if (state.current === action.id) {
return state;
}
return {
current: action.id,
prev: state.current,
clickLog: genClickLog(state.clickLog, action.id),
};
case "initial":
return {
current: action.id,
prev: null,
clickLog: [action.id],
};
default:
throw new Error("bad reducer action");
}
}
const Tabs = props => {
const {children, onTabChange, initialTab} = props;
const [state, dispatch] = useReducer(
reducer,
{
clickLog: [],
prev: null,
current: null,
},
{type: "initial", id: initialTab}
);
const handleTabChange = tab => {
dispatch({type: "change", id: tab});
onTabChange(tab);
};
const tabs = React.Children.map(children, x => ({
id: x.props.id,
name: x.props.name,
render: x.props.children,
}));
const child = (
<>
<TabHeader
tabs={tabs}
activeTab={state.current}
onTabChange={handleTabChange}
/>
{tabs.map(x => (
<div key={x.id}>
<TabFrag
id={x.id}
key={x.id}
activeTab={state.current}
render={x.render}
/>
</div>
))}
</>
);
return (
<div className="TabContainer">
{createSuspenseTree(
state.current,
[...state.clickLog],
child,
tabs,
handleTabChange
)}
</div>
);
};
const Fallback = props => {
const {prevTab, activeTab, onTabChange, tabs} = props;
if (prevTab && prevTab !== activeTab) {
return (
<>
<TabHeader
tabs={tabs}
activeTab={prevTab}
loadingTab={activeTab}
onTabChange={onTabChange}
/>
{tabs.map(x => (
<div key={x.id}>
<TabFrag
id={x.id}
key={x.id}
activeTab={prevTab}
render={x.render}
/>
</div>
))}
</>
);
}
return <Spinner />;
};
const TabFrag = props => {
if (props.id === props.activeTab) {
return props.render;
}
return null;
};
const TabHeader = props => {
const {tabs, activeTab, loadingTab, onTabChange} = props;
return (
<div className="TabHeader">
{tabs.map(x => (
<TabItem
id={x.id}
key={x.id}
name={x.name}
active={x.id === activeTab}
loading={x.id === loadingTab}
onTabChange={onTabChange}
/>
))}
</div>
);
};
const TabItem = props => {
const {id, name, loading, active, onTabChange} = props;
const handleTabChange = () => {
onTabChange(id);
};
return (
<div
className={classNames("TabItem", {
ActiveTab: active,
LoadingTab: loading,
})}
onClick={handleTabChange}
>
{name}
</div>
);
};

Resources