Home > database >  Changing one item in a mapped array
Changing one item in a mapped array

Time:04-12

I want to show a pause icon only on the track that's playing. For nonplaying tracks, show a play icon.

useEffect(() => {
if (!artistId) return setTracks([])
if (!accessToken) return
let cancel = false
spotifyApi.getArtistTopTracks(artistId, 'US').then(res => {
  let icon = value ? '/icons8-pause-30.png' : '/icons8-play-30.png'
  if (cancel) return
  setTracks(res.body.tracks.map((track) => {

If value === true, grab that one track and make its icon '/icons8-pause-30.png'. And for every other track, make the icons remain '/icons8-play-30.png' Right now, every item's icon is either play or pause.

    return (
      <div onClick={() => setPlayingTrack(track)}>
      <button className='track' onClick={toggleValue} key={track.id}>
        <img className='icon' src={icon} />
        <TrackImg src={track.album.images[0].url} />
        <TrackName>{track.name}</TrackName>
      </button>
      </div>
    )
  }))
})
}, [artistId, accessToken, value, playingTrack])

CodePudding user response:

You can check this working implementation that makes use of napster api instead of spotify, ( the logic is the same once you retrieve the array of tracks ), to see how to manage this scenario.
You are going to save at state the currentIndex of the song that is chosen, and an isPlaying state toggled when the track is played or paused, this way you can check wheter each track is playing or not idx === currentIdx && isPlaying and add styles, icons, etc... accordingly, in a declarative way, you can check the code and working example https://stackblitz.com/edit/react-audio-ref-classic-test-fix?file=src/App.js:

// App.js

const API_KEY = 'MWNjYjY1NGQtMTU1NS00YTM0LWFlZWItYWZkYzdiMTJiZTll';
const URL = 'https://api.napster.com/v2.2/artists/Art.28463069/tracks/top';

export default function App() {
  const [tracks, setTracks] = useState([]);
  const [currentSongIdx, setCurrentSongIdx] = useState(-1);
  const [isPlaying, setIsPlaying] = useState(false);
  const audioRef = useRef();

  //FETCH TRACKS
  useEffect(() => {
    fetchNapster();

    async function fetchNapster() {
      const res = await fetch(URL, {
        headers: {
          apikey: API_KEY,
        },
      });
      const data = await res.json();
      setTracks(
        data.tracks.map(({ albumName: name, previewURL: src }) => ({
          name,
          src,
        }))
      );
    }
  }, []);

  //HANDLERS

  function handleNextSong() {
    setCurrentSongIdx((curr) => (curr   1 === tracks.length ? 0 : curr   1));
  }
  function handlePrevSong() {
    setCurrentSongIdx((curr) => (curr - 1 < 0 ? tracks.length - 1 : curr - 1));
  }

   function handlePlay() {
    audioRef.current.play();
    setIsPlaying(true);
  }

  function handlePause() {
    audioRef.current.pause();
    setIsPlaying(false);
  }

  useEffect(() => {
    audioRef.current?.addEventListener('canplay', handlePlay);
    return () => audioRef.current?.removeEventListener('canplay', handlePlay);
  }, []);

  return (
    <div>
      <h4>CURRENT SONG:</h4>
      <h5>{tracks[currentSongIdx]?.name}</h5>
      <Controls
        handleNextSong={handleNextSong}
        handlePrevSong={handlePrevSong}
        handlePlay={handlePlay}
        handlePause={handlePause}
        audioRef={audioRef}
      />
      <div>
        {tracks?.map(({ name }, idx) => (
          <TrackInfo
            key={name   idx}
            isSelected={idx === currentSongIdx}
            data={{ name }}
            setCurrentSongIdx={setCurrentSongIdx}
            idx={idx}
          />
        ))}
      </div>
      ;
      <Audio src={tracks[currentSongIdx]?.src} audioRef={audioRef} />
    </div>
  );
}

// Audio.js

function Audio({ src, audioRef }) {
  return (
    <>
      <audio ref={audioRef} src={src} />
    </>
  );
}

// TrackInfo.js  -- This is where you show info and apply styles, icons, etc...

