Dispatch is not a function error with createSlice() - reactjs

My objective is to get the customer details from a fake API through redux. I have migrated to using createSlice() to reduce the boilerplate, but I keep having that error. I have tried and researched as much as I can, but to no avail.
Here is a self contained code: https://stackblitz.com/edit/react-gbhdd9?file=src/App.js
App.js
import React, { useCallback, useState, useEffect } from 'react';
import { customerApi } from './__fakeAPI__/customerApi';
import { getCustomer as getCustomerSlice } from './slices/customers';
export default function App() {
const [customer, setCustomer] = useState(null);
const getCustomer = useCallback(async () => {
try {
//const data = await customerApi.getCustomer(); //works but not what I wanted
const data = getCustomerSlice();
setCustomer(data);
} catch (err) {
console.error(err);
}
}, []);
useEffect(() => {
getCustomer();
}, [getCustomer]);
return <div>{console.log(customer)}</div>;
}
slices/customers.js
import { createSlice } from '#reduxjs/toolkit';
import { customerApi } from '../__fakeAPI__/customerApi';
const initialState = {
customer: {}
};
const slice = createSlice({
name: 'customers',
initialState,
reducers: {
getCustomer(state, action) {
state.customer = action.payload;
}
}
});
export const { reducer } = slice;
export const getCustomer = () => async dispatch => {
const data = await customerApi.getCustomer();
//dispatch seems to not be working
dispatch(slice.actions.getCustomer(data));
};
export default slice;

You needed to dispatch your action to make it work, to access customer you can use useSelector to get access to your state :
https://stackblitz.com/edit/react-v4u5ft?file=src/App.js

