简体   繁体   中英

How to render a icon dynamically using React Redux

I'm creating a playlist and I want to show my "favorite tracks" with a different icon, like a heart filled, by default render a bordered heart. Basically the default code is working but when refresh the page, the icon filled is gone and render the default. And if anyone has a tip, i'm new in react and dont know if I can have many "useStates" like that.

I forgot to mention, the app are using Redux-persist, so the info continues in the store after the page refresh. That is why I want show the icon filled based on info at the store

import React, { useState, useEffect } from "react";
import ReactDOMServer from "react-dom/server";
import axios from "axios";
import { useSelector, useDispatch } from "react-redux";
import { ListContainer, PlayerStyle, InfoBox } from "./style.jsx";
import FavoriteBorderIcon from "@material-ui/icons/FavoriteBorder";
import FavoriteIcon from "@material-ui/icons/Favorite";
import PlayCircleOutlineIcon from "@material-ui/icons/PlayCircleOutline";
import PauseCircleOutlineIcon from "@material-ui/icons/PauseCircleOutline";
import MusicPlayer from "../MusicPlayer/index";

const List = () => {
  const [isLoading, setLoading] = useState(true);
  const [valueList, setValueList] = useState(20);
  const [search, setSearch] = useState("");
  const [dataList, setDataList] = useState([]);
  const [trackList, setTrackList] = useState([]);
  const [bannerAlbum, setBannerAlbum] = useState("");
  const [createdBy, setCreatedBy] = useState("");
  const [isPlayed, setIsPlayed] = useState(false);
  const [musicPlayed, setMusicPlayed] = useState("");
  const [showTrackList, setShowTrackList] = useState(true);
  const [showFavoriteList, setShowFavoriteList] = useState(false);
  const [favoriteData, setFavoriteData] = useState([]);

  const favoriteList = useSelector(
    (state) => state.FavoriteListReducer.favorites
  );
  const dispatch = useDispatch();

  let chartPlaylist = `https://api.deezer.com/playlist/1111141961?&limit=${valueList}`;

  useEffect(() => {
    axios.get(chartPlaylist).then((res) => {
      // console.log(res.data, "album");
      setDataList(res.data);
      setTrackList(res.data.tracks.data);
      setBannerAlbum(res.data.picture_medium);
      setCreatedBy(res.data.creator.name);
      // Ao terminar a requisição dos dados, mostra os componentes
      setLoading(false);
    });
  }, [chartPlaylist]);

  useEffect(() => {
    axios.all(favoriteList.map((l) => axios.get(l))).then(
      axios.spread(function (...res) {
        // all requests are now complete
        setFavoriteData(res);
      })
    );
    if (!showTrackList && favoriteList.length === 0) {
      setShowTrackList(true);
      setShowFavoriteList(false);
    }
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [favoriteList]);

  const handleInput = (e) => {
    setSearch(e.target.value);
  };

  const handlePlayed = () => {
    if (!isPlayed) {
      setIsPlayed(true);
    } else {
      setIsPlayed(false);
    }
  };
  const handleIconSwap = (e) => {
    e.currentTarget.firstElementChild.innerHTML = ReactDOMServer.renderToString(
      !isPlayed ? <PlayCircleOutlineIcon /> : <PauseCircleOutlineIcon />
    );
  };

  const Add_fav = (e) => {
    dispatch({
      type: "ADD_FAV",
      newId: `https://api.deezer.com/track/${e.currentTarget.getAttribute(
        "value"
      )}`,
    });

    e.currentTarget.firstElementChild.innerHTML = ReactDOMServer.renderToString(
      favoriteList.includes(e.currentTarget.getAttribute("value")) ? (
        ""
      ) : (
        <FavoriteIcon style={{ color: "red" }} />
      )
    );
  };

  const Del_fav = (e) => {
    dispatch({
      type: "DELETE_FAV",
      remove: `https://api.deezer.com/track/${e.currentTarget.getAttribute(
        "value"
      )}`,
    });

    e.currentTarget.firstElementChild.innerHTML = ReactDOMServer.renderToString(
      favoriteList.includes(e.currentTarget.getAttribute("value")) ? (
        ""
      ) : (
        <FavoriteBorderIcon fontSize={"inherit"} />
      )
    );
  };

  // eslint-disable-next-line no-array-constructor
  const handleFav = (e) => {
    favoriteList.includes(
      `https://api.deezer.com/track/${e.currentTarget.getAttribute("value")}`
    )
      ? Del_fav(e)
      : Add_fav(e);
  };

  const toggleData = () => {
    if (showTrackList && favoriteList.length > 0) {
      setShowTrackList(false);
      setShowFavoriteList(true);
    }
    if (!showTrackList) {
      setShowTrackList(true);
      setShowFavoriteList(false);
    }
  };

  if (isLoading) {
    return <div>Loading...</div>;
  }

  return (
    <>
      <button
        onClick={() => {
          if (valueList < 100) {
            setValueList(valueList + 20);
          }
        }}
      >
        adicionar +20 musicas
      </button>
      <br />

      <br />
      <br />
      <button
        onClick={() => {
          toggleData();
        }}
      >
        Mostrar Favoritos
      </button>
      <InfoBox>
        <input
          type="text"
          value={search}
          onChange={(e) => setSearch(e.target.value)}
        />
        <div className="content">
          <img src={bannerAlbum} alt="" />
          <div className="Info--desc">
            <h1>{dataList.title}</h1>
            <div>
              <span>criado por: </span>
              {createdBy}
            </div>
          </div>
        </div>
      </InfoBox>
      <ListContainer>
        <div className="headerList">
          <div className="rank">#</div>
          <div className="favorite--icon">
            {/* <FavoriteBorderIcon fontSize={"inherit"} /> */}
          </div>
          <div className="title--music">FAIXA</div>
          <div className="title--artist">ARTISTA</div>
          <div className="title--album">ALBUM</div>
          <div className="title--duration">D.</div>
        </div>

        <div className="bodyList">
          {showTrackList &&
            trackList.map((item, key) => {
              return (
                <>
                  <div
                    key={key}
                    onMouseEnter={handleIconSwap}
                    onMouseLeave={(e) => {
                      if (!isPlayed) {
                        e.currentTarget.firstElementChild.innerHTML = key + 1;
                      } else {
                        e.currentTarget.firstElementChild.innerHTML =
                          ReactDOMServer.renderToString(
                            <PauseCircleOutlineIcon />
                          );
                      }
                    }}
                  >
                    <div
                      className="rank"
                      onClick={() => setMusicPlayed(trackList[key].preview)}
                    >
                      {key + 1}
                    </div>
                    <div className="favorite--icon">
                  // Here that has to dynamically render 
                      <span value={item.id} onClick={(e) => handleFav(e)}>
                        <Icon favIco={false} />
                      </span>
                    </div>
                    <div className="title--music">
                      <a target="_blank" rel="noreferrer" href={item.link}>
                        {item.title}
                      </a>
                    </div>
                    <div className="title--artist">{item.artist.name}</div>
                    <div className="title--album">{item.album.title}</div>
                    <div className="title--duration">
                      {item.duration / 60 < 10
                        ? "0" +
                          (item.duration / 60)
                            .toFixed(2)
                            .toString()
                            .replace(".", ":")
                        : (item.duration / 60)
                            .toFixed(2)
                            .toString()
                            .replace(".", ":")}
                    </div>
                  </div>
                </>
              );
            })}
        </div>
      </ListContainer>
    </>
  );
};

const Icon = (props) => {
  switch (props.favIco) {
    case true:
      return <FavoriteIcon style={{ color: "red" }} />;
    case false:
      return <FavoriteBorderIcon fontSize={"inherit"} />;
    default:
      return <FavoriteBorderIcon fontSize={"inherit"} />;
  }
};

export default List;

My Reducer store is working fine, just not render the heart filled on load the page, if I click at heart and remove the info from the store and click again the icon change to filled again.

const initialState = {
  favorites: [],
};

const List = (state = initialState, action) => {
  switch (action.type) {
    case "ADD_FAV":
      return { ...state, favorites: [...state.favorites, action.newId] };
    case "DELETE_FAV":
      let index = state.favorites.indexOf(action.remove);
      if (index > -1) {
        state.favorites.splice(index, 1);
      }
      return {
        ...state,
        favorites: [...state.favorites],
      };
    default:
  }

  return state;
};

export default List;

Thank you all

I've seen in your comment above that you're currently using redux-persist to keep the store between reloads, so having access to the data is not the problem.

Looking your code I think the problem might be with the way you're rendering the icons.

If I understood correctly, that is done by the Add_fav function:

const Add_fav = (e) => {
  dispatch({
    type: "ADD_FAV",
    newId: `https://api.deezer.com/track/${e.currentTarget.getAttribute(
      "value"
    )}`,
  });

  e.currentTarget.firstElementChild.innerHTML = ReactDOMServer.renderToString(
    favoriteList.includes(e.currentTarget.getAttribute("value")) ? (
      ""
    ) : (
      <FavoriteIcon style={{ color: "red" }} />
    )
  );
};

But this function only runs when you add a new favorite - hence the absence of the icons after a reload, because this part of the code would not be ran at all.

My suggestion would be to change the icon rendering logic to the return JSX of the component, instead of inside the event handler function. Something like this:

<span value={item.id} onClick={(e) => handleFav(e)}>
  { 
    favoriteList.includes(item.id) 
    ? <FavoriteIcon style={{ color: "red" }} /> 
    :  <FavoriteBorderIcon fontSize={"inherit"} /> 
  }
</span>

So figured out I was send to the store was a string and at the render logic was searching for a int number, then a edited the function add the method parseInt()

const Add_fav = (e) => {
  dispatch({
    type: "ADD_FAV",
    newId: parseInt(e.currentTarget.getAttribute("value")),
  });
};

and now works fine bacause the logic is searching for a Number Int.

<span value={item.id} onClick={(e) => handleFav(e)}>
  { 
    favoriteList.includes(item.id) 
    ? <FavoriteIcon style={{ color: "red" }} /> 
    :  <FavoriteBorderIcon fontSize={"inherit"} /> 
  }
</span>

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM