Home > Software design >  Redux-Saga: TypeError: Cannot read properties of undefined (reading 'data')
Redux-Saga: TypeError: Cannot read properties of undefined (reading 'data')

Time:11-01

I was trying to run my Redux app with redux-saga.

Basically on my store.js I have the following codes:

import { applyMiddleware, createStore } from "redux";
import createSagaMiddleware from "redux-saga";
import logger from "redux-logger";

import rootReducer from "./reducers/rootReducer";
import rootSaga from "./sagas/userSagas";

const sagaMiddleware = createSagaMiddleware();

const middleware = [sagaMiddleware];

if (process.env_NODE_ENV === "development") {
  middleware.push(logger);
}

const store = createStore(rootReducer, applyMiddleware(...middleware));

sagaMiddleware.run(rootSaga);

export default store;

My usersApi.js looks something like this:

import axios from "axios";

export const loadUsersApi = async () => {
  await axios.get("http://localhost:5000/users");
};

And here is my userSagas:

import * as type from "../actionType";

import {
  take,
  takeEvery,
  takeLatest,
  put,
  all,
  delay,
  fork,
  call,
} from "redux-saga/effects";

import { loadUsersSuccess, loadUsersError } from "../actions/userAction";
import { loadUsersApi } from "../services/userApi";

export function* onLoadUsersStartAsync() {
  try {
    const response = yield call(loadUsersApi);
    if (response.status === 200) {
      yield delay(500);
      yield put(loadUsersSuccess(response.data));
    }
  } catch (error) {
    yield put(loadUsersError(error.response.data));
  }
}

export function* onLoadUsers() {
  yield takeEvery(type.LOAD_USERS_START, onl oadUsersStartAsync);
}

const userSagas = [fork(onLoadUsers)];

export default function* rootSaga() {
  yield all([...userSagas]);
}

When I run this on my HomePage.js file where I load and dispatch the data:

import React, { useEffect } from "react";
import { useDispatch, useSelector } from "react-redux";
import { loadUsersStart } from "../redux/actions/userAction";

export default function HomePage() {
  const dispatch = useDispatch();

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

  return (
    <div>
      <h1>Home</h1>
    </div>
  );
}

It gave me this error:

[HMR] Waiting for update signal from WDS...
index.js:1 TypeError: Cannot read properties of undefined (reading 'data')
    at onl oadUsersStartAsync (userSagas.js:25)
    at onl oadUsersStartAsync.next (<anonymous>)
    at next (redux-saga-core.esm.js:1157)
    at currCb (redux-saga-core.esm.js:1251)

index.js:1 The above error occurred in task onl oadUsersStartAsync
    created by takeEvery(LOAD_USERS_START, onl oadUsersStartAsync)
    created by onl oadUsers
    created by rootSaga
Tasks cancelled due to error:
takeEvery(LOAD_USERS_START, onl oadUsersStartAsync)

I am not sure what's causing this error, but even the logger of my app doesn't even show the actions being dispatched.

Any idea how can I fix this issue?

CodePudding user response:

You need to return promise from

export const loadUsersApi = () => {
  return axios.get("http://localhost:5000/users");
};

Maybe next time try to use typescript. It will prevent You from similar mistakes

CodePudding user response:

Try this

try {
    const response = yield call(await loadUsersApi);
    if (response.status === 200) {
      yield delay(500);
      yield put(loadUsersSuccess(response.data));
    }
  } catch (error) {
    yield put(loadUsersError(error.response.data));
  }

CodePudding user response:

Alternatively, you can build your redux without using redux-saga.

This is how I usually set them up:-

A. Reducer
  • /slices/auth.js
import { createSlice } from '@reduxjs/toolkit'

const initialState = {
  users: [],
  loading: false,
  success: false,
  error: false,
  message: ''
}

export const usersSlice = createSlice({
  name: 'users',
  initialState,
  // // The `reducers` field lets us define reducers and generate associated actions
  reducers: {
    setUsers: (state, action) => {
      // Redux Toolkit allows us to write "mutating" logic in reducers. It
      // doesn't actually mutate the state because it uses the Immer library,
      // which detects changes to a "draft state" and produces a brand new
      // immutable state based off those changes
      state.users = action.payload
    },
    setLoading: (state, action) => {
      state.loading = action.payload
    },
    setSuccess: (state, action) => {
      state.success = action.payload.status
      state.message = action.payload.message
    },
    setError: (state, action) => {
      state.error = action.payload.status
      state.message = action.payload.message
    }
  }
})

export const { setUsers, setLoading, setSuccess, setError, setMessage } = usersSlice.actions;

// The function below is called a selector and allows us to select a value from
// the state. Selectors can also be defined inline where they're used instead of
// in the slice file. For example: `useSelector((state: RootState) => state.counter.value)`
export const selectUsers = (state) => state.users.users
export const selectLoading = (state) => state.users.loading
export const selectSuccess = (state) => state.users.success
export const selectError = (state) => state.users.error
export const selectMessage = (state) => state.users.message

export default usersSlice.reducer;

B. Store

  • store.js
import { configureStore, getDefaultMiddleware } from '@reduxjs/toolkit'
import usersReducer from '../slices/users'

export const store = configureStore({
  reducer: {
    users: usersReducer
  },
  middleware: getDefaultMiddleware({
    serializableCheck: false
  })
})
C. App Component
import { Provider } from 'react-redux'
import { store } from '../app/store'

export default function App() {
  return {
    <>
      <Provider store={store}>
        {/* your content... */}    
      </Provider>
    </>
  }
}
D. Component (where you use the redux)
import { useContext, useEffect } from 'react'
import { useDispatch, useSelector } from 'react-redux'
import { selectUsers, selectLoading, selectSuccess, selectError, selectMessage, setUsers, setLoading, setSuccess, setError } from '../slices/users'
import axios from 'axios'

export default function HomePage() {
  const dispatch = useDispatch()
  const users = useSelector(selectUser)
  const loading = useSelector(selectLoading)  
  const success = useSelector(selectSuccess)
  const error = useSelector(selectorError)
  const message = useSelector(selectorMessage)

  useEffect(() => {
    async function init() {
      dispatch(setLoading(true))
      const response = await axios.get('http://localhost:5000/users')
      
      if(response?.status == 200) {
        dispatch(setUsers(response?.data?.data))
        dispatch(setSuccess({ status: true, message: 'Successfully get users data.' }))
      } else {
        dispatch(setError({ status: true, message: 'Failed to get data.' }))
      }

      dispatch(setLoading(false))
    }
    return init()
  }, [])

  return {
    <>
      {user}
    </>
  }
}
  • Related