0

I am trying to make a slideshow app by passing the following data to Slide.js via props.

const SLIDES = [
    {
        title: "Today's workout plan",
        text: "We're gonna do 3 fundamental exercises."
    },
    {
        title: "First, 10 push-ups",
        text: "Do 10 reps. Remember about full range of motion. Don't rush."
    },
    {
        title: "Next, 20 squats",
        text: "Squats are important. Remember to keep your back straight."
    },
    {
        title: "Finally, 15 sit-ups",
        text: "Slightly bend your knees. Remember about full range of motion."
    },
    {
        title: "Great job!",
        text: "You made it, have a nice day and see you next time!"
    }
];

My child component Slide.js has the following code

import React, { useState, useEffect } from "react";

function Slides({ slides }) {
  const [slideShow, setSlide] = useState(slides);

  useEffect(() => {
    setSlide(slideShow[0]);
    console.log("slides", slideShow[0]);
  }, []);

  return (
    <div>
      <div id="navigation" className="text-center">
        <button data-testid="button-restart" className="small outlined">
          Restart
        </button>
        <button data-testid="button-prev" className="small">
          Prev
        </button>
        <button data-testid="button-next" className="small">
          Next
        </button>
      </div>
      {slideShow.map((slide, i) => (
        <div id={i} className="card text-center">
          <h1 data-testid="title">{slide.title}</h1>
          <p data-testid="text">{slide.text}</p>
        </div>
      ))}
    </div>
  );
}

export default Slides;

I thought using setSlide(slideShow[0]) would only render the first item in the array of objects after the app initially loads. But I get

TypeError: slideShow.map is not a function

But when I console.log("slides", slideShow[0]) I output

slides {title: "Today's workout plan", text: "We're gonna do 3 fundamental exercises."}

And if I change my code to

setSlide(slideShow.slice(0, 1));

The app shows only the first object in the array but if I console.log("slides", slideShow), I get the whole array of objects even though I thought I should receive the sliced version.

Is my map function not referencing the slideShow[0] for the error to occur?

Sam
  • 1,765
  • 11
  • 82
  • 176
dev_el
  • 2,357
  • 5
  • 24
  • 55
  • 2
    When you `setSlide(slideShow[0])` your `slideShow` is no longer an array that can be mapped over, it's an object. In the code you've given us, there's no reason to have the `useEffect` (or `useState`) hooks at all – Tom Oct 15 '20 at 04:34
  • State changes in React are asynchronous. You cannot `console.log` on the next line and see the change. Log the state in the main body of the function – Jayce444 Oct 15 '20 at 04:34
  • 1
    *Is my map function not referencing the slideShow[0] for the error to occur?*, Yes. In `useEffect` you are doing `setSlide(slideShow[0])`which will be an object. So instead of `setSlide(slideShow[0])` this if you do `setSlide([{...slideShow[0]}])`, then it'll not break the app. – Nithish Oct 15 '20 at 04:35

3 Answers3

2

The problem over here is setSlide(slideShow[0]). Initially the state is initialised with an array called slides. But in the useEffect that state is being updated with an object. So, now the slideShow is no more an array but an object. Hence object.map is breaking the app.

  useEffect(() => {
    setSlide(slideShow[0]);
    console.log("slides", slideShow[0]);
  }, []);

Below is an example of the kind of operations which you are looking for. Instead of storing the slides in the state I just added index of the item in the state and updating it accordingly.

const SLIDES = [{title:"Today's workout plan",text:"We're gonna do 3 fundamental exercises."},{title:"First, 10 push-ups",text:"Do 10 reps. Remember about full range of motion. Don't rush."},{title:"Next, 20 squats",text:"Squats are important. Remember to keep your back straight."},{title:"Finally, 15 sit-ups",text:"Slightly bend your knees. Remember about full range of motion."},{title:"Great job!",text:"You made it, have a nice day and see you next time!"}];

const { useState, useEffect } = React;

function Slides({ slides }) {
  const [selectedIndex, setSelectedIndex] = useState(0);

  const previous = () => {
    if(selectedIndex !== 0) {
      setSelectedIndex(index => index-1);
    }
  }
  
  const next = () => {
    if(selectedIndex !== slides.length - 1) {
      setSelectedIndex(index => index+1);
    }
  }
  
  const restart = () => {
    setSelectedIndex(0);
  }
  
  return (
    <div>
      <div id="navigation" className="text-center">
        <button data-testid="button-restart" className="small outlined" onClick={restart}>
          Restart
        </button>
        <button data-testid="button-prev" className="small" onClick={previous}>
          Prev
        </button>
        <button data-testid="button-next" className="small" onClick={next}>
          Next
        </button>
      </div>
      <div className="card text-center">
        <h1 data-testid="title">{slides[selectedIndex].title}</h1>
        <p data-testid="text">{slides[selectedIndex].text}</p>
      </div>
    </div>
  );
}

ReactDOM.render(<Slides slides={SLIDES}/>, document.getElementById("react"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.0/umd/react-dom.production.min.js"></script>


<div id="react"></div>

Note: Here, for all the three operations we can just have one single function, just for simplicity I have created three different functions for updating the slide index.

Nithish
  • 5,393
  • 2
  • 9
  • 24
1

The error is referencing this line slideShow.map((slide, i) => (

The problem is that your useEffect is called after your component renders, and you set slideshow to be a single slide slideShow[0]. You then proceed to re-render your component, and you try to map over slideShow, but slideshow is equal to the first item of your original slideshow variable (slideshow[0]) and you can't map over a single slide.


You would probably want to edit your code to be something more like

function Slides({ slides }) {
  const [slideIdx, setSlide] = useState(0);

  const updateIndex = (val) => {
      if (val === -1){
          if (slideIdx === 0){
              setSlide(slides.length -1)
          }else{
              setSlide(slideIdx - 1)
          }
      }
      if (val === 1){
          if (slideIdx === slides.length - 1){
              setSlide(0)
          }else{
              setSlide(slideIdx + 1)
          }
      }
  }

  return (
    <div>
      <div id="navigation" className="text-center">
        <button onClick={() => setSlide(0)} data-testid="button-restart" className="small outlined">
          Restart
        </button>
        <button onClick={() => updateIndex(-1)} data-testid="button-prev" className="small">
          Prev
        </button>
        <button onClick={() => updateIndex(1)} data-testid="button-next" className="small">
          Next
        </button>
      </div>
        <div id="Whatyouwant" className="card text-center">
          <h1 data-testid="title">{slides[slideIdx].title}</h1>
          <p data-testid="text">{slides[slideIdx].text}</p>
        </div>
    </div>
  );
}

Here's an answer where I tried to implement something similar

Sam
  • 1,765
  • 11
  • 82
  • 176
  • I tried implementing your solution but I get `TypeError: Cannot read property 'title' of undefined` – dev_el Oct 15 '20 at 04:47
  • @ElijahLee I updated some stuff. It's probably not an exact solution, you'll probably have to work with it a bit – Sam Oct 15 '20 at 04:53
0

useState is async by nature but doesn't return a promise, but to solve the asynchronous nature of useState and a quick dirty fix would be to just put a null propagation on your data e.g.

slideshow?.map

but how I would do it would be something like this: Just throwing some ideas for you

import SLIDES from ....slides.etc

const [slideIndex, setSlideIndex]=useState(0)

<item onClick={()=>{setSlideIndex(slideIndex + 1)}} >
{SLIDES[slideIndex]}
</item>

just an example to give you an idea

Kimimomo
  • 99
  • 1
  • 8