Search code examples
reactjsreduxreact-reduxreact-hooksreact-thunk

useEffect is causing an infinite Loop


I am getting an infinite loop and it is constantly fetching information from the API.

The issue can be solved if I wrap fetchTheMovie() inside an if statement but I do not understand the reason, any ideas or any better solution?

if (!movie.Title) {
  fetchTheMovie(id);
}

The endpoint is /movie/id

Movie Component:

const SingleMovie = (props) => {
  const { id } = useParams();
  const {movie} = props;

  useEffect(() => {
    const {fetchTheMovie} = props;
    fetchTheMovie(id);
  }, []);
}

export default connect(
  mapStateToProps,
  mapDispatchToProps
)(WithSpinner(SingleMovie));

WithSpinner Component:

const WithSpinner = (WrappedComponent) => {
  const Spinner = ({ isLoading, ...otherProps }) => {
    return isLoading ? (
      <SpinnerOverlay>
        <SpinnerContainer />
      </SpinnerOverlay>
    ) : (
      <WrappedComponent {...otherProps} />
    );
  };
  return Spinner;
};

Page that the component is used

const Page = ({ loading }) => {
  const { id } = useParams();
  return (
    <div>
      <SingleMovieComp isLoading={loading} />
    </div>
  );
};

Action:

export function fetchMovieStart() {
  return {
    type: SingleMovieActionTypes.FETCH_MOVIE_START,
  };
}

export function fetchMovieSuccess(movie) {
  return {
    type: SingleMovieActionTypes.FETCH_MOVIE_SUCCESS,
    payload: movie,
  };
}

export function fetchMovieError(error) {
  return {
    type: SingleMovieActionTypes.FETCH_MOVIE_ERROR,
    payload: error,
  };
}

export const fetchMovieAsync = (movieId) => {
  return (dispatch) => {
    dispatch(fetchMovieStart());
    fetch(`URL`)
      .then((response) => response.json())
      .then((movie) => dispatch(fetchMovieSuccess(movie)))
      .catch((error) => dispatch(fetchMovieError(error.message)));
  };
};

Reducer:

const INITIAL_STATE = {
  loading: false,
  movie: {},
};

const singleMovieReducer = (state = INITIAL_STATE, action) => {
  switch (action.type) {
    case SingleMovieActionTypes.FETCH_MOVIE_START: {
      return {
        ...state,
        movie: {},
        loading: true,
      };
    }
    case SingleMovieActionTypes.FETCH_MOVIE_SUCCESS: {
      return {
        ...state,
        movie: action.payload,
        loading: false,
      };
    }
    case SingleMovieActionTypes.FETCH_MOVIE_ERROR: {
      return {
        ...state,
        loading: false,
      };
    }
    default:
      return state;
  }
};

Solution

  • You can try the following:

    const ComponentWithSpinner = WithSpinner(SingleMovie);
    //create single movie container
    const SingleMovieContainer = (props) => {
      const { id } = useParams();
      const { fetchTheMovie } = props;
      //remove the useEffect from SingleMovie
      useEffect(() => {
        fetchTheMovie(id);
        //you need to add these dependencies
      }, [fetchTheMovie, id]);
      //Here you could try to use useSelector to get the movie
      //  and pass to SingleMovie but I think you already do
      //  that somewhere else
      //return component with spinner so SingleMovieContainer
      // will not unmount when you load movie
      return <ComponentWithSpinner {...props} />;
    };
    
    export default connect(
      mapStateToProps,
      mapDispatchToProps
    )(SingleMovieContainer);
    

    Problem is that SingleMovie is unmounted when you load a movie and gets mounted when loading is false, that causes loading to be set true (fetchMovie is dispatched) so it is unmounted again.