You need to make a few changes to App.js to make this work.
The getCustomerSlice() method is an action creator and in your case, it returns a thunk, and to get access to the dispatch method in a thunk, you must first dispatch the action/function returned from that action creator i.e in App.js
export default function App() {
...
const getCustomer = useCallback(async () => {
try {
const data = dispatch(getCustomerSlice());
...
}, []);
...
}
To get access to the dispatch method in App.js, import the useDispatch from react-redux, useDispatch is a function that returns the dispatch method i.e
...
import { useDispatch } from "react-redux"
...
export default function App() {
const dispatch = useDispatch()
...
const getCustomer = useCallback(async () => {
try {
const data = dispatch(getCustomerSlice());
...
}, []);
...
}
When you do this redux-thunk will automatically supply dispatch, getState as parameters to any function that is returned from an action creator. This will cause your implementation to update your store properly.
Since you are expecting a return value from your dispatch method, make this modification to your customers.js file
...
export const getCustomer = () => async dispatch => {
const data = await customerApi.getCustomer();
//dispatch seems to not be working
dispatch(slice.actions.getCustomer(data));
// return the data
return data;
};
...
then in your App.js file await the dispatch function since you defined it as async
...
import { useDispatch } from "react-redux"
...
export default function App() {
const dispatch = useDispatch()
...
const getCustomer = useCallback(async () => {
try {
const data = await dispatch(getCustomerSlice());
...
}, []);
...
}
TLDR
Your App.js should now look like this.
import React, { useCallback, useState, useEffect } from 'react';
import { customerApi } from './__fakeAPI__/customerApi';
import { useSelector, useDispatch} from "react-redux"
import { getCustomer as getCustomerSlice } from './slices/customers';
export default function App() {
const dispatch = useDispatch()
const [customer, setCustomer] = useState(null);
const getCustomer = useCallback(async () => {
try {
//const data = await customerApi.getCustomer(); //works but not what I wanted
const data = await dispatch(getCustomerSlice());
setCustomer(data);
} catch (err) {
console.error(err);
}
}, []);
useEffect(() => {
getCustomer();
}, [getCustomer]);
return <div>{console.log("customers",customer)}</div>;
}
And your customers.js like this
import { createSlice } from '#reduxjs/toolkit';
import { customerApi } from '../__fakeAPI__/customerApi';
const initialState = {
customer: {}
};
const slice = createSlice({
name: 'customers',
initialState,
reducers: {
getCustomer(state, action) {
state.customer = action.payload;
}
}
});
export const { reducer } = slice;
export const getCustomer = () => async dispatch => {
const data = await customerApi.getCustomer();
//dispatch seems to not be working
dispatch(slice.actions.getCustomer(data));
return data;
};
export default slice;
it is important to note that your component has not subscribed to the store yet. so if the customers object in the store is modified by another component then this component won't re-render, to fix this look up the useSelector hook from react-redux

Related

reducer method not called with useDispatch in react

I have created a new component in react
import React, {FC, useEffect} from 'react'
import { useDispatch, useSelector } from 'react-redux'
import { fetchExternalLinks } from '../../redux/reducers/appReducer'
import { getExternalLinksSelector } from '../../redux/selectors/appSelector'
type LinksType = {
title?: string | undefined
}
const ExternalLinks : FC<LinksType> = (props) => {
const {
title
} = props
const dispatch = useDispatch()
const links = useSelector(getExternalLinksSelector)
useEffect(() => {
console.log('use effect')
dispatch(fetchExternalLinks)
})
const openWindow = (path: string) => {
window.open(path, '_blank', 'toolbar=0,location=0,menubar=0');
}
return (
<>
Links:
{links.map((link) => {
return <a onClick={() => openWindow(link.path)}>{link.title}</a>
})
}
</>
)
}
export default ExternalLinks
when I try to call the fetchExternalLinks method from reducer with dispatch, it doesn't work.
In my console I can see only "use effect", but nothing from reducer
export const fetchExternalLinks = (): ThunkType => async (dispatch) => {
console.log("test")
try {
dispatch(appActions.toggleIsFetching(true))
const response = await appApi.getCustomers()
dispatch(appActions.setExternalLinks(response.data))
} catch (e) {
dispatch(appActions.toggleResponseMessage({isShown: true, isSuccess: false}))
} finally {
dispatch(appActions.toggleIsFetching(false))
}
}
Change your code from this,
dispatch(fetchExternalLinks)
to this,
dispatch(fetchExternalLinks())
Since you are only passing the reference, your async thunk is not getting executed, when the fetchExternalLinks function will be called using () rather than passing a reference you will receive an arrow function as a return value, to which react-redux will pass the dispatch function and will execute it for you

Redux - API is being called multiple times (Redux Thunk)

I am using Next.js and Redux as a state management. Everything is working perfectly fine except one thing and that is API calls. What I mean by this is that API is being called multiple times even though I dispatched it just once. When I go and see in the network tab in Google Chrome, I see multiple calls being called.
I am also using Redux Thunk and Redux Toolkit:
store
import { configureStore } from "#reduxjs/toolkit";
import layoutSlice from "./layoutSlice";
export const store = configureStore({
reducer: {
layout: layoutSlice,
},
});
layoutSlice
import { createSlice, createAsyncThunk } from "#reduxjs/toolkit";
import axios from "axios";
const BASE_URL = "http://localhost:1337";
export const getHeaderData = createAsyncThunk(
"layout/getHeaderData",
async () => {
const response = await axios.get(
`${BASE_URL}/api/navigations?populate=*&sort=id`
);
return response.data;
}
);
export const getFooterData = createAsyncThunk(
"layout/getFooterData",
async () => {
const response = await axios.get(
`${BASE_URL}/api/footers?populate[ContactForm][populate]=*&populate[Links][populate]=*&populate[Info][populate]=*`
);
return response.data;
}
);
const initialState = {
header: [],
footer: [],
isLoadingHeader: false,
isLoadingFooter: false,
};
const layoutSlice = createSlice({
name: "layout",
initialState,
extraReducers: {
[getHeaderData.pending]: (state) => {
state.isLoadingHeader = true;
},
[getHeaderData.fulfilled]: (state, action) => {
state.header = action.payload;
state.isLoadingHeader = false;
},
[getHeaderData.rejected]: (state) => {
state.isLoadingHeader = false;
},
[getFooterData.pending]: (state) => {
state.isLoadingFooter = true;
},
[getFooterData.fulfilled]: (state, action) => {
state.footer = action.payload;
state.isLoadingFooter = false;
},
[getFooterData.rejected]: (state) => {
state.isLoadingFooter = false;
},
},
});
export default layoutSlice.reducer;
generalLayout (where the API is called)
import React, { useEffect, useState } from "react";
import { Header, Footer } from "../components";
import { useDispatch, useSelector } from "react-redux";
import { getHeaderData, getFooterData } from "../redux/layoutSlice";
const GeneralLayout = ({ children }) => {
const { isLoadingHeader, isLoadingFooter } = useSelector(
(state) => state.layout
);
const dispatch = useDispatch();
useEffect(() => {
dispatch(getHeaderData());
dispatch(getFooterData());
}, []);
if (isLoadingHeader === true || isLoadingFooter === true) {
return <div>Loading...</div>;
}
return (
<>
<Header />
{children}
<Footer />
</>
);
};
export default GeneralLayout;
I am also using Strapi (dont mind the query for the API call, it works for me so I do not think the problem is there, at least it should not be)
Network tab
It is because of useEffect
In development, React strictmode calls all effects twice to catch any memory leaks and other issues.
This only applies to development mode, production behavior is unchanged
So you don't want to worry about it being called twice in production/build
From official React docs (beta at time of writing)
If your Effect fetches something, the cleanup function should either abort the fetch or ignore its result:
useEffect(() => {
let ignore = false;
async function startFetching() {
const json = await fetchTodos(userId);
if (!ignore) {
setTodos(json);
}
}
startFetching();
return () => {
ignore = true;
};
}, [userId]);
Read more here
You must add a dispatch to dependency of useEffect function..
make sure the peomise function receives parameter as the informations you want to get or post.. And call the parameter inside axios func after APIurl.

Calling API in redux

//Store
import { configureStore } from "#reduxjs/toolkit";
import { currencyListSlice } from "./Reducers/CurrencyListReducer";
export const store = configureStore({
reducer: {
currencyList: currencyListSlice.reducer,
}
}
)
export default store
//CurrencyListReducer
import { createSlice } from "#reduxjs/toolkit"
export const loadCurrencyList = () => {
return async (dispatch, getState) => {
const data = await fetch(API-Key)
const payload = await data.json()
dispatch({
type: 'currencyList/setCurrencyList',
payload: payload
})
}
}
const options = {
name: 'currencyList',
initialState: [],
reducers: {
setCurrencyList(state, action) {
return action.payload
}
}
}
export const currencyListSlice = createSlice(options)
//CurrencyList Component
import React, { useEffect } from 'react'
import { useSelector, useDispatch } from 'react-redux'
import { currencyListSlice } from '../../Reducers/CurrencyListReducer'
const selectCurrencyList = state => state.CurrencyList
export const CurrencyList = () => {
const dispatch = useDispatch()
const currencyList = useSelector(selectCurrencyList)
const { loadCurrencyList } = currencyListSlice.actions
useEffect(() => {
dispatch(loadCurrencyList())
}, [dispatch, loadCurrencyList])
console.log(currencyList)
return (
<div>
/*Some elements here*/
</div>
)
}
I'm working with redux for the first time and having some real problem in calling API and storing data in store. The problem is I'm not getting anything from API but the console.log(currencyList) just gives me undefined. I tried calling API directly in reducer but that too didn't work out. I'm a newbie to redux and calling the API in redux is being a difficult task for me. Forgive any silly mistake(if present).
try reading this: createAsyncThunk

TypeError: functionName is not a function (Redux - useDispatch)

I am getting TypeError: functionName is not a function when I call it with useDispatch. The initial state return normal as an empty array. This is my code:
Action:
export const getTopStories = () => async (dispatch) => {
try {
const { data } = await axios.get('http://news-site.com/api');
dispatch({
type: GET_TOP_STORIES,
payload: data,
});
} catch (err) {
console.log(err)
}
};
Reducer:
const initialState = {
topStories: [],
};
export default (state = initialState, action) => {
switch (action.type) {
case GET_TOP_STORIES:
return { ...state, topStories: action.payload};
default:
return state;
}
};
Page:
import { useEffect } from 'react';
import { useDispatch, useSelector } from 'react-redux';
const TopStories = () => {
const stories = useSelector((state) => state.stories);
const { topStories, getTopStories } = stories;
const dispatch = useDispatch();
useEffect(() => {
dispatch(getTopStories());
}, []);
You are trying to extract an action from the reducer state. If won't work like that. To get the state, you can use the useSelector hook like you are doing. But to use the action, you need to import that function and use it in the dispatch that you already have.
Something like this:
import { useEffect } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { getTopStories } from './action.js'; // Adjust the path
const TopStories = () => {
const stories = useSelector((state) => state.stories);
const { topStories } = stories;
const dispatch = useDispatch();
useEffect(() => {
dispatch(getTopStories());
}, []);

Should I add async code in container component?

I'm making my first React-Redux project.
I wanna get data from getListAPI.
I checked console.log(data) in [GET_LIST_SUCCESS], and there was what I wanted.
But console.log(temp) in container, I expect 'data', it was just action object(only type exists).
How can I get the 'data'?
// container
import React from 'react';
import { useDispatch } from 'react-redux';
import Home from 'presentations/Home';
import * as homeActions from 'modules/home';
const HomeContainer = () => {
const dispatch = useDispatch();
const temp = dispatch(homeActions.getList());
console.log(temp);
return (
<Home />
);
}
export default HomeContainer;
// Redux module
import axios from 'axios';
import { call, put, takeEvery } from 'redux-saga/effects';
import { createAction, handleActions } from 'redux-actions';
function getListAPI() {
return axios.get('http://localhost:8000/');
}
const GET_LIST = 'home/GET_LIST';
const GET_LIST_SUCCESS = 'home/GET_LIST_SUCCESS';
const GET_LIST_FAILURE = 'home/GET_LIST_FAILURE';
export const getList = createAction(GET_LIST);
function* getListSaga() {
try {
const response = yield call(getListAPI);
yield put({ type: GET_LIST_SUCCESS, payload: response });
} catch (e) {
yield put({ type: GET_LIST_FAILURE, payload: e });
}
}
const initialState = {
data: {
id: '',
title: '',
created_at: '',
updated_at: '',
content: '',
view: '',
}
};
export function* homeSaga() {
yield takeEvery('home/GET_LIST', getListSaga);
}
export default handleActions(
{
[GET_LIST_SUCCESS]: (state, action) => {
const data = action.payload.data;
console.log(data);
return {
data
};
}
}, initialState
);
Maybe I need like async/await or Promise.then() or useCallback, etc in container?
Because I thought Redux-Saga handles async, but container isn't in Redux-Saga area.
So shouldn't I inject the container with async processing?
I wrote some code for test.
Expecting to receive other data in a few seconds.
// container
// const temp = dispatch(homeActions.getList());
let temp = dispatch(homeActions.getList());
let timer = setInterval(() => console.log(temp), 1000);
setTimeout(() => { clearInterval(timer); alert('stop');}, 50000);
Nothing changed.
It's just log action object(only type exists).
What am I missing?
dispatch() returns the action dispatched to the store (that's why the console.log(temp) shows the action itself).
You need to create a selector to fetch the data from the store and use the useSelector() hook:
// container
import React from 'react';
import { useDispatch } from 'react-redux';
import Home from 'presentations/Home';
import * as homeActions from 'modules/home';
const selectData = (state) => state.data
const HomeContainer = () => {
const dispatch = useDispatch();
const temp = useSelector(selectData)
dispatch(homeActions.getList());
// Do something with temp
return (
<Home />
);
}
export default HomeContainer;

Resources