Share State between two specific instances of the same react component React - javascript

Before y'all say global state(redux), I'd like to say one thing. I'm mapping through an array I fetched from my API. I receive images and map over them and render my Slider component. Every 2 sliders must share the same state. So, then if i move to the next slide in the first slider, then the second slider must also go to the next slide(but not any other slides). If I move to the next slide in the 5th slider, the 6th must also move to the next slide... so on.
Component where I map over slides:
<div className='image-grid'>
{screenshots.map((imagesByResolution, resIdx, screenshotResArr) => {
return imagesByResolution.map((img, scriptIdx, screenshotScriptsArr) => {
return <Slider slides={formattedSlides} />;
});
})}
</div>
Slider:
import Button from '#material-ui/core/Button';
import MobileStepper from '#material-ui/core/MobileStepper';
import { useTheme } from '#material-ui/core/styles';
import KeyboardArrowLeft from '#material-ui/icons/KeyboardArrowLeft';
import KeyboardArrowRight from '#material-ui/icons/KeyboardArrowRight';
import React from 'react';
import SwipeableViews from 'react-swipeable-views';
import { autoPlay } from 'react-swipeable-views-utils';
import { encodeImage } from '../services/images';
import useStyles from '../styles/slider';
const AutoPlaySwipeableViews = autoPlay(SwipeableViews);
export interface ISlide {
title: string;
img: ArrayBuffer;
}
interface Props {
slides: ISlide[];
}
export default function Slider(props: Props) {
console.log(props);
const { slides } = props;
const classes = useStyles();
const theme = useTheme();
const [activeSlide, setActiveSlide] = React.useState(0);
const maxSlides = slides.length;
const handleNext = () => {
setActiveSlide((prevActiveStep) => prevActiveStep + 1);
};
const handleBack = () => {
setActiveSlide((prevActiveStep) => prevActiveStep - 1);
};
const handleSlideChange = (step: number) => {
setActiveSlide(step);
};
return (
<div className={classes.root}>
<div className={classes.header}>
<h4 className={classes.title}>{slides[activeSlide].title}</h4>
</div>
<AutoPlaySwipeableViews
axis={theme.direction === 'rtl' ? 'x-reverse' : 'x'}
index={activeSlide}
onChangeIndex={handleSlideChange}
enableMouseEvents
>
{slides.map((slide, index) => (
<div key={index}>
{Math.abs(activeSlide - index) <= 2 ? (
<img className={classes.img} src={encodeImage(slide.img, 'image/png')} alt={slide.title} />
) : null}
</div>
))}
</AutoPlaySwipeableViews>
<MobileStepper
steps={maxSlides}
position='static'
variant='text'
activeStep={activeSlide}
nextButton={
<Button size='small' onClick={handleNext} disabled={activeSlide === maxSlides - 1}>
Next
{theme.direction === 'rtl' ? <KeyboardArrowLeft /> : <KeyboardArrowRight />}
</Button>
}
backButton={
<Button size='small' onClick={handleBack} disabled={activeSlide === 0}>
{theme.direction === 'rtl' ? <KeyboardArrowRight /> : <KeyboardArrowLeft />}
Back
</Button>
}
/>
</div>
);
}
If this is not possible using either some global state management library or plain ol' react state, what is the other alternative? Thanks in advance!

Pass a unique key prop to each instance of your component.
Credits: https://stackoverflow.com/a/65654818/9990676

Related

react button conditional rendering not working properly

working on a cart app in a udemy course - the problem is when the quantity gets bought it supposed to make the button disabled but its not working, only showing the add to cart button without disabling it when quantity are zero
data.countInStock seems not to be updating
import { Button } from 'react-bootstrap';
import { Card } from 'react-bootstrap';
import { Link } from 'react-router-dom';
import React, { useContext } from 'react';
import Rating from './Rating';
import axios from 'axios';
import { Store } from '../Store';
function Product(props){
const {product} = props;
const {state , dispatch:ctxDispatch} = useContext(Store);
const {cart: {cartItems}} = state
const addToCartHandler = async (item )=>{
const existItem = cartItems.find((x)=> x._id === product._id);
const quantity = existItem ? existItem.quantity+1:1 ;
const {data} = await axios.get(`/api/products/${item._id}`);
if(data.countInStock < quantity){
window.alert('sorry product is out of stock')
return;
}
ctxDispatch({
type:'CART_ADD_ITEM'
, payload:{...item , quantity},
});
};
return(
<Card>
<Link to={`/product/${product.slug}`}>
<img src={product.image} className="card-img-top" alt={product.name} />
</Link>
<Card.Body>
<Link to={`/product/${product.slug}`}>
<Card.Title>{product.name}</Card.Title>
</Link>
<Rating rating={product.rating} numReviews={product.numReviews} />
<Card.Text>${product.price}</Card.Text>
{ product.countInStock === 0 ? (
<Button color="light" disabled={true} > Out of stock</Button>
):(
<Button onClick={() => addToCartHandler(product)}>Add to cart</Button>
)}
</Card.Body>
</Card>
)}
it's not showing the button out of stock when quantity gets used, What's wrong with the code?
full code: https://github.com/basir/mern-amazona/commit/12e565bf6e1859b963729eaba46a5352962fe9e1
full code with backend : https://github.com/basir/mern-amazona/tree/12e565bf6e1859b963729eaba46a5352962fe9e1
Maybe this could start you out. There's no need to make 2 buttons. You can just manipulate the state of the button using your logic
const isOutOfStock = product.countInStock === 0
const buttonText = isOutOfStock ? "Out of stock" : "Add to cart"
<Button color="light" disabled={isOutOfStock} onClick={() => addToCartHandler(product)}>{buttonText}</Button>

Next/image doesn't load my src image when I use a map

I use next/image to load my images in my app. It works fine except for a carousel with multiple images.
When I do like this I have that error :
Error: Image is missing required "src" property. Make sure you pass "src" in props to the next/image component. Received: {}
The problem is not because I have an entity without any file
image.js
import { getStrapiMedia } from "../utils/medias"
import NextImage from "next/image"
const Image = (props) => {
if (!props.media) {
return <NextImage {...props} />
}
const { url, alternativeText } = props.media
const loader = ({ src }) => {
return getStrapiMedia(src)
}
return (
<NextImage
loader={loader}
layout="responsive"
objectFit="contain"
width={props.media.width}
height={props.media.height}
src={url}
alt={alternativeText || ""}
/>
)
}
export default Image
Carousel.js
import React, { useCallback } from "react"
import useEmblaCarousel from "embla-carousel-react"
import NextImage from "./Image"
export const EmblaCarousel = (product) => {
const [emblaRef, emblaApi] = useEmblaCarousel()
useEmblaCarousel.globalOptions = { loop: true }
const scrollPrev = useCallback(() => {
if (emblaApi) emblaApi.scrollPrev()
}, [emblaApi])
const scrollNext = useCallback(() => {
if (emblaApi) emblaApi.scrollNext()
}, [emblaApi])
return (
<div className="embla" ref={emblaRef}>
<div className="embla__container">
{product.gallery.map((_gallery) => (
<div key={_gallery.id}>
<NextImage media={_gallery.image} className="embla__slide" />
</div>
))}
</div>
<button
className="hidden md:inline embla__prev mr-2"
onClick={scrollPrev}
>
Prev
</button>
<button
className="hidden md:inline embla__next ml-2"
onClick={scrollNext}
>
Next
</button>
</div>
)
}
export default EmblaCarousel
The issue is
if (!props.media) {
return <NextImage {...props} />
}
in your custom Image component. When the media prop is falsy like undefined or null, you're passing everything else to NextImage but that everything else doesn’t include src prop which is mandatory for next Image component. Also your url extraction is dependent on media prop to be truthy and have a property called url. Can be seen from the next line :-
const { url, alternativeText } = props.media
And you intend to pass this url to src as can be seen from your usage. Either you can return null when media is falsy or you can filter out those items in your list where media prop is falsy and then map on it.
Not sure if you ever found an answer for this but I was running into the same issue and noticed that when looping through the multiple images object from Strapi the object is slightly different than with single images.
To fix this issue I supplied it to the getStrapiMedia() function in the same way it expects single images i.e:
{aboutpage?.attributes.shareCta.images.data.slice(0, 4).map((image) => (
<div key={image.id} className="relative h-64 w-full">
<Image
layout="fill"
objectFit="cover"
placeholder="blur"
blurDataURL={blurDataUrl}
src={
getStrapiMedia({ data: image }) ||
"/images/placeholders/image-placeholder.png"
}
/>
</div>
));
}
Hope this helps and kind regards
Replace NextImage with Image
import { getStrapiMedia } from "../utils/medias"
import Image from "next/image"
const NextImage = (props) => {
if (!props.media) {
return <Image {...props} />
}
const { url, alternativeText } = props.media
const loader = ({ src }) => {
return getStrapiMedia(src)
}
return (
<Image
loader={loader}
layout="responsive"
objectFit="contain"
width={props.media.width}
height={props.media.height}
src={url}
alt={alternativeText || ""}
/>
)
}
export default NextImage
Carousel.js
import React, { useCallback } from "react"
import useEmblaCarousel from "embla-carousel-react"
import NextImage from "./Image"
export const EmblaCarousel = (product) => {
const [emblaRef, emblaApi] = useEmblaCarousel()
useEmblaCarousel.globalOptions = { loop: true }
const scrollPrev = useCallback(() => {
if (emblaApi) emblaApi.scrollPrev()
}, [emblaApi])
const scrollNext = useCallback(() => {
if (emblaApi) emblaApi.scrollNext()
}, [emblaApi])
return (
<div className="embla" ref={emblaRef}>
<div className="embla__container">
{product.gallery.map((_gallery) => (
<div key={_gallery.id}>
<NextImage media={_gallery.image} className="embla__slide" />
</div>
))}
</div>
<button
className="hidden md:inline embla__prev mr-2"
onClick={scrollPrev}
>
Prev
</button>
<button
className="hidden md:inline embla__next ml-2"
onClick={scrollNext}
>
Next
</button>
</div>
)
}
export default EmblaCarousel

How to control react-bootstrap carousel with custom buttons?

here i need to control carousel slides with custom buttons i achieved this with the help of this Example.
This is the code for it..
import React, { useState } from "react";
import { Carousel, Button, Container, Row } from "react-bootstrap";
import Cards from "../cards";
import "./slider.css";
const Slider = () => {
const [index, setIndex] = useState(0);
const handleSelect = (selectedIndex, e) => {
setIndex(selectedIndex);
};
const onPrevClick = () => {
if (index > 0) {
setIndex(index - 1);
} else if (index === 0) setIndex(2);
};
const onNextClick = () => {
if (index === 2) {
setIndex(0);
} else if (index === 0 || index > 0) setIndex(index + 1);
};
return (
<>
<div className="button-container">
<Container>
<Row>
<Button variant="primary" onClick={onPrevClick}>
Previous
</Button>
<Button variant="primary" onClick={onNextClick}>
Next
</Button>
</Row>
</Container>
</div>
<Carousel
activeIndex={index}
onSelect={handleSelect}
indicators={false}
controls={false}
>
<Carousel.Item>
<Cards />
</Carousel.Item>
<Carousel.Item>
<Cards />
</Carousel.Item>
<Carousel.Item>
<Cards />
</Carousel.Item>
</Carousel>
</>
);
};
export default Slider;
But when the active index is in last item that means in the last carousel slide. When i press the next button, it moves all the way around from last carousel to first carousel, i.e the direction is like 3 -> 2 -> 1.
I know that something i made terrible logic in the onClick functions. So i am seeking help to suggest me the best way to control carousel slides with custom buttons , your help is much appreciated. Please review my code and tell me any suggestions. Thanks in advance.
And here is the code sandbox link
Using the ref property you can control the prev and next actions
const ref = useRef(null);
const onPrevClick = () => {
ref.current.prev();
};
const onNextClick = () => {
ref.current.next();
};
JSX
<Carousel
ref={ref}
...
>

onClick event binding (not a button)

I have a small React app that makes calls to a JSON file and returns the text of a card.
I have successfully made this work by calling the onClick on a button, but what I really want to do is add a onClick as an event to my Cards module I've created. I've looked at binding at this level but with no success;
<Cards name={card} onClick={() => setCard(Math.floor(Math.random() * 57) + 1)}/>
Do I need to write a custom handler in my cards.js to handle the onClick? Or can I append it within this file (App.js)?
The full code is below.
import React, { useState } from 'react';
import './App.css';
import Cards from '../cards';
function App() {
const [card, setCard] = useState('1');
return (
<div className="wrapper">
<h1>Card text:</h1>
<button onClick={() => setCard(Math.floor(Math.random() * 57) + 1)}>Nile</button>
<Cards name={card} />
</div>
);
}
export default App;
My card.js component code is;
export default function Cards({ name }) {
const [cardInformation, setCards] = useState({});
useEffect(() => {
getCards(name)
.then(data =>
setCards(data)
);
}, [name])
const FlippyOnHover = ({ flipDirection = 'vertical' }) => (
<Flippy flipOnHover={true} flipDirection={flipDirection}>
</Flippy>
);
return(
<div>
<Flippy flipOnHover={false} flipOnClick={true} flipDirection="vertical" style={{ width: '200px', height: '200px' }} >
<FrontSide style={{backgroundColor: '#41669d',}}>
</FrontSide>
<BackSide style={{ backgroundColor: '#175852'}}>
{cardInformation.continent}
</BackSide>
</Flippy>
<h2>Card text</h2>
<ul>
<li>{cardInformation.continent}</li>
</ul>
</div>
)
}
Cards.propTypes = {
name: PropTypes.string.isRequired
}
We can create an event. So let's call it onSetCard.
App.js file:
class App extends Component {
handleSetCard= () => {
// set state of card here
};
<Cards name={card}
card=card
onSetCard={this.handleSetCard}
/>
}
Cards.js file:
class Cards extends Component {
render() {
console.log("Cards props", this.props);
return (
<button
onClick={() => this.props.onSetCard(this.props.card)}
className="btn btn-secondary btn-sm m-2"
>
FooButton
</button>
)
}
}

Render Variable Styled Component From Context

Hi I am making a website building app that uses styled components and forms to render data into sections of a page. So I decided all of my styled components will be default exports then imported to a context and then loaded into the form when its opened. Then from a select menu the user gets a map of all the possible components to render and when an option is selected a string reference filters the component array down to the called function and then adds all the other data and then displays it on screen. When I launch I get this error: "React.jsx: type is invalid -- expected a string (for built-in components) or a class/function (for composite components) but got: < BurgerMenu / >. Did you accidentally export a JSX literal instead of a component?"
Here are all of the parts:
BurgerMenu.js
import React, { useState, useRef } from "react";
import { ThemeProvider } from "styled-components";
import { useOnClickOutside } from "../../state/useOnClickOutside";
import { GlobalStyles } from "../../state/globals";
import { useTheme } from "../../state/useTheme";
import Hamburger from "./Hamburger";
import Menu from "./Menu";
const BurgerMenu = () => {
const [open, setOpen] = useState(false);
const node = useRef();
useOnClickOutside(node, () => setOpen(false));
const { theme } = useTheme();
return (
<ThemeProvider theme={theme}>
<>
<GlobalStyles />
<div ref={node}>
<Hamburger open={open} setOpen={setOpen} />
<Menu open={open} setOpen={setOpen} />
</div>
</>
</ThemeProvider>
);
};
export default BurgerMenu;
Component Context:
import BurgerMenu from "../header/Hamburger/BurgerMenu";
const components = [{
name: "BurgerMenu",
func: BurgerMenu,
els: ["a", "a", "a", "a", "a", "a"],
}]
Site Form (where the component context is called)
const { components } = useComponentContext();
const [mount, setMount] = useState('')
<select name='component' onChange={(e) => setMount(e.target.value)}>
{components.map((component) => (
<option key={component.name} value={component.name}>
{component.name}
</option>
))}
</select>
<button
className='btn primary btn-block'
onClick={() => {
convertStringToComponent(mount);
setTimeout(setLoaded((prevState) => !prevState),2000)}}>
Add Component
</button>
Function convertStringToComponent
const convertStringToComponent = (mount, compStyle) => {
const ComponentName = components
.filter((comp) => comp.name === mount)
.map(({ func }) => func)[0];
return (
<ComponentName
content={content}
font={font}
pallet={pallet}
h={h}
icon={icon}
p={p}
vid={vid}
img={img}
a={a}
li={li}
button={button}></ComponentName>
);
};
const VariableComponent = convertStringToComponent(mount);
this is then called in a different component with
{loaded === true && <VariableComponent />}
Any help would be great!
The issue is with how you are using convertStringToComponent's function reeturn valaue.
When you call, convertStringToComponent you are returned an instance of component which is
<ComponentName
content={content}
font={font}
pallet={pallet}
h={h}
icon={icon}
p={p}
vid={vid}
img={img}
a={a}
li={li}
button={button}></ComponentName>
Now when rendering you are trying to again create an instance eout of this by using it as
{loaded === true && <VariableComponent />}
instead of
{loaded === true && VariableComponent}
However, there is another issue, when you call convertStringToComponent you shouldn't try to store the result in a variable and export it, instead you should be storing it in instance state and render it.
A better way to structure your code would be
export const convertStringToComponent = (mount, compStyle) => {
const ComponentName = components
.filter((comp) => comp.name === mount)
.map(({ func }) => func)[0];
return (otherProps) => (
<ComponentName
{...otherProps}
content={content}
font={font}
pallet={pallet}
h={h}
icon={icon}
p={p}
vid={vid}
img={img}
a={a}
li={li}
button={button}></ComponentName>
);
};
Now you can use it like
const [VariableComponent, setVariableComponent] = useState(null);
<button
className='btn primary btn-block'
onClick={() => {
const comp = convertStringToComponent(mount);
setVariableComponent(comp);
setTimeout(setLoaded((prevState) => !prevState),2000)}}>
Add Component
</button>
{isLoaded && <VariableComponent />}

Categories