Search code examples
javascriptreactjsreduxreact-reduxredux-toolkit

How do I unsubscribe to the dispatch to grab new data onload on useEffect? - I'm using Redux Toolkit


I can load my data but only after I refresh the page. Until then, it shows the data from the previous item I clicked on. It's behaving like a cache would.

Here is my mediaSlice

import { createSlice, createAsyncThunk } from "@reduxjs/toolkit";
import axios from "axios";

const KEY = process.env.REACT_APP_API_KEY
const BASE_URL = process.env.REACT_APP_BASE_URL
const HBO_SINGLE_MEDIA_API = `${BASE_URL}/titlestest`

const initialState = {
  media:{},
  status: 'idle',  //'idle', 'loading', 'succeeded', 'failed'  
  error:null
}

export const fetchSingleMediaTitle = createAsyncThunk(
  'media/fetchSingleMediaTitle', 
  async (id) => { 
    const response = await axios.get(
      HBO_SINGLE_MEDIA_API,
      {
        headers: {
          'Content-Type': 'application/json',
          'X-API-KEY': KEY,
        },
        params: {
          titleId: id,
        }
      }
    )

    return response.data.Item;
  } 
)

const mediaSlice = createSlice({
  name: 'media',
  initialState,
  reducers:{},
  extraReducers: {
    [fetchSingleMediaTitle.pending]: () => {
      console.log("Pending");
    },
    [fetchSingleMediaTitle.fulfilled]: (state, { payload }) => {
      state.status = 'succeeded'
      state.media = payload
    },
    [fetchSingleMediaTitle.rejected]: () => {
      console.log("Rejected");
    },
  }
})

// SELECTORS
export const selectSingleMedia = (state) => state.media.media;

export const getMediaStatus = (state) => state.media.status;

export default mediaSlice.reducer

And then the Media Component has what you would expect

const [media, setMedia] = useState([]);   
const {id} = useParams();
const dispatch = useDispatch();

const singlemedia = useSelector((state) => selectSingleMedia(state, id))
const mediaStatus = useSelector(getMediaStatus)

useEffect(() => { 
  if (mediaStatus === 'idle') {
    dispatch(fetchSingleMediaTitle(id)) //yes, it's imported
  }
    
  setMedia(singlemedia); 

  //it returns the array with the data but not the current one
  console.log("singlemedia: ", singlemedia); 

  return () => {  };
  // Someone suggested removing the dependencies below but then it doesn't load anything.
}, [id, singlemedia, mediaStatus, dispatch, media_data])

I am at a loss here because as I understand it, the useEffect is supposed to fire onload and give me the current data. The ID is correct in the params but the state is not mutating.

Thanks in advance

EDIT For reference, here is the console log. Those console logs are in the useEffect. The API is slightly slower than the useEffect and the render is happening before the data is ready (race condition) or at least that's what I think it's happening here that's why it loads empty and then it loads again. But the confusing part is that ALL of that happens on a page refresh only. On a normal load the state is not empty is loaded and in time for the UI to render it (no race condition), only, it's loaded with the old data from a previous state shape

enter image description here

Here is the redux dev tools enter image description here


Solution

  • Your selector takes one argument, but you're passing it two. Change it to:

    const singlemedia = useSelector(selectSingleMedia);
    

    Second, your singlemedia is the state. There's no need to setMedia(singlemedia);.

    Your useEffect should look like:

    useEffect(() => { 
      if (mediaStatus === 'idle') {
        dispatch(fetchSingleMediaTitle(id));
      }
    }, [dispatch, id, mediaStatus]);
    

    Also, you should look into RTK Query, which would replace createAsyncThunk: https://redux-toolkit.js.org/tutorials/rtk-query

    Edit per our discussion:

    useEffect(() => { 
      dispatch(fetchSingleMediaTitle(id));
    }, [dispatch, id]);