React: Check Array if string matches and set state - arrays

I have the following Array:
[
"/test/static/media/its9-odc_d.9d5de720.png",
"/test/static/media/its9-odc_m.178c1879.png",
"/test/static/media/its9-odc_w.5e70ca59.png",
"/test/static/media/its9-odc_y.8cf41473.png"
]
When I click on a Button, either _d, _m, _w or _y is saved in a React state (timeperiods).
I need a function which should save the string which matches my timeperiods in another React state (imageSource), so I can render the image.
This is what I tried so far but I always get returned 'yay' and when I try to setImageSource I get an error for infinite loop.
const imageForTimeperiod = () => {
images.forEach((img) => {
if (timeperiod && img.split('.')[0].includes(timeperiod)) {
console.log('yay');
// setImage(img);
}
})
};
{Object.entries(Timeperiods.timeperiods).map((entries) => {
return (
<Button onClick={() => setTimeperiod(entries[1].file)}>
{entries[0]}
</Button>
);
})}
In the end:
I click on Button 1 Day, it sets timeperiod to _d and I show the image /test/static/media/its9-odc_d.9d5de720.png.

You can pass the name of timeperiod on clicked button and then based on the name set the image and timePeriod state.
const { useState } = React;
const App = () => {
const [image, setImage] = useState(null);
const [timePeriod, setTimePeriod] = useState(null);
const images = [
"/test/static/media/its9-odc_d.9d5de720.png",
"/test/static/media/its9-odc_m.178c1879.png",
"/test/static/media/its9-odc_w.5e70ca59.png",
"/test/static/media/its9-odc_y.8cf41473.png"
];
const timePeriods = ['_d', '_m', '_w', '_y'];
const handleClick = (v) => {
const foundImage = images.find(name => name.includes(v));
setImage(foundImage || null);
setTimePeriod(v);
}
return <div>
{timePeriod}
<br />
{image && <img src={image} />}
<br />
{timePeriods.map((v, i) => {
return (
<button onClick={() => handleClick(v)}>
{v}
</button>
)
})}
</div>
}
ReactDOM.render(
<App />,
document.getElementById("root")
)
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.1/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.1/umd/react-dom.production.min.js"></script>
<div id="root"></div>

Related

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 toggle class of a single element in a .map() function?

I am trying to toggle a class for a specific element inside a loop.
const ItemList: React.FC<ListItemUserProps> = (props) => {
const { items } = props;
const [showUserOpt, setShowUserOpt] = useState<boolean>(false);
function toggleUserOpt() {
setShowUserOpt(!showUserOpt);
}
const userOptVisible = showUserOpt ? 'show' : 'hide';
return (
<>
{items.map((t) => (
<React.Fragment key={t.userId}>
<div
className={`item ${userOptVisible}`}
role="button"
tabIndex={0}
onClick={() => toggleUserOpt()}
onKeyDown={() => toggleUserOpt()}
>
{t.userNav.firstName}
</div>
</React.Fragment>
))}
</>
);
};
export default ItemList;
When I click on an element, the class toggles for every single one.
You can create another component that can have it's own state that can be toggled without effecting other sibling components' state:
Child:
const ItemListItem: React.FC<SomeInterface> = ({ item }) => {
const [show, setShow] = useState<boolean>(false);
const userOptVisible = show ? "show" : "hide";
const toggleUserOpt = (e) => {
setShow((prevState) => !prevState);
};
return (
<div
className={`item ${userOptVisible}`}
role="button"
tabIndex={0}
onClick={toggleUserOpt}
onKeyDown={toggleUserOpt}
>
{item.userNav.firstName}
</div>
);
};
Parent:
const ItemList: React.FC<ListItemUserProps> = ({ items }) => {
return (
<>
{items.map((t) => (
<ItemListItem key={t.userId} item={t} />
))}
</>
);
};
If you simply adding classes to the element, I would keep it simple and use a handler to toggle the class using pure JS.
const handleClick = (e) => {
// example of simply toggling a class
e.currentTarget.classList.toggle('selected');
};
Demo:
const {
useState,
} = React;
// dummy data
const data = Array(20).fill(null).map((i, index) => `item ${(index + 1).toString()}`);
function App() {
const [items, setItems] = useState(data);
const handleClick = (e) => {
e.currentTarget.classList.toggle('selected');
};
return (
<div>
{items.map((item) => (
<button key={item} onClick={handleClick}>{item}</button>
))}
</div>
);
}
ReactDOM.render( <
App / > ,
document.getElementById("app")
);
.selected {
background: red;
}
<script crossorigin src="https://unpkg.com/react#17/umd/react.production.min.js"></script>
<script crossorigin src="https://unpkg.com/react-dom#17/umd/react-dom.production.min.js"></script>
<div id="app"></div>
I think it'd be best if you kept track of the index so that you could target a single item in your list. As it stands the boolean is going to change the styling for all as you haven't specified which one should get the className.
Add a useState hook to keep track of it like:
const [activeIndex, setActiveIndex] = useState(null);
Then create a new function:
function handleIndexOnClick(index) {
setActive(index);
}
Then in your map() function add index. You'll then need to pass index in to you className attribute and the onClick function. The end result for that bit should look like:
{items.map((t, index) => (
<React.Fragment key={t.userId}>
<div
className={`item ${activeIndex && items[activeIndex] ? 'show' : 'hide }`}
role="button"
tabIndex={0}
onClick={() => handleIndexOnClick(index)}
onKeyDown={() => toggleUserOpt()}
>
{t.userNav.firstName}
</div>
</React.Fragment>
))}