function TrackInfo({
  data: { name },
  isSelected,
  setCurrentSongIdx,
  idx,
  isPlaying,
}) {
  const icons = ['▶️', '⏹️'];

  const handleTrackClick = () => {
    setCurrentSongIdx(idx);
  };

  return (
    <div>
      <span>{icons[isPlaying && isSelected ? 1 : 0]}</span>
      <span
        onClick={handleTrackClick}
        style={{ color: isSelected ? 'green' : 'red', cursor: 'pointer' }}
      >
        ALBUM: {name}
      </span>
    </div>
  );
}


// Controls.js

function Controls({
  handleNextSong,
  handlePrevSong,
  handlePlay,
  handlePause,
  audioRef
}) {
  const [perc, setPerc] = useState(0);
  //ADD LISTENERS

  useEffect(() => {
    const handleUpdate = ({ target }) =>
      setPerc(calcPerc(target.currentTime, target.duration));
    audioRef.current?.addEventListener("timeupdate", handleUpdate);
    return () =>
      audioRef.current?.removeEventListener("timeupdate", handleUpdate);
  }, []);

  return (
    <div>
      <button onClick={handlePlay}>PLAY</button>
      <button onClick={handlePause}>PAUSE</button>
      <button onClick={handleNextSong}>NEXT</button>
      <button onClick={handlePrevSong}>BACK</button>
      <span>{bar(perc)}</span>
    </div>
  );
}

function bar(perc) {
  return (
    <div
      style={{
        width: perc ? perc   "%" : 0,
        height: "20px",
        background: "#000"
      }}
    >
      <h5 style={{ color: "#FFF" }}>{perc}%</h5>
    </div>
  );
}

//HELPER

const calcPerc = (curr, total) => {
  return Math.floor((curr * 100) / total);
};

function Equalizer({ audioRef }) {
  const canvasRef = useRef(null);

  useEffect(() => {
    //NEED JUST A REF TO CANVAS AND TO THE AUDIO ELEMENTS
    const canvas = canvasRef.current;
    const audio = audioRef.current;
    //INIT CANVAS CONTEXT
    const canvasCtx = canvas.getContext("2d");
    canvas.width = window.innerWidth * 0.8;
    canvas.height = window.innerHeight * 0.6;
    //INIT AUDIO CONTEXT AND ANALYSER
    const context = new AudioContext();
    const analyser = new AnalyserNode(context, { fftsize: 2048 });
    //GET AUDIO SOURCE
    const source = context.createMediaElementSource(audio);
    source.connect(analyser);
    analyser.connect(context.destination);
    //DATA USED TO DRAW THE GRAPH
    const bufferLength = analyser.frequencyBinCount;
    const dataArray = new Uint8Array(bufferLength);
    //POPULATE THE dataArray
    analyser.getByteTimeDomainData(dataArray);
    //NAME OF ANIMATIONFRAME TO CALL TO CANCEL THE DRAWING
    let animation;

    audio.onplay = draw;
    audio.onpause = () => cancelAnimationFrame(animation);

    function draw() {
      console.log("PLAYING");
      animation = requestAnimationFrame(draw);

      analyser.getByteTimeDomainData(dataArray);

      canvasCtx.fillStyle = "rgb(200, 200, 200)";
      canvasCtx.fillRect(0, 0, canvas.width, canvas.height);

      canvasCtx.lineWidth = 2;
      canvasCtx.strokeStyle = "rgb(0, 0, 0)";

      canvasCtx.beginPath();

      var sliceWidth = (canvas.width * 1.0) / bufferLength;
      var x = 0;

      for (let i = 0; i < bufferLength; i  ) {
        var v = dataArray[i] / 100.0;
        var y = (v * canvas.height) / 2;

        if (i === 0) {
          canvasCtx.moveTo(x, y);
        } else {
          canvasCtx.lineTo(x, y);
        }

        x  = sliceWidth;
      }

      canvasCtx.lineTo(canvas.width, canvas.height / 2);
      canvasCtx.stroke();
    }

    return () => {
      audio.onplay = null;
      audio.onpause = null;
    };
  }, []);

  return <canvas ref={canvasRef} />;
}


CodePudding user response:

You need to make use of React's lifecycle functionality. Instead of setting icon as a let value you need to keep it in state and setState accordingly.

Add this to the top of your component:

const [icon, setIcon] = useState('/icons8-play-30.png');

Then in the useEffect you need to replace:

let icon = value ? '/icons8-pause-30.png' : '/icons8-play-30.png'

with

setIcon(value ? '/icons8-pause-30.png' : '/icons8-play-30.png')
  • Related