useDispatch doesn't dispatch to the reducer - javascript

I'm trying to convert an existing project to configureStore from createStore.
store.js :
export default configureStore({
reducer: {
articlesList: loadArticlesReducer
}
});
home.js :
const articlesList = useSelector((state) => state.articlesList);
const dispatch = useDispatch()
useEffect(() => {
dispatch(getArticles());
})
articleSlice.js :
const articleSlice = createSlice({
name: 'articles',
initialState : [],
reducers : {
loadArticlesReducer: (state, action) => {
console.log("WE NEVER REACH THIS CODE") <=== the problem is here
state = action.payload;
}
}
});
export const { loadArticlesReducer } = articleSlice.actions;
export const getArticles = () => dispatch => {
fetch("https://....")
.then(response => response.json())
.then(data => {
dispatch(loadArticlesReducer(data))
})
};
The problem, as stated in the comment, is that getArticles action never dispatches the data to loadArticlesReducer.
What am I missing here?

loadArticlesReducer is an action creator, not a reducer function. I suggest renaming the action creator so its purpose isn't confusing to future readers (including yourself) and actually exporting the reducer function.
Example:
const articleSlice = createSlice({
name: 'articles',
initialState : [],
reducers : {
loadArticlesSuccess: (state, action) => {
state = action.payload;
}
}
});
export const { loadArticlesSuccess } = articleSlice.actions;
export const getArticles = () => dispatch => {
fetch("https://....")
.then(response => response.json())
.then(data => {
dispatch(loadArticlesSuccess(data));
});
};
export default articleSlice.reducer; // <-- export reducer function
import articlesReducer from '../path/to/articles.slice';
export default configureStore({
reducer: {
articlesList: articlesReducer
}
});
You may also want to consider converting getArticles to a more idiomatic RTK thunk function using createAsyncThunk. You'd use the slice's extraReducers to handle the fulfilled Promise returned from the Thunk. Example:
import { createAsyncThunk, createSlice } from '#reduxjs/toolkit';
export const getArticles = createAsyncThunk(
"articles/getArticles",
() => {
return fetch("https://....")
.then(response => response.json());
}
);
const articleSlice = createSlice({
name: 'articles',
initialState : [],
extraReducers: builder => {
builder.addCase(getArticles.fulfilled, (state, action) => {
state = action.payload;
});
},
});
export default articleSlice.reducer;

As per the documentation, you'll need to replace the following line:
export default configureStore({
reducer: {
articlesList: loadArticlesReducer // <= This currently points to the exported actions and not the reducer
}
});
With this one:
import articleReducer from './articleSlice.js'
export default configureStore({
reducer: {
articlesList: articleReducer
}
});
You'll need to export the reducer from the articleSlice.js of course:
export default articleSlice.reducer;
As a general tip, always reproduce the examples from the documentation using exactly the same setup and naming, and once it works, customize the code accordingly in a slow step by step manner. It's easy to miss something while replicating such complicated setups if there's no 1-to-1 correspendence with the original code.

Related

undefined is returned using redux toolkit

I am currently working with the redux toolkit I am facing an issue for the last 2 days.
The problem is when I am trying to select a state using the useSelector undefined is returned.
The code is given below:
reducer
import { createSlice } from "#reduxjs/toolkit";
import axios from "axios";
const SchoolDataReducer = createSlice({
name: "datasets",
initialState: [],
reducers: {
getSchoolRecords(state, action) {
return action.payload
}
}
})
export const { getSchoolRecords } = SchoolDataReducer.actions;
export default SchoolDataReducer.reducer;
export function getDataTriger() {
return async function getSchoolDataThunk(dispatch, getstate) {
try {
const res = await axios.get("/getschool")
dispatch(getSchoolRecords(res.data))
} catch (error) {
console.log(error);
}
}
}
store
import { configureStore } from "#reduxjs/toolkit";
import SchoolDataReducer from "./SchoolData"
const store = configureStore({
reducer: {
School: SchoolDataReducer
}
});
export default store;
result
const maindATA = useSelector(State => State.datasets)
console.log(maindATA); // undefined
Your whole state data structure is { School: [] }, you can get the state slice using useSelector(state => state.School).
Please see https://redux-toolkit.js.org/api/configureStore#reducer about the data structure of the redux state. So you can change the reducer option of configureStore like below:
const store = configureStore({
reducer: {
datasets: SchoolDataReducer
}
});
// In component:
const maindATA = useSelector(state => state.datasets)

how to use redux thunks with redux toolkit in custom useFetch for fetching api data

im new to the redux toolkit here is my problem
im fetching data every time by creating own custom useFetch file for handling loading , success, error status
i used the same way with redux toolkit by creating createSlice method for accesing reducers and actions
this is working successfully and getting data from this way by dispaching actions and reducers
but i did't used the createAsyncThunk from redux toolkit
my confusion is is this currect way to fetch data from custom useFetch or should i use createAsyncthunk
Im not sure how to use createAsyncThunk in custom useFetch
if anyone knows the answer that is so appreciatable
posted my all files below
if i get answer with createAsyncThunk in custom useFetch that is soo appreciable
thanks advance
App.js
import React from "react";
import { useDispatch, useSelector } from "react-redux";
import useFetchData from "./Components/useFetchData";
import { actions } from "./Slices/CounterSlice";
const App = () => {
useFetchData("https://jsonplaceholder.typicode.com/todos");
const apiData = useSelector((s) => s);
console.log(apiData.api);
return (
<>
{apiData.api.status !== "success" && <h1>hello</h1>}
{apiData.api.status === "success" &&
apiData.api.apiData.map((el) => {
return <h6 key={el.id}>{el.title}</h6>;
})}
</>
);
};
export default App;
custom useFetchData.js file
import React, { useCallback } from "react";
import { useDispatch } from "react-redux";
import { ApiActions } from "../Slices/ApiSlice";
const useFetchData = (link) => {
const dispatch = useDispatch();
const fetchData = useCallback(async () => {
try {
dispatch(ApiActions.loadingTime());
const getData = await fetch(link);
const toJson = await getData.json();
dispatch(ApiActions.successTime(toJson));
} catch {
dispatch(ApiActions.errorTime());
}
}, [link]);
React.useEffect(() => {
fetchData();
}, [fetchData]);
};
export default useFetchData;
this is createSlice file for creating actions and reducers
import { createSlice } from "#reduxjs/toolkit";
const apiSlice = createSlice({
name: "api",
initialState: {
status: "idle",
apiData: [],
error: false,
},
reducers: {
loadingTime: (state, action) => {
state.status = "Loading";
},
successTime: (state, action) => {
state.apiData = action.payload;
state.status = "success";
},
errorTime: (state, action) => {
state.apiData = [];
state.status = "error";
},
},
});
export const ApiActions = apiSlice.actions;
export const apiReducers = apiSlice.reducer;
You can use one way or the other. Example with createAsyncThunk:
export const checkIfAuthenticated = createAsyncThunk(
"auth/checkIsAuth",
async (_, thunkAPI) => {
const response = await axios.post(
"http://127.0.0.1:8080/dj-rest-auth/token/verify/",
{ token: localStorage.getItem("access") }
);
thunkAPI.dispatch(getUserInfo());
return response.data;
}
);
Then you handle loading, success, error status as before:
extraReducers: (builder) => {
builder
.addCase(checkIfAuthenticated.fulfilled, (state, action) => {
state.isAuthenticated = true;
})
.addCase(checkIfAuthenticated.rejected, (state, action) => {
state.isAuthenticated = false;
snackbar({ error: "Nie jesteÅ› zalogowany" });
})
},
However, if you have a lot of API queries, the optimal solution is to use redux toolkit query or react-query. These two libraries make queries a lot better.
If you care about code cleanliness and ease, check them out.

Generic modals with Redux and Thunk

I've been looking into creating generic modals with React, Redux, and Thunk. Ideally, my state would look like the following:
export interface ConfirmModalState {
isOpened: boolean;
onConfirm: null | Function
}
export const initialConfirmModalState: ConfirmModalState = {
isOpened: false,
onConfirm: null
};
However, this would mean putting non-serializable data into the state, which seems to be highly discouraged.
I've read a great blogpost by markerikson. However, I don't think the proposed solution would work with asynchronous actions and Thunk.
How do you suggest to resolve this issue?
I actually wrote the post that you linked, and I wrote a much-expanded version of that post a couple years later:
Practical Redux, Part 10: Managing Modals and Context Menus.
I've actually implemented a couple variations of this approach myself since I wrote that post, and the best solution I've found is to add a custom middleware that returns a promise when you dispatch a "show modal" action, and resolves the promise with a "return value" when the dialog is closed.
There's an existing implementation of this approach at https://github.com/AKolodeev/redux-promising-modals . I ended up making my own implementation. I have a partial version of my homegrown approach in a gist at https://gist.github.com/markerikson/8cd881db21a7d2a2011de9e317007580 , and the middleware looked roughly like:
export const dialogPromiseMiddleware: Middleware<DialogPromiseDispatch> = storeAPI => {
const dialogPromiseResolvers: Record<string, Resolver> = {};
return next => (action: AnyAction) => {
switch (action.type) {
// Had to resort to `toString()` here due to https://github.com/reduxjs/redux-starter-kit/issues/157
case showDialogInternal.toString(): {
next(action);
let promiseResolve: Resolver;
const dialogPromise = new Promise((resolve: Resolver) => {
promiseResolve = resolve;
});
dialogPromiseResolvers[action.payload.id] = promiseResolve!;
return dialogPromise;
}
case closeDialog.toString(): {
next(action);
const {id, values} = action.payload;
const resolver = dialogPromiseResolvers[id];
if (resolver) {
resolver(values);
}
delete dialogPromiseResolvers[id];
break;
}
default:
return next(action);
}
};
};
(note: I made that gist when I was having some TS syntax issues getting dispatching to work correctly, so it's likely it won't 100% work out of the box. RTK also now includes some .match() action matching utilities that would be useful here. but, it shows the basic approach.)
The rough usage in a component is:
const closedPromise = dispatch(showDialog("TestDialog", {dialogNumber : counter});
const result = await closedPromise
// do something with the result
That way you can write the "on confirm" logic write there in the place that asked for the dialog to be shown in the first place.
Thank you markerikson for providing an answer. This inspired me to create a solution with thunks. Please give me some feedback here :)
I will be using hooks and #reduxjs/toolkit in my example.
This is the state of my ConfirmationModal reducer:
export interface confirmationModalState {
isOpened: boolean;
isConfirmed: boolean;
isCancelled: boolean;
}
export const initialConfirmationModalState: confirmationModalState = {
isOpened: false,
isConfirmed: false,
isCancelled: false,
};
This is the slice (a combination of the reducer and actions):
import { createSlice } from '#reduxjs/toolkit';
import { initialConfirmationModalState } from './state';
const confirmationModalSlice = createSlice({
name: 'controls/confirmationModal',
initialState: initialConfirmationModalState,
reducers: {
open: state => {
state.isOpened = true;
state.isConfirmed = false;
state.isCancelled = false;
},
confirm: state => {
state.isConfirmed = true;
state.isOpened = false;
},
cancel: state => {
state.isCancelled = true;
state.isOpened = false;
},
},
});
export const confirmationModalActions = confirmationModalSlice.actions;
export default confirmationModalSlice;
This is the thunk action for it:
import { createAsyncThunk } from '#reduxjs/toolkit';
import ThunkApiConfig from '../../../types/ThunkApiConfig';
import { AppState } from '../../reducers';
import { confirmationModalActions } from './slice';
const confirmationModalThunkActions = {
open: createAsyncThunk<boolean, void, ThunkApiConfig>(
'controls/confirmationModal',
async (_, { extra, dispatch }) => {
const store = extra.store;
dispatch(confirmationModalActions.open());
return await new Promise<boolean>(resolve => {
store.subscribe(() => {
const state: AppState = store.getState();
if (state.controls.confirmationModal.isConfirmed) {
resolve(true);
}
if (state.controls.confirmationModal.isCancelled) {
resolve(false);
}
});
});
},
),
};
export default confirmationModalThunkActions;
You can notice it uses extra.store to perform the subscribe. We need to provide it when creating a store:
import combinedReducers from './reducers';
import { configureStore, getDefaultMiddleware } from '#reduxjs/toolkit';
import { ThunkExtraArguments } from '../types/ThunkExtraArguments';
function createStore() {
const thunkExtraArguments = {} as ThunkExtraArguments;
const customizedMiddleware = getDefaultMiddleware({
thunk: {
extraArgument: thunkExtraArguments,
},
});
const store = configureStore({
reducer: combinedReducers,
middleware: customizedMiddleware,
});
thunkExtraArguments.store = store;
return store;
}
export default createStore();
Now, let's create a hook that allows us to dispatch all of the above actions:
import { useDispatch, useSelector } from 'react-redux';
import { AppState } from '../../../reducers';
import { useCallback } from 'react';
import confirmationModalThunkActions from '../thunk';
import { confirmationModalActions } from '../slice';
import { AppDispatch } from '../../../../index';
export function useConfirmationModalState() {
const dispatch: AppDispatch = useDispatch();
const { isOpened } = useSelector((state: AppState) => ({
isOpened: state.controls.confirmationModal.isOpened,
}));
const open = useCallback(() => {
return dispatch(confirmationModalThunkActions.open());
}, [dispatch]);
const confirm = useCallback(() => {
dispatch(confirmationModalActions.confirm());
}, [dispatch]);
const cancel = useCallback(() => {
dispatch(confirmationModalActions.cancel());
}, [dispatch]);
return {
open,
confirm,
cancel,
isOpened,
};
}
(don't forget to attach confirm and cancel to the buttons in your modal)
And that's it! We can now dispatch our confirmation modal:
export function usePostControls() {
const { deleteCurrentPost } = usePostsManagement();
const { open } = useConfirmationModalState();
const handleDelete = async () => {
const { payload: isConfirmed } = await open();
if (isConfirmed) {
deleteCurrentPost();
}
};
return {
handleDelete,
};
}

How to use Redux-Thunk with Redux Toolkit's createSlice?

I have come across Redux Toolkit (RTK) and wanting to implement further functionality it provides. My application dispatches to reducers slices created via the createSlice({}) (see createSlice api docs)
This so far works brilliantly. I can easily use the built in dispatch(action) and useSelector(selector) to dispatch the actions and receive/react to the state changes well in my components.
I would like to use an async call from axios to fetch data from the API and update the store as the request is A) started B) completed.
I have seen redux-thunk and it seems as though it is designed entirely for this purpose, but the new RTK does not seem to support it within a createSlice() following general googling.
Is the above the current state of implementing thunk with slices?
I have seen in the docs that you can add extraReducers to the slice but unsure if this means I could create more traditional reducers that use thunk and have the slice implement them?
Overall, it is misleading as the RTK docs show you can use thunk, but doesn't seem to mention it not being accessible via the new slices api.
Example from Redux Tool Kit Middleware
const store = configureStore({
reducer: rootReducer,
middleware: [thunk, logger]
})
My code for a slice showing where an async call would fail and some other example reducers that do work.
import { getAxiosInstance } from '../../conf/index';
export const slice = createSlice({
name: 'bundles',
initialState: {
bundles: [],
selectedBundle: null,
page: {
page: 0,
totalElements: 0,
size: 20,
totalPages: 0
},
myAsyncResponse: null
},
reducers: {
//Update the state with the new bundles and the Spring Page object.
recievedBundlesFromAPI: (state, bundles) => {
console.log('Getting bundles...');
const springPage = bundles.payload.pageable;
state.bundles = bundles.payload.content;
state.page = {
page: springPage.pageNumber,
size: springPage.pageSize,
totalElements: bundles.payload.totalElements,
totalPages: bundles.payload.totalPages
};
},
//The Bundle selected by the user.
setSelectedBundle: (state, bundle) => {
console.log(`Selected ${bundle} `);
state.selectedBundle = bundle;
},
//I WANT TO USE / DO AN ASYNC FUNCTION HERE...THIS FAILS.
myAsyncInSlice: (state) => {
getAxiosInstance()
.get('/')
.then((ok) => {
state.myAsyncResponse = ok.data;
})
.catch((err) => {
state.myAsyncResponse = 'ERROR';
});
}
}
});
export const selectBundles = (state) => state.bundles.bundles;
export const selectedBundle = (state) => state.bundles.selectBundle;
export const selectPage = (state) => state.bundles.page;
export const { recievedBundlesFromAPI, setSelectedBundle, myAsyncInSlice } = slice.actions;
export default slice.reducer;
My store setup (store config).
import { configureStore } from '#reduxjs/toolkit';
import thunk from 'redux-thunk';
import bundlesReducer from '../slices/bundles-slice';
import servicesReducer from '../slices/services-slice';
import menuReducer from '../slices/menu-slice';
import mySliceReducer from '../slices/my-slice';
const store = configureStore({
reducer: {
bundles: bundlesReducer,
services: servicesReducer,
menu: menuReducer,
redirect: mySliceReducer
}
});
export default store;
I'm a Redux maintainer and creator of Redux Toolkit.
FWIW, nothing about making async calls with Redux changes with Redux Toolkit.
You'd still use an async middleware (typically redux-thunk), fetch data, and dispatch actions with the results.
As of Redux Toolkit 1.3, we do have a helper method called createAsyncThunk that generates the action creators and does request lifecycle action dispatching for you, but it's still the same standard process.
This sample code from the docs sums up the usage;
import { createAsyncThunk, createSlice } from '#reduxjs/toolkit'
import { userAPI } from './userAPI'
// First, create the thunk
const fetchUserById = createAsyncThunk(
'users/fetchByIdStatus',
async (userId, thunkAPI) => {
const response = await userAPI.fetchById(userId)
return response.data
}
)
// Then, handle actions in your reducers:
const usersSlice = createSlice({
name: 'users',
initialState: { entities: [], loading: 'idle' },
reducers: {
// standard reducer logic, with auto-generated action types per reducer
},
extraReducers: (builder) => {
// Add reducers for additional action types here, and handle loading state as needed
builder.addCase(fetchUserById.fulfilled, (state, action) => {
// Add user to the state array
state.entities.push(action.payload)
})
},
})
// Later, dispatch the thunk as needed in the app
dispatch(fetchUserById(123))
See the Redux Toolkit "Usage Guide: Async Logic and Data Fetching" docs page for some additional info on this topic.
Hopefully that points you in the right direction!
You can use createAsyncThunk to create thunk action, which can be trigger using dispatch
teamSlice.ts
import { createSlice, createAsyncThunk } from "#reduxjs/toolkit";
const axios = require("axios");
export const fetchPlayerList = createAsyncThunk(
"team/playerListLoading",
(teamId: string) =>
axios
.get(`https://api.opendota.com/api/teams/${teamId}/players`)
.then((response) => response.data)
.catch((error) => error)
);
const teamInitialState = {
playerList: {
status: "idle",
data: {},
error: {},
},
};
const teamSlice = createSlice({
name: "user",
initialState: teamInitialState,
reducers: {},
extraReducers: {
[fetchPlayerList.pending.type]: (state, action) => {
state.playerList = {
status: "loading",
data: {},
error: {},
};
},
[fetchPlayerList.fulfilled.type]: (state, action) => {
state.playerList = {
status: "idle",
data: action.payload,
error: {},
};
},
[fetchPlayerList.rejected.type]: (state, action) => {
state.playerList = {
status: "idle",
data: {},
error: action.payload,
};
},
},
});
export default teamSlice;
Team.tsx component
import React from "react";
import { useSelector, useDispatch } from "react-redux";
import { fetchPlayerList } from "./teamSlice";
const Team = (props) => {
const dispatch = useDispatch();
const playerList = useSelector((state: any) => state.team.playerList);
return (
<div>
<button
onClick={() => {
dispatch(fetchPlayerList("1838315"));
}}
>
Fetch Team players
</button>
<p>API status {playerList.status}</p>
<div>
{playerList.status !== "loading" &&
playerList.data.length &&
playerList.data.map((player) => (
<div style={{ display: "flex" }}>
<p>Name: {player.name}</p>
<p>Games Played: {player.games_played}</p>
</div>
))}
</div>
</div>
);
};
export default Team;
Use redux-toolkit v1.3.0-alpha.8
Try this
import { createAsyncThunk, createSlice } from '#reduxjs/toolkit';
export const myAsyncInSlice = createAsyncThunk('bundles/myAsyncInSlice', () =>
getAxiosInstance()
.get('/')
.then(ok => ok.data)
.catch(err => err),
);
const usersSlice = createSlice({
name: 'bundles',
initialState: {
bundles: [],
selectedBundle: null,
page: {
page: 0,
totalElements: 0,
size: 20,
totalPages: 0,
},
myAsyncResponse: null,
myAsyncResponseError: null,
},
reducers: {
// add your non-async reducers here
},
extraReducers: {
// you can mutate state directly, since it is using immer behind the scenes
[myAsyncInSlice.fulfilled]: (state, action) => {
state.myAsyncResponse = action.payload;
},
[myAsyncInSlice.rejected]: (state, action) => {
state.myAsyncResponseError = action.payload;
},
},
});

What is the best option to use redux actions when using redux hooks?

I want to use redux hook useSelector to access the store and get rid of connect(), so I need to create a way to export my actions, and I'm thinking on a class with static methods, here is an example
export default class AuthActions {
static async login(userData) {
try {
const user = await axios.post('http://localhost:5000', userData);
dispatch({
type: AUTH.LOGIN,
payload: user.data
})
} catch (error) {
dispatch({
type: SET_ERROR,
payload: error
})
}
}
static setUser() {
console.log("SET USER")
}
static logout() {
console.log("Logout")
}
}
And then I use the action methods as follows:
import React from 'react';
import AuthActions from '../../redux/actions/AuthActions';
import { useSelector } from 'react-redux';
export default const Login = () => {
//More logic....
const { isAuth } = useSelector((state) => state.auth);
const submitHandler = e => {
e.preventDefault();
AuthActions.login(userData)
}
return (
<form onSubmit={submitHandler}>
My Login form ....
</form>
);
};
But I'm wondering if there is a disadvantage or performance issues to use redux in this way, or should I avoid the usage of a class and use a simple object instead?
Thank you in advance
this is my format of a reducer, inspired by ducks-modular-redux
for example, check out this darkMode reducer:
export const constants = {
TOGGLE: "darkMode/TOGGLE"
};
export const actions = {
toggleDarkMode: () => {
return {
type: constants.TOGGLE
};
}
};
export const thunks = {
toggleDarkMode: () => {
return (dispatch, getState) => {
dispatch(actions.toggleDarkMode());
const isDark = getState().darkMode.isDark;
localStorage.setItem("isDark", isDark);
};
}
};
const initialState = { isDark: localStorage.getItem("isDark") === "true" };
export default (state = initialState, action) => {
switch (action.type) {
case constants.TOGGLE:
return {
isDark: !state.isDark
};
default:
return state;
}
};

Categories

Resources