So, i'am trying to create async thunk action, to mock data from file to the state.
import { createAsyncThunk, createSlice, PayloadAction } from '#reduxjs/toolkit';
import mocktasks from './mocktasks';
export interface TasksState {
data: Task[];
}
const initialState: TasksState = {
data: [],
};
export const fetchTasks = createAsyncThunk('tasks/fetchTasks', () => mocktasks);
export const tasksSlice = createSlice({
name: 'tasks',
initialState,
reducers: {
setTasks: (state:TasksState, action: PayloadAction<Task[]>) => ({
...state,
data: action.payload,
}),
addTask: (state: TasksState, action: PayloadAction<Task>) => ({
...state,
data: [...state.data, action.payload],
}),
},
extraReducers: {
[fetchTasks.fulfilled.name]: (state, action: PayloadAction<Task[]>) => ({
...state,
data: action.payload,
}),
},
});
export const { setTasks, addTask } = tasksSlice.actions;
export default tasksSlice.reducer;
But there is a strange thing: even though, fetch actions have dispatched, my state hasn't changed.
State and dispatched actions
I thought, that there was an issue with passing the payload to fetchTasks/fulfilled, but Redux devtools shows, that fulfilled has right data in payload, that is obtained from mock file:Action payload
UPD: even though, using dispatch(setTasks(mocktasks)) inside createAsyncThunk works just like i need.
Use builder notation to avoid typescript errors (official recomendation from redux-toolkit docs):
extraReducers: (builder) => {
.addCase(fetchTasks.fulfilled, (state) => ({
...state,
data: action.payload,
}))
},
[fetchTasks.fulfilled.type] should work, as .name seems to always return the string actionCreator.
Related
Here below I have mentioned a redux slice. A fetchAllApps thunk function is created with createAsyncThunk for action 'allApps/allappsAdded/' which I dynamically got by allAppsAdded.type. When the fetchAllapps is dispatched it generated actions of type 'allApps/allappsAdded/pending', 'allApps/allappsAdded/fulfilled', 'allApps/allappsAdded/rejected' which I need to add in extraReducers to handle it by doing hardcode.Is there any way to add these action types like allAppsAdded.type programatically?. so that in future It makes easy for me to change these without redundant..
import {
configureStore,
createAsyncThunk,
createSlice
} from "#reduxjs/toolkit";
const initialState = {
apps: [],
categories: [],
loading: {
apps: false
}
};
const allappsSlice = createSlice({
name: "allapps",
initialState,
reducers: {
allappsAdded: (state, action) => {
state["apps"] = action.payload.apps;
state["categories"] = action.payload.categories;
}
},
extraReducers: {
}
});
export default () =>
configureStore({
reducer: allappsSlice.reducer
});
const { allappsAdded } = allappsSlice.actions;
const fetchAllApps = createAsyncThunk(allappsAdded.type, async () => {
console.log("ss");
setTimeout(() => ({ apps: [], categories: [] }), 2000);
});
export { allappsAdded, fetchAllApps };
am new to redux, i created a addToBasket and removeBasket object in my reducers variable but when am trying to export it so i can use it in another component, am getting an Error
TypeError: Cannot destructure property 'addToBasket' of 'basketSlice.action' as it is undefined. i don't know if am not destructuring it the right way, please can someone help out, i don't know what am doing wrong
here is my code
import { createSlice } from '#reduxjs/toolkit';
const initialState = {
items: [],
}
export const basketSlice = createSlice({
name: "basket",
initialState,
reducers: {
addToBasket: (state, action) => {
state.items = [...state.items, action.payload]
},
removeFromBasket: (state, action) => {},
}
});
export const { addToBasket, removeFromBasket} = basketSlice.action;
export default basketSlice.reducer;
You have a typo there - it's actions, not action.
export const { addToBasket, removeFromBasket} = basketSlice.actions;
Here's the example code from Codecademy:
import { createSlice, createAsyncThunk } from '#reduxjs/toolkit';
import { client } from '../api';
const initialState = {
todos: [],
status: 'idle'
};
export const fetchTodos = createAsyncThunk('todos/fetchTodos', async () => {
const response = await client.get('/todosApi/todos');
return response.todos;
});
const todosSlice = createSlice({
name: 'todos',
initialState,
reducers: {
addTodo: (state, action) => {
state.todos.push(action.payload);
}
},
extraReducers: {
[fetchTodos.pending]: (state, action) => {
state.status = 'loading';
},
[fetchTodos.fulfilled]: (state, action) => {
state.status = 'succeeded';
state.todos = state.todos.concat(action.payload);
}
}
});
I'm confused about what fetchTodos.pending and fetchTodos.fulfilled mean as computed properties. I don't see that fetchTodos has those attributes. What is going on?
Those are generated by createAsyncThunk
Check out the RDK docs on createAsyncThunk
Parameters#
createAsyncThunk accepts three parameters: a string action type value, a payloadCreator callback, and an options object.
type
A string that will be used to generate additional Redux action type
constants, representing the lifecycle of an async request:
For example, a type argument of 'users/requestStatus' will generate these
action types:
pending: 'users/requestStatus/pending'
fulfilled: 'users/requestStatus/fulfilled'
rejected: 'users/requestStatus/rejected'
I've struggled to implement react-redux-firebase and redux-firestore into my app after configuring the redux store (struggled with this too, even though redux-toolkit simplified some things). Is it possible that I can communicate with firebase without using those two packages above? If so, how do I use firebase in any of my slices? e.g., auth slice below.
import {createSlice, createAsyncThunk} from '#reduxjs/toolkit';
import firebase from 'firebase/app';
export const authSlice = createSlice({
name: 'authSlice',
initialState: {
currentUser: null,
isLoggedIn: false,
isLoading: false,
},
reducers: {
login: async (state, action) => {},
registerUser: (state, action) => {},
changeProfile: (state, action) => {},
logout: async (state, action) => {},
setCurrentUser: (state, action) => {},
},
});
// Action creators are generated for each case reducer function
export const {
login,
registerUser,
changeProfile,
logout,
setCurrentUser,
} = authSlice.actions;
export default authSlice.reducer;
This is the query in a separate file.
import firestore from '#react-native-firebase/firestore';
export const getPopularProducts = firestore()
.collection('POPULAR')
.orderBy('count', 'desc')
.limit(10)
.get()
.then(querySnapshot => {
const views = [];
querySnapshot.forEach(doc => {
views.push({
key: doc.id,
count: doc.data().count,
product: doc.data().product,
});
});
return views;
})
.catch(error => {
alert('Error getting popular products: ', error);
});
In the reducer/slice, import getPopularProducts.
import {createSlice, createAsyncThunk} from '#reduxjs/toolkit';
import {getPopularProducts} from './../../lib/fetchData';
// Initial states
const initialState = {
products: [],
mainList: [],
popular: [],
};
// Get popular products from firebase
export const fetchPopularProducts = createAsyncThunk(
'prodSlice/fetchPopularProducts',
async () => {
const data = getPopularProducts;
const {_W} = data;
if (_W !== null) {
return _W;
}
},
);
export const productSlice = createSlice({
name: 'prodSlice',
initialState,
reducers: {
fetchData: (state, action) => {
state.isLoading = true;
state.mainList = action.payload;
state.products = action.payload;
}
},
extraReducers: {
[fetchPopularProducts.fulfilled]: (state, action) => {
state.popular = action.payload;
},
},
});
// Action creators are generated for each case reducer function
export const {fetchData} = productSlice.actions;
export const selectProducts = state => state.prodSlice;
export default productSlice.reducer;
Then you dispatch fetchPopularProducts inside the useEffect hook. I cases where I needed a parameter for the query, I'd put the query inside createAsyncThunk.
Maybe I'm missing something completely obvious but this has been tripping me up today.
Let's say we have a Redux store with a structure like so:
const state = {
...
pages: {
...
accountPage: {
currentTab: 'dashboard',
fetching: false,
tableSettings: {
sortDir: 'asc',
sortField: 'name'
}
}
}
}
So there is obviously a main reducer...
export default combineReducers({
...
pages: pagesReducer
...
});
Then the reducer for pages has the reducer for each page...
export default combineReducers({
...
accountPage: accountPageReducer
...
});
And now finally we get down to the meat of the problem, the reducer for this particular piece of state.
export default handleActions({
[setCurrentTab]: (state, action) => { ... },
[setIsFetching]: (state, action) => { ... }
});
That's all good right? Well, the key in the state given at the outset at tableSettings should actually be handled by it's own reducer. This pattern may exist many times in the state, so it is abstracted away to a reducer-creating function:
const defaultState = {
sortDir: 'asc',
sortField: null
};
export const createTableSettingReducer (actions, extra ={}) => {
return handleActions({
[actions.changeSortDir]: (state, action) => ({ ...state, sortDir: action.payload }),
[actions.changeSortField]: (state, action) => ({ ...state, sortField: action.payload }),
...extra
}, defaultState)
}
So, above the reducer for the sections of state (accountPageReducer), we created the reducer:
// pretend these actions were imported
const tableSettingsReducer = createTableSettingReducer({
changeSortDir: setSortDir,
changeSortField: setSortField
});
So the question is, where do I put tableSettingsReducer?
This of course, doesn't work:
export default handleActions({
[setCurrentTab]: (state, action) => { ... },
[setIsFetching]: (state, action) => { ... },
tableSettings: tableSettingsReducer
});
It doesn't work because handleActions expects to use the action constants as keys, not the actual key in the state.
There is also nowhere to use combineReducers, since there is only one nested reducer of this slice of state. currentTab and fetching do not need their own reducer, so it's fruitless to use combineReducers.
I know that recently redux-actions started support nested reducers...but there isn't really any documentation available showing exactly how it's supposed to be done, or even describing the parameters needed to make it happen.
I could possibly use combineActions, and combine all of the actions in handleActions for every action that can be taken by a nested reducer. But that doesn't seem very clean...plus, what if the nested reducer has it's own nested reducers? That means every time those reducers can process a new action, that action needs to be added to combineActions in all its parents. Not the best.
Thoughts?
Every key in your state gets its own reducer. Some reducers are really simple, some are themselves composed of other reducers. All the sister keys at each level of your state tree can be combined with combineReducers.
const initialCurrentTab = 'dashboard';
const currentTabReducer = handleActions({
[setCurrentTab]: (state, action) => {
return action.payload;
},
}, initialCurrentTab);
const defaultFetchingState = false;
const fetchingReducer = handleActions({
[setIsFetching]: (state, action) => {
return action.payload;
},
}, defaultFetchingState);
export default combineReducers({
currentTab: currentTabReducer,
fetching: fetchingReducer,
tableSettings: tableSettingsReducer,
});
let say you have the initialState = { data : []}
let assume that the upcoming action has payload of an array
export the reducer as the following :
return handleActions({
["Action Type 1" ]: (state, { payload }) => {
return { ...state, data: [...state.data, ...payload ]} ;
},
["Action Type 1" ]: (state, { payload }) => {
return { ...state, data: [...state.data, ...payload ]} ;
},
}, initialSate );
import this reducer in your combine reducer .