Search code examples
reactjsreact-hooksreact-reduxreact-context

Passing object to Reducer is undefined - React


I am following a tutorial on Context API to implement into a project that gets movie details from an API and have struggled to pass the objects into the Reducer. The app involves passing the object from an onClick function but the object is always undefined.

GlobalState.jsx

import React, { createContext, useReducer} from "react";
import AppReducer from "./AppReducer";

const initialState = {
  watchlist: [],
};

export const GlobalContext = createContext(initialState);

export const GlobalProvider = (props) => {
  const [state, dispatch] = useReducer(AppReducer, initialState);

  const addMovieToWatchlist = (movie) => {
    dispatch({ type: "ADD_TO_WATCHLIST", payload: movie });
  };

  return (
    <GlobalContext.Provider
      value={{ watchlist: state.watchlist, addMovieToWatchlist }}
    >
      {props.children}
    </GlobalContext.Provider>
  );
};

AppReducer.jsx

export default (state, action) => {
  switch (action.type) {
    case "ADD_TO_WATCHLIST":
      return {
        ...state,
        watchlist: [action.paylod, ...state.watchlist],
      };
    default:
      return state;
  }
};

MovieCard.jsx

import React, { useState, useEffect, useContext } from "react";
import { motion } from "framer-motion";
import { Link } from "react-router-dom";
import { GlobalContext } from "../context/GlobalState";

export const MovieCard = (props) => {
  const BACKDROP_IMAGE = "https://image.tmdb.org/t/p/w1280/";
  const MOVIE_DETAILS =
    "https://api.themoviedb.org/3/movie/" +
    props.id +
    "?api_key=77c4dbe7e827bbe92b720b38af14e9d5";

  const [details, setDetails] = useState([]);
  const { addMovieToWatchlist } = useContext(GlobalContext);

  const genreArray = [
    { id: 28, name: "Action" },
    { id: 12, name: "Adventure" },
    { id: 16, name: "Animation" },
    { id: 35, name: "Comedy" },
    { id: 80, name: "Crime" },
    { id: 99, name: "Documentary" },
    { id: 18, name: "Drama" },
    { id: 10751, name: "Family" },
    { id: 14, name: "Fanatsy" },
    { id: 36, name: "27" },
    { id: 27, name: "Horror" },
    { id: 10402, name: "Music" },
    { id: 9648, name: "Mystery" },
    { id: 10749, name: "Romance" },
    { id: 878, name: "Science Fiction" },
    { id: 10770, name: "TV Movie" },
    { id: 53, name: "Thriller" },
    { id: 10752, name: "War" },
    { id: 37, name: "Western" },
  ];

  useEffect(() => {
    fetch(MOVIE_DETAILS)
      .then((res) => res.json())
      .then((data) => {
        setDetails(data);
      });
  }, []);

  function findGenreName(array, genreId) {
    return array.find((element) => {
      return element.id === genreId;
    });
  }

  return details.backdrop_path && props.genre_ids ? (
    <motion.section
      className="cursor-pointer"
      initial={{ opacity: 0 }}
      whileInView={{ opacity: 1 }}
      transition={{ duration: 0.6 }}
      viewport={{ once: true }}
    >
      <div className="overflow-hidden mb-3 relative z-10">
        <Link
          to={`/movie/${props.title}`}
          state={{
            id: props.id,
            title: props.title,
            overview: props.overview,
            year: props.release_date.substring(0, 4),
          }}
        >
          <img
            src={BACKDROP_IMAGE + details.backdrop_path}
            alt={props.title}
            loading="lazy"
          />
        </Link>
        <motion.button
          type="button"
          whileHover={{ backgroundColor: "#BDFF00", color: "#0b0c0b" }}
          className="absolute bottom-5 right-5 text-light bg-dark.5 w-10 h-10 rounded-full flex items-center justify-center"
          onClick={() => addMovieToWatchlist(props)}
        >
          <span>+</span>
        </motion.button>
      </div>
      <div className="flex flex-col float-left w-36 ">
        <p className="text-xs text-light opacity-50">▶</p>
        <p className="text-xs text-light text-opacity-50">
          {details.runtime + " mins"}
        </p>
      </div>
      <div className="text-xs uppercase text-light float-left">
        <h4>{props.title}</h4>
        <h4 className="text-light text-opacity-50">
          {props.release_date.substring(0, 4)} /{" "}
          {props.genre_ids[0]
            ? findGenreName(genreArray, props.genre_ids[0]).name
            : " "}
        </h4>
      </div>
    </motion.section>
  ) : (
    ""
  );
};

Within the 'motion.button' is the onClick function that should add the movie object into the initial state but is always undefined when checking the React dev tools. I have surrounded my app.jsx in the GlobalProvider tags and everything that the tutorial mentions. Any help would be appreciated.


Solution

  • As stated before, you have a typo in paylod. Renaming it to payload should fix the issue.

    There is another thing I see which is a potential bug in your code.

    In the MovieCard.jsx component, you're passing the entire props to addMovieToWatchlist. I would suggest to pass the movie details instead. Make sure the details object contains the movie information you want to add to the watchlist which you set in setDetails.

    Instead of:

    onClick={() => addMovieToWatchlist(props)}
    

    Do this:

    onClick={() => addMovieToWatchlist(details)}