Search code examples
javascriptreactjsreact-hooksuse-reduceruse-context

UseReduce Hook dispatch twice-ReactJS


I develop a small application with the purpose of studying react hooks. The application consists of writing the name of an artist in a section. In the next section, a similar form, to fill with albums of each artist.

I have all my components separate, as well as the context, status, and reducer. I have a separate context for each artist, albums, and songs. When I click on an artist button, I want to filter the albums so that only theirs appear. But use reducer dispatch twice and in console log it tells me that both come from reducer file.

Something interesting is that when I click the first time, I only go through once, but the second time I see that I select a dispatch artist twice.

Another interesting thing is that when I change the selected artist, it removes the state albums, which is where I have all the albums. It does not filter them but removes them.

GitHub repository

This is the form where I click to call the id of each artist and I sent the currently selected artist to artist context and album context

import React, { useContext } from "react";

import AlbumContext from "../../context/Album/AlbumContext";
import ArtistContext from "../../context/Artist/ArtistContext";

const Artist = ({ item }) => {
  const albumContext = useContext(AlbumContext);
  const { getArtist } = albumContext;

  const artistContext = useContext(ArtistContext);
  const { artist, getselectedArtist } = artistContext;

  const handleSelect = (artist_id) => {
    getArtist(artist_id);
    getselectedArtist(artist_id);
  };
  return (
    <>
      <div
        style={{ border: "1px solid black", margin: "10px", padding: "5px" }}
      >
        <p>{item.name}</p>
        <button type="button">Edit</button>
        <button type="button">Delete</button>
        <button type="button" onClick={() => handleSelect(item.id)}>
          Select
        </button>
      </div>
    </>
  );
};

export default Artist;

This second one is the ALBUMSTATE where I call the function getArtist to dispatch and get all the albums of that selected artist

import React, { useReducer } from "react";

import AlbumContext from "./AlbumContext";
import AlbumReducer from "./AlbumReducer";
import { GET_ARTIST, ADD_ALBUM, VALIDATE_FORM } from "../../types";

const AlbumState = (props) => {
  const initialState = {
    albums: [],
    errorform: false,
    selectedartist: "",
  };

  const [state, dispatch] = useReducer(AlbumReducer, initialState);

  //-----METHODS-----//
  const addAlbum = (album) => {
    dispatch({
      type: ADD_ALBUM,
      payload: album,
    });
  };

  const setError = (error) => {
    dispatch({
      type: VALIDATE_FORM,
      payload: error,
    });
  };

  const getArtist = (id) => {
    dispatch({
      type: GET_ARTIST,
      payload: id,
    });
  };

  return (
    <AlbumContext.Provider
      value={{
        selectedartist: state.selectedartist,
        albums: state.albums,
        errorform: state.errorform,
        addAlbum,
        setError,
        getArtist,
      }}
    >
      {props.children}
    </AlbumContext.Provider>
  );
};

export default AlbumState;

This is the ALBUMREDUCER

import { GET_ARTIST, ADD_ALBUM, VALIDATE_FORM } from "../../types";

export default (state, action) => {
  switch (action.type) {
    case ADD_ALBUM:
      return {
        ...state,
        albums: [action.payload, ...state.albums],
      };
    case VALIDATE_FORM:
      return {
        ...state,
        errorform: action.payload,
      };
    case GET_ARTIST:
      console.log(action.payload);
      return {
        ...state,
        selectedartist: action.payload,
        albums: state.albums.filter(
          (album) => album.artist_creator === action.payload
        ),
      };

      return {
        ...state,
        selectedartist: action.payload,
      };

    default:
      return state;
  }
};


Solution

  • There are two things you need to consider here

    • You are using React.strictMode in which during Development React intentianally calls dispatch method twice. According to react docs

    Strict mode can’t automatically detect side effects for you, but it can help you spot them by making them a little more deterministic. This is done by intentionally double-invoking the following functions:

    • Class component constructor, render, and shouldComponentUpdate methods
    • Class component static getDerivedStateFromProps method
    • Function component bodies
    • State updater functions (the first argument to setState)
    • Functions passed to useState, useMemo, or useReducer
    • Secondly you do not attach any albums with an artist, so your artist_creator for each album is null always and when you try to filter based on it will return an empty array
    case GET_ARTIST:
          console.log(action.payload, state.albums, "getArtistalbum");
          return {
            ...state,
            selectedartist: action.payload,
            albums: state.albums.filter(
              album => album.artist_creator === action.payload
            ) // This returns empty array
          };
    
    
    • Last thing to note here is that you are overriding original albums array in redux with the filtered one and hence all your albums data will be removed. Instead keep a separete key for filtered items and filter it like

      case GET_ARTIST: console.log(action.payload, state.albums, "getArtistalbum"); return { ...state, selectedartist: action.payload, selectedAlbums: state.albums.filter( album => album.artist_creator === action.payload ) // A different key for selected albums filtered from all albums };