React: Cannot read properly 'map' of null

Working on a React app where you can search through an API and add favorites. I had the search working, but when I added the favorites method I am now getting a “TypeError: Cannot read property ‘map’ of null” error in my MovieList.js, pointing out my <React.Fragment> code.

Here is my App.js

import React, { useState, useEffect } from 'react';
import MovieList from './components/MovieList';
import MovieListHeading from './components/MovieListHeading';
import SearchBox from './components/SearchBox';
import AddFavorites from './components/AddFavorites';
import RemoveFavorites from './components/RemoveFavorites';
import 'bootstrap/dist/css/bootstrap.min.css';
import './App.css';


const App = () => {
  const [movies, setMovies] = useState([]);
  const [favorites, setFavorites] = useState([]);
  const [searchValue, setSearchValue] = useState('');

  const getMovieRequest = async(searchValue) => {
    const url = `http://www.omdbapi.com/?s=${searchValue}&apikey=c5171cf5`
    const response = await fetch(url);
    const responseJson = await response.json();

    if(responseJson.Search){
      setMovies(responseJson.Search);
    }
  };

  useEffect(() => {
    getMovieRequest(searchValue);
  }, [searchValue]);

  useEffect(()=> {
    const movieFavorites = JSON.parse(localStorage.getItem('app-favorites')
      );
    setFavorites(movieFavorites);

  }, []);

  const saveToLocalStorage = (items) => {
    localStorage.setItem('app-favorites', JSON.stringify(items));
  };

  const addFavoriteMovie = (movie) => {
    const newFavoriteList = [...favorites, movie];
    setFavorites(newFavoriteList);
    saveToLocalStorage(newFavoriteList);
  };

  const removeFavoriteMovie = (movie) => {
    const newFavoriteList = favorites.filter((favorite)=> favorite.imdbID !== movie.imdbID
      );


    setFavorites(newFavoriteList);
    saveToLocalStorage(newFavoriteList);
  };

  return (
    <div className='container-fluid movie-app'>
      <div className='row d-flex align-items-center mt-4 mb-4'>
        <MovieListHeading heading='Movies' />
        <SearchBox searchValue={searchValue} setSearchValue={setSearchValue} />
      </div>

     <div className='row'>
        <MovieList
          movies={movies}
          handleFavoritesClick={addFavoriteMovie}
          favouriteComponent={AddFavorites}
        />
      </div>
      <div className='row d-flex align-items-center mt-4 mb-4'>
        <MovieListHeading heading='Favorites' />
      </div>
      <div className='row'>
        <MovieList
          movies={favorites}
          handleFavouritesClick={removeFavoriteMovie}
          favouriteComponent={RemoveFavorites}
        />
      </div>
    </div>
  );
};


export default App;

And my MovieList.js

import React from 'react';

const MovieList = (props) => {
  const FavoriteComponent = props.favoriteComponent;

  return (
    <React.Fragment>
      {props.movies.map((movie, index) => (
      <div className="image-container d-flex justify-content-start m-3">
        <img src={movie.Poster} alt='movie' />
      <div onClick={()=>props.handleFavoritesClick(movie)} className="overlay d-flex align-items-center justify-content-center"></div>
        <FavoriteComponent />
      </div>
      ))}
    </React.Fragment>
  );
};

export default MovieList;

When I console.log(props.movies); it comes back as “props is undefined”. I don’t get this, I had it working earlier and could search through movies. Is something incorrect with the code I added for the Favorites function?

Here is the entirety of my app in a Sandbox: https://codesandbox.io/s/pensive-sea-p4dus

Hi

You do zJSON.parsez for value by key from localStorage, but value may be empty and after JSON.parse(null) will be Error. Try try/catch or check value and do JSON.parse after valid value by key.

I think you should do that:

useEffect(()=> {
  try {
     const movieFavorites = JSON.parse(localStorage.getItem('app-favorites'));
     setFavorites(movieFavorites);
  }  catch(e) {}
}, []);

I tried that code but it’s not returning anything. I have the empty array in the code in case JSON.parse(null) but that doesn’t seem to be working.

Error message has gone? If yes you should write json to local storage and setFavorites will work

I still have the same error message with “cannot properly map of null”, but I am not seeing anything displayed using the try/catch method on the JSON local storage.

It happens because your props.movies in MovieList.js have undefined value. It will happen if you do setFavorites(undefined|null). I see your component’s code and see it problem place:

useEffect(()=> {
    const movieFavorites = JSON.parse(localStorage.getItem('app-favorites')
      );
    setFavorites(movieFavorites);

  }, []);

I see that you have a default value for favorites as empty array [], but you rewrite it in useEffect and your value will be different value, it value is null.

I hope i resolved your problem.

useEffect is supposed to save the setFavorites(movieFavorites) into an empty array. I still don’t understand how my props.movies is undefined. I don’t have my setFavorites as (undefined|null), I have it set as movieFavorites.

What exactly needs to be changed with my useEffect? I don’t get why it is having an effect on my MovieList component.

No, setFavorites(movieFavorites) not write value to empry value, it revrite your value [] to null. If you have not data by key app-favorites in page localStorage localStorage.getItem('app-favorites') will be null and JSON.parse(null) will be Error.

Use here try {} catch() {} for exeption and if (movieFavorites).

I run your code and resolved problem. Correct useEffect is:

useEffect(()=> {
    try {
      const movieFavorites = JSON.parse(localStorage.getItem('app-favorites'));

      if (movieFavorites) {
        setFavorites(movieFavorites);
      }
    }  catch(e) {}
  }, []);

I added if condition for check that movieFavorites is not null

Hi @Fork. It is actually not recommended to define logic for performing external effects outside useEffect. It is advisable to define it inside useEffect like.

useEffect(() => {
    const getMovieRequest = async(searchValue) => {
    const url = `http://www.omdbapi.com/?s=${searchValue}&apikey=c5171cf5`
    const response = await fetch(url);
    const responseJson = await response.json();

    if(responseJson.Search){
      setMovies(responseJson.Search);
    }
  };
    getMovieRequest(searchValue);
  }, [searchValue]);

I would also recommend you look at how you are updating searchValue. Are you sure you are not updating searchValue in onChange event handler. Try console logging the data fetched to see what the value is. I suspect you are triggering a call to the API on every key stroke. Unfortunately I wasn’t able to play with your code on codesandbox because it appears you forgot to save the code in some files so I get some errors.

1 Like

Can’t you just do:

const movieFavorites = JSON.parse(localStorage.getItem("app-favorites")) || [];
1 Like

I tried @lasjorg 's method originally but I got unexpected end of JSON input.

If I use @vladimirschneider if statement, my page shows up. I took @nibble 's advice and defined my code inside useEffect. However, when I search it crashes and gives me an “element type invalid” error for MovieList.js. When I console log searchValue it returns each key stroke. However I intended it to show the results while your searching so you don’t have to enter in your entire query and hit send, so it is supposed to log each keystroke.

So the problem is solved?

Not yet, when I search the app crashes and gives me an “element type invalid” error.

Can you give full exception message?

The error is below, it is the same one I got earlier.

Error: Element type is invalid: expected a string (for built-in components) or a class/function (for composite components) but got: undefined. You likely forgot to export your component from the file it’s defined in, or you might have mixed up default and named imports. Check the render method of MovieList.

getMovieRequest

src/App.js:24

  21 |   const responseJson = await response.json();  22 |   23 |   if(responseJson.Search){> 24 |     setMovies(responseJson.Search);     | ^  25 |   }  26 | };  27 |   getMovieRequest(searchValue);

Here is my revised code for App.js per the suggestions in this thread.

import React, { useState, useEffect } from 'react';
import MovieList from './components/MovieList';
import MovieListHeading from './components/MovieListHeading';
import SearchBox from './components/SearchBox';
import AddFavorites from './components/AddFavorites';
import RemoveFavorites from './components/RemoveFavorites';
import 'bootstrap/dist/css/bootstrap.min.css';
import './App.css';


const App = () => {
  const [movies, setMovies] = useState([]);
  const [favorites, setFavorites] = useState([]);
  const [searchValue, setSearchValue] = useState('');


  useEffect(() => {
    const getMovieRequest = async(searchValue) => {
    const url = `http://www.omdbapi.com/?s=${searchValue}&apikey=c5171cf5`
    const response = await fetch(url);
    const responseJson = await response.json();

    if(responseJson.Search){
      setMovies(responseJson.Search);
    }
  };
    getMovieRequest(searchValue);
  }, [searchValue]);

  console.log(searchValue);

  useEffect(()=> {
    try {
      const movieFavorites = JSON.parse(localStorage.getItem('app-favorites'));

      if (movieFavorites) {
        setFavorites(movieFavorites);
      }
    }  catch(e) {}
  }, []);


  const saveToLocalStorage = (items) => {
    localStorage.setItem('app-favorites', JSON.stringify(items));
  };

  const addFavoriteMovie = (movie) => {
    const newFavoriteList = [...favorites, movie];
    setFavorites(newFavoriteList);
    saveToLocalStorage(newFavoriteList);
  };

  const removeFavoriteMovie = (movie) => {
    const newFavoriteList = favorites.filter(
      (favorite) => favorite.imdbID !== movie.imdbID
      );


    setFavorites(newFavoriteList);
    saveToLocalStorage(newFavoriteList);
  };

  return (
    <div className='container-fluid movie-app'>
      <div className='row d-flex align-items-center mt-4 mb-4'>
        <MovieListHeading heading='Movies' />
        <SearchBox searchValue={searchValue} setSearchValue={setSearchValue} />
      </div>

     <div className='row'>
        <MovieList
          movies={movies}
          handleFavoritesClick={addFavoriteMovie}
          favouriteComponent={AddFavorites}
        />
      </div>
      <div className='row d-flex align-items-center mt-4 mb-4'>
        <MovieListHeading heading='Favorites' />
      </div>
      <div className='row'>
        <MovieList
          movies={favorites}
          handleFavouritesClick={removeFavoriteMovie}
          favouriteComponent={RemoveFavorites}
        />
      </div>
    </div>
  );
};


export default App;

As long as you try to use empty components (like you have in the Codesandbox) you will get that error. Without seeing all the code, i.e. with all the components actually having code in them, we can’t really test your code.

Here it is with all the empty components commented out and two fixes (ignore the thumbnail for some reason it is not updating).

  1. https for the fetch URL

  2. Using || for localStorage.

Well, and a key for the map, but that’s about it. I can test the search because the component is empty.

Hi, I have the same error , but with this code run perfect!

useEffect(() => {
		try {
			const movieFavourites = JSON.parse(
			localStorage.getItem('react-movie-app-favourites')) || [];
			setFavourites(movieFavourites);
		 } catch(e){}		
	}, []);

Thanks to vladimirschneider and sorry for my bad english!

1 Like