'How can I use the same setState for two different tasks?

EDIT*** THE CODE IN THE SNIPPET IS NOT MEANT TO BE RUN BUT ONLY DISPLAY MY CODE, SORRY FOR THE CONFUSION

I am working a slider for an e-commerce app. Making each slider map over my useState array to create the different cards and the useState is receiving data from my fetched JSON server.

Now I am stuck at a brick wall in figuring how to use the same state to handle rendering the image onto my slider & click on the arrow to show the new slide.

enter image description here

I realize that I should update the state but I feel that I am over (or under) thinking things on how to do so.

My code snippets will show one, my code & two my JSON server data.

I receive an "uncaught error: too many re-renders." React limits number of renders to prevent an infinite loop

Can somebody please walk me through what I need to do here to complete this task? Thanks!

import {useState, useEffect} from 'react';
import { ArrowLeftOutlined, ArrowRightOutlined } from "@material-ui/icons";

import styled from "styled-components";
import { set } from 'react-hook-form';

const Container = styled.div`
    width: 100%;
    height: 95vh;
    display: flex;
    // background-color: #b3f0ff;
    position: relative;
    overflow: hidden;
`;
    const Arrow = styled.div`
    width: 50px;
    height: 50px;
    background-color: #e6ffff;
    border-radius: 50%;
    display: flex;
    align-items: center;
    justify-content: center;
    position: absolute;
    top: 0;
    bottom: 0;
    left: ${(props) => props.direction === "left" && "10px"};
    right: ${(props) => props.direction === "right" && "10px"};
    margin: auto;
    cursor: pointer;
    opacity: 0.5;
    z-index: 2;
`;
const Wrapper = styled.div`
    height: 100%;
    display: flex;
    transform: translateX(${props => props.slideIndx.data2 * -100}vw);
`
const Slide = styled.div`
    width: 100vw;
    height: 100vw;
    display: flex;
    align-items: center;
    background-color: ${props => props.bg};
`
const ImgContainer = styled.div`
    height: 100%;
    flex:1;
`
const Image = styled.img`
    padding-left: 30px;
    align-items: left;
`
const InfoContainer = styled.div`
    height: 80%;
    flex:1;
    padding: 30px;
`
const Title = styled.h1`
    font-size: 50px
`
const Desc = styled.p`
    margin: 50px 0px;
    font-size: 20px;
    font-weight: 500;
    letter-spacing: 3px;
`
const Button = styled.button`
    padding: 10px;
    font-size: 20px;
    background-color: transparent;
    cursor: pointer;
`


const Slider = () => {
    const [slideIndx, setSlideIndx] = useState({data1:[], data2: 0});


    const handleClick = (direction) => {
        if(direction === "left"){
            setSlideIndx(slideIndx.data2 > 0 ? slideIndx.data2 - 1 : 2);
        } else{
            setArrowIndx(slideIndx.data2 < 2 ? slideIndx.data2 + 1 : 0);
        }
    };


    const newArray = []
    setSlideIndx((slideIndx) => ({
        ...slideIndx,
        data2: [...newArray]
    }));


    const fetchSliderItems = (id) => {
        fetch('http://localhost:3000/sliderItems')
        .then(resp => resp.json())
        .then(data => {
            console.log(data)
            setSlideIndx(data)
        })
    }
    useEffect(() => {fetchSliderItems()}, [])


  return (
    <Container>
        <Arrow direction="left" onClick={() => handleClick("left")}>
            <ArrowLeftOutlined />
        </Arrow>
        <Wrapper slideIndx={slideIndx.data2}>
        {slideIndx.data1.map((item) => (
            <Slide bg={item.bg}>
            <ImgContainer>
                <Image src={item.img}/>
            </ImgContainer>
            <InfoContainer>
                <Title>{item.title}</Title>
                <Desc>{item.desc}</Desc>
                <Button>SHOP NOW</Button>
            </InfoContainer>
            </Slide>
        ))}
        </Wrapper>

        <Arrow direction="right" onClick={() => handleClick("right")}>
            <ArrowRightOutlined />
        </Arrow>

    </Container>
  )
}

export default Slider
{
  "sliderItems": [

    {
        "id": 1,
        "img": "../images/model1.png",
        "title": "SPRING CLEANING",
        "desc": "DONT MISS OUR BEST COLLECTION YET! USE #FLATIRON10 TO RECEIVE 10% OFF YOUR FIRST ORDER",
        "bg": "#b3ecff"
      },
      {
        "id": 2,
        "img": "../images/model2.png",
        "title": "SHOW OFF HOW YOU DRESS",
        "desc": "WITH OUR HUGE SELECTION OF CLOTHES WE FIT ALL YOUR STYLING NEEDS",
        "bg": "#ccf2ff"
      },
      {
        "id": 3,
        "img": "../images/model3.png",
        "title": "POPULAR DEALS",
        "desc": "RECEIVE FREE SHIPPING ON ALL ORDERS OVER $50!",
        "bg": "#fe6f9ff"
      }
  ]
}


Solution 1:[1]

It is totally possible to do this with one useState, two useStates and my personal preference, useReducer.

I'll just give an example. I haven't tried running this code, as I don't have the components. So treat it like pseudo code:

const initialState = {
  selectedIndex: 0,
  "sliderItems": [
    {
      "id": 1,
      "img": "../images/model1.png",
      "title": "SPRING CLEANING",
      "desc": "DONT MISS OUR BEST COLLECTION YET! USE #FLATIRON10 TO RECEIVE 10% OFF YOUR FIRST ORDER",
      "bg": "#b3ecff"
    },
    {
      "id": 2,
      "img": "../images/model2.png",
      "title": "SHOW OFF HOW YOU DRESS",
      "desc": "WITH OUR HUGE SELECTION OF CLOTHES WE FIT ALL YOUR STYLING NEEDS",
      "bg": "#ccf2ff"
    },
    {
      "id": 3,
      "img": "../images/model3.png",
      "title": "POPULAR DEALS",
      "desc": "RECEIVE FREE SHIPPING ON ALL ORDERS OVER $50!",
      "bg": "#fe6f9ff"
    },
  ],
}

function reducer(state, action) {
  switch (action.type) {
    case 'setData':
      return {
        selectedIndex: 0,
        sliderItems: action.sliderItems,
      };
    case 'slideRight':
      return {
        ...state,
        // this assumes you want to go back to index 0 if you slide right on last item
        selectedIndex: state.sliderItems.length - state.selectedIndex > 1 ? state.selectedIndex + 1 : 0,
      };
    case 'slideLeft':
      return {
        ...state,
        // this assumes you want to go to last item if sliding left on first item
        selectedIndex: state.selectedIndex === 0 ? state.sliderItems.length - 1 : state.selectedIndex - 1,
      };
    default:
      return state;
  }
}
const Example = () => {
  const [{ sliderItems, selectedIndex }, dispatch] = useReducer(initialState, reducer);

  return (
    <Container>
      <Arrow direction="left" onClick={() => dispatch({ type: 'slideLeft' })}>
        <ArrowLeftOutlined />
      </Arrow>
      <Wrapper slideIndx={selectedIndex}>
      {sliderItems.map((item) => (
        <Slide bg={item.bg}>
          <ImgContainer>
            <Image src={item.img}/>
          </ImgContainer>
          <InfoContainer>
            <Title>{item.title}</Title>
            <Desc>{item.desc}</Desc>
            <Button>SHOP NOW</Button>
          </InfoContainer>
        </Slide>
      ))}
      </Wrapper>
      <Arrow direction="right" onClick={() => dispatch({ type: 'slideRight' })}>
        <ArrowRightOutlined />
      </Arrow>
    </Container>
  )
}

If you choose to try this approach, just dispatch a setData action in your fetch handler. That will reset the state with new items and start at index 0. The behavior can of course be modified to your liking.

dispatch({
  type: 'setData',
  sliderItems: fetchResult.sliderItems,
})

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1