How do I make one component talk to another when they are generated with .map() in React?

I have multiple components with the same module using map().
list.map((data, index) => <MyComponent key={index} value={d}/>)
Then <p> in each of MyComponent changes colors from green to red when it is clicked.
const MyComponent = ({value}) => {
const [clicked, setClicked] = useState(false);
const buttonOnClick = () => {
setClicked(true);
}
return (
<div>
<p style={clicked ? {color: 'green'} : {color: 'red'}}>{value}</p>
<button onClick={buttonOnClick}>click</button>
</div>
);
};
In this case, I would like to turn color of <p> in other MyComponent red when one of them are clicked.
How can I check the <p> state of other MyComponent?
You need to pass a callback into your child component, and have your parent component to store and control the state. Here's an example:
const list = [1, 2, 3];
const MyComponent = ({ value, clickedValue, onClick }) => {
const style = { color: clickedValue === value ? 'green' : 'red' };
return (
<div>
<p style={style}>{value}</p>
{/* Callback with the value */}
<button onClick={() => onClick(value)}>click</button>
</div>
);
};
const App = () => {
const [clickedValue, setClickedValue] = React.useState();
const handleClick = value => {
setClickedValue(value);
};
return React.Children.toArray(
list.map(value => (
<MyComponent
value={value}
clickedValue={clickedValue}
onClick={handleClick}
/>
))
);
}
ReactDOM.render(
<App />
, document.querySelector('#app'));
<script src="https://unpkg.com/react#16.7.0-alpha.0/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom#16.7.0-alpha.0/umd/react-dom.development.js"></script>
<div id="app"></div>
Probably the easiest option would be to lift state up into the parent component: https://reactjs.org/docs/lifting-state-up.html

Best way to change screen in React when URL not updated

I have an app I'm building in React where the url never updates despite each screen taking up the entire page.
What is the best way to render each screen when it's the screen to be viewed?
export const App = () => {
const [activeScreen, setActiveScreen] = useState("PAGE_1");
const goToScreen2 = () => setActiveScreen("PAGE_2");
const goToScreen3 = () => setActiveScreen("PAGE_3");
return (
<Wrapper>
{activeScreen === "PAGE_1" &&
<Page1 nextScreen={goToScreen2} />
}
{activeScreen === "PAGE_2" &&
<Page2 nextScreen={goToScreen3} />
}
{activeScreen === "PAGE_3" &&
<Page3 />
}
</Wrapper>
);
};
I would do something like this:
function Page1() {
return 'Page 1'
}
function Page2() {
return 'Page 2'
}
function Page3() {
return 'Page 3'
}
function Pager({
next,
prev,
children
}) {
return (
<div>
<button onClick={prev}>Prev</button>
{children}
<button onClick={next}>Next</button>
</div>
)
}
const pages = [Page1, Page2, Page3]
const App = () => {
const [activeScreen, setActiveScreen] = React.useState(0);
const goNext = () => setActiveScreen((activeScreen + 1) % pages.length);
const goBack = () => setActiveScreen((activeScreen - 1 + pages.length) % pages.length);
const ActivePage = pages[activeScreen]
return (
<Pager next={goNext} prev={goBack}>
<ActivePage />
</Pager>
);
};
ReactDOM.render( <App /> , document.querySelector('#root'))
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.1/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.1/umd/react-dom.production.min.js"></script>
<div id="root" />

Resources