I need to reset current state to initial state. But
all my attempts were unsuccessful. How can I do it using redux-toolkit?
const showOnReviewSlice = createSlice({
name: 'showOnReview',
initialState: {
returned: [],
},
reducers: {
reset(state) {
//here I need to reset state of current slice
},
},
});
Something like this:
const intialState = {
returned: []
}
const showOnReviewSlice = createSlice({
name: 'showOnReview',
initialState,
reducers: {
reset: () => initialState
}
});
This worked for me (mid-late 2020). Formatted with your code context as an example.
const initialState = {
returned: [],
};
const showOnReviewSlice = createSlice({
name: 'showOnReview',
initialState,
reducers: {
reset: () => initialState,
},
});
Replacing state with initialState directly did not work for me (mid 2020). What I finally got working was to copy each property over with Object.assign(). This worked:
const showOnReviewSlice = createSlice({
name: 'showOnReview',
initialState: {
returned: []
},
reducers: {
reset(state) {
Object.assign(state, initialState)
}
}
});
When using multiple slices, all slices can be reverted to their initial state using extraReducers.
First, create an action that can be used by all slices:
export const revertAll = createAction('REVERT_ALL')
In every slice add an initialState, and an extraReducers reducer using the revertAll action:
const initialState = {};
export const someSlice = createSlice({
name: 'something',
initialState,
extraReducers: (builder) => builder.addCase(revertAll, () => initialState),
reducers: {}
});
The store can be created as usual:
export const store = configureStore({
reducer: {
someReducer: someSlice.reducer,
}
})
And in your react code you can call the revertAll action with the useDispatch hook:
export function SomeComponent() {
const dispatch = useDispatch();
return <span onClick={() => dispatch(revertAll())}>Reset</span>
}
In my case, as the previous answer, mid 2021, just setting the initial state DO NOT WORK, even if you use the toolkit adapter like :
reducers: {
// Other reducers
state = tasksAdapter.getInitialState({
status: 'idle',
error: null,
current: null
})
}
},
instead, you should use Object.assign(), guess that it's related with the internal immer library behavior
We do it like this guys.
Suppose you want to clear all the data at the point of logging out.
In your store.tsx file:
import { AnyAction, combineReducers, configureStore } from '#reduxjs/toolkit';
import authReducer from './slices/authSlice'
import messageReducer from './slices/messageSlice'
const appReducer = combineReducers({
auth: authReducer,
message: messageReducer,
});
const reducerProxy = (state: any, action: AnyAction) => {
if(action.type === 'logout/LOGOUT') {
return appReducer(undefined, action);
}
return appReducer(state, action);
}
export const store = configureStore({
reducer: reducerProxy,
});
Then you create a thunk like this:
export const logout = createAsyncThunk(
"auth/logout",
async function (_payload, thunkAPI) {
thunkAPI.dispatch({ type: 'logout/LOGOUT' });
console.log('logged out')
}
);
You can use spread opearator for initialState
const initialState: {
returned: unknown[] //set your type here
} = {
returned: []
}
const showOnReviewSlice = createSlice({
name: 'showOnReview',
initialState,
reducers: {
reset() {
return {
...initialState
}
}
}
});
Try this. In my case, I wanted to return all slices to initialState when a certain action is dispatched.
First, let's create action:
import { createAction } from '#reduxjs/toolkit';
export const resetPanelsAction = createAction('resetPanelsData');
When creating our store, we save a copy of the initialState in the middleware:
import { Middleware } from '#reduxjs/toolkit';
export const resetDataMiddleware: Middleware =
({ getState }) =>
(next) => {
// "Caching" our initial app state
const initialAppState = getState();
return (action) => {
// Let's add the condition that if the action is of
// type resetData, then add our cached state to its payload
if (action.type === 'resetData') {
const actionWithInitialAppState = {
...action,
payload: initialAppState,
};
return next(actionWithInitialAppState);
}
return next(action);
};
};
Almost done! Now let's change our root reducer a little by adding a wrapper that will check the action type, and if it is equal to resetData, then return combinedReducers with our initialState, which will be in payload.
import { AnyAction } from 'redux';
import { combineReducers } from '#reduxjs/toolkit';
export const combinedReducers = combineReducers({
/** Your reducers */
});
export const rootReducer = (
state: ReturnType<typeof combinedReducers> | undefined,
action: AnyAction,
) => {
if (action.type === 'resetPanelsData') {
return combinedReducers(action.payload, action);
}
return combinedReducers(state, action);
};
Related
my slice and reducer :
import { createSlice } from '#reduxjs/toolkit'
export const TransactionsListSlice = createSlice({
name: 'transactions_list',
initialState: {
value: []
},
reducers: {
saveTransactionsList: (state, action) => {
state.value = action.payload
}
}
})
export const { saveTransactionsList } = TransactionsListSlice.actions
export default TransactionsListSlice.reducer
let transactions_list = useSelector(state => state.transactions_list.value);
when trying to update my transactions_list with new data i have the below error:
Attempted to assign to readonly property.what is wrong?
I am relatively new to React Redux - I am trying to change my add to cart logic so as items in the cart can increment their quantity and prices instead of duplicating as separate cart items. For this I have changed my store.js from legacy createStore to configureStore. Below is the code:
import { configureStore } from '#reduxjs/toolkit'
import cartItems from './Reducers/cartItem'
const store = configureStore({
reducer: {
cart: cartItems
}
})
export default store;
Below is cartItem.js file \ reducer code [Initially this had the the ADD_TO_CART, REMOVE_FROM_CART switch case code]
import { createSlice } from "#reduxjs/toolkit";
const initialState = {
cartItems: [],
cartTotalQuantity: 0,
cartTotalAmount: 0,
};
const cartItems = createSlice({
name: "cart",
initialState,
reducers: {
addToCart(state, action) {
const itemIndex = state.cartItems.findIndex(
(item) => item.id === action.payload.id
);
if (itemIndex >= 0) {
state.cartItems[itemIndex].cartQuantity += 1;
} else {
const tempProduct = { ...action.payload, cartQuantity: 1 };
state.cartItems.push(tempProduct);
}
},
},
});
export const { addToCart } = cartItems.actions;
export default cartItems.reducer;
The challenge is on what do i need to change on the mapDispatchToProps below on my ProductCard.js file for the non-serializable error to disappear.
const mapDispatchToProps = (dispatch) => {
return {
addItemToCart: (product) =>
dispatch(actions.addToCart(product))
} }
export default connect(null, mapDispatchToProps)(ProductCard);
How can I share common actions in redux state slices?
Say I want to share updateField defined below in multiple slices other than profile. Should I import it from external file?
I could use extraReducers from #redux/toolkit but then I should create a new slice containing my updateField action and that action don't have state of its own. It just use the state of the slice it's imported in.
import { createSlice } from '#reduxjs/toolkit';
export const INITIAL_STATE = { title: { value: '' } }
const slice = createSlice({
name: 'profile',
initialState: INITIAL_STATE,
reducers: {
updateField: (state, { payload: { name, value } }) => ({
...state,
[name]: {
...state.data[name],
value,
},
}),
},
})
export const {
updateField,
} = slice.actions
Thanks
You can use the extraReducers property on createSlice to listen to custom actions.
// actions
export const incrementBy = createAction<number>('incrementBy')
// reducers
const counter1 = createSlice({
name: 'counter',
initialState: 0,
reducers: {
decrement: (state) => state - 1,
},
// "builder callback API"
extraReducers: (builder) => {
builder.addCase(incrementBy, (state, action) => {
return state + action.payload
})
},
})
const counter2 = createSlice({
name: 'counter',
initialState: 0,
reducers: {
decrementByTwo: (state) => state - 2,
},
// "builder callback API"
extraReducers: (builder) => {
builder.addCase(incrementBy, (state, action) => {
return state + action.payload
})
},
})
If you are wanting to implement a single reducer function you can certainly define it once and export/import into the slices that need it.
Example:
export const updateField = (state, { payload: { name, value } }) => ({
...state,
[name]: {
...state.data[name],
value,
},
})
Profile slice
import { createSlice } from '#reduxjs/toolkit';
import { updateField } from '../path/to/updateField'; // <-- import
export const INITIAL_STATE = { title: { value: '' } }
const slice = createSlice({
name: 'profile',
initialState: INITIAL_STATE,
reducers: {
updateField, // <-- pass to reducers object
},
});
User slice
import { createSlice } from '#reduxjs/toolkit';
import { updateField } from '../path/to/updateField'; // <-- import
export const INITIAL_STATE = { title: { value: '' } }
const slice = createSlice({
name: 'user',
initialState: INITIAL_STATE,
reducers: {
updateField, // <-- pass to reducers object
},
});
Is it possible that multiple reducer (which is created with createSlice method) must respond to the same action?
import { createSlice } from '#reduxjs/toolkit';
const isAuthenticated = createSlice({
name: 'isAuthenticated',
initialState: false,
reducers: {
loginSuccess(state, action) {
return true;
},
logout(state, action) {
return false;
},
},
});
export const { loginSuccess, logout } = isAuthenticated.actions;
export default isAuthenticated.reducer;
const currentUser = createSlice({
name: 'currenUser',
initialState: 'jhon',
reducers: {
loginSuccess(state, action) {
return 'steve';
},
logout() {
return state;
},
},
});
export const currentUserReducer = currentUser.reducer;
As You can see action.type loginSuccess is in two different reducers since i am only exporting loginSuccess of isAuthenticated i can use only that action to dispatch.
i know i can export loginSuccess from currentUser as well but i want to dispatch only one action and change the state in two different states.
I know this can be done with vanilla redux and also redux recommend using it Here
In short i am trying to replicate this but with createSlice method in redux-tool-kit.
Thanks in advance for helping.
You are looking for extraReducers:
const isAuthenticated = createSlice({
name: 'isAuthenticated',
initialState: false,
reducers: {
loginSuccess(state, action) {
return true;
},
logout(state, action) {
return false;
},
},
});
export const { loginSuccess, logout } = isAuthenticated.actions;
export default isAuthenticated.reducer;
const currentUser = createSlice({
name: 'currenUser',
initialState: 'jhon',
reducers: {
logout() {
return state;
},
},
extraReducers: builder => {
builder.addCase(isAuthenticated.actions.loginSuccess, () => {
return 'steve'
})
}
});
I'm learning Redux-Saga and everything works well with { configureStore, getDefaultMiddleware, createAction, createReducer }. However, I cannot successfully implement createSlice.
My actions seem to be dispatched just fine (though I'm not sure since I have multiple Redux stores and placing console.log inside createSlice doesn't seem to work...). I just cannot get the store values - after dispatched action the relevant state value (initially '') becomes undefined. I did wrap my component inside Provider and all. Can someone enlighten me how does createSlice work? Thanks.
RESOLVED I had a bug somewhere else in my code, that's why the reducers weren't working proberly. BUT what I was asking about and what was causing my problems is this: actions passed to createSlice must be 'pure' functions, meaning: (state, action) -> state, nothing fancy. That's why I had to remove my fetching functions (getData1 and getData2) from this createSlice.
ComponentWrapper returns this
<Provider store={toolkitCreateSliceStore}>
<ReduxToolkitCreateSliceComponent />
</Provider>
Component (Buttons just dispatch actions)
class ReduxToolkitCreateSliceComponent extends React.Component {
render () {
return (
<>
<h2>
{this.props.data1}
{(this.props.data1!=='' && this.props.data2!=='') ? ', ' : ''}
{this.props.data2}
</h2><br/>
<h3>{this.props.message}</h3>
<Button1 />
<Button2 />
<Button3 />
</>
);
}
}
function mapStateToProps(state) {
return {
data1: state.toolkitCreateSliceReducer.data1,
data2: state.toolkitCreateSliceReducer.data2,
message: state.toolkitCreateSliceReducer.message
};
}
export default connect(mapStateToProps)(ReduxToolkitCreateSliceComponent);
Redux Toolkit slice
import { createSlice } from "#reduxjs/toolkit";
import axios from "axios";
const initialSliceState = {
data1: '',
data2: '',
message: ''
};
const slice = createSlice({
name: "slice",
initialState: initialSliceState,
reducers: {
getData1: (state, action) => {
return dispatch => {
dispatch(loading1());
return axios.get('http://localhost:8081/data1')
.then(function (response) {
if (response.status === 200) {
dispatch(setResponse1(response.data));
}
}).catch(error => dispatch(displayError1(error)));
};
},
getData2: (state, action) => {
return dispatch => {
dispatch(loading2());
return axios.get('http://localhost:8081/data2')
.then(function (response) {
if (response.status === 200) {
dispatch(setResponse2(response.data));
}
}).catch(error => dispatch(displayError2(error)));
};
},
setResponse1: (state, action) => {
state.data1 = action.payload;
state.message = 'success';
},
setResponse2: (state, action) => {
state.data2 = action.payload;
state.message = 'success';
},
reset: (state, action) => {
state.data1 = '';
state.data2 = '';
state.message = 'reset';
},
loading1: (state, action) => {
state.message = 'loading';
},
loading2: (state, action) => {
state.message = 'loading';
},
displayError1: (state, action) => {
state.message = action.payload;;
},
displayError2: (state, action) => {
state.message = action.payload;;
}
}
});
export const toolkitCreateSliceReducer = slice.reducer;
const { getData1, getData2, setResponse1, setResponse2, reset, loading1, loading2,
displayError1, displayError2} = slice.actions;
export default slice;
Redux Toolkit store
const middleware = [
...getDefaultMiddleware()
];
const toolkitCreateSliceStore = configureStore({
reducer: {
toolkitCreateSliceReducer
},
middleware
});
export default toolkitCreateSliceStore;
Your "reducers" are very wrong.
A reducer must never have any side effects like AJAX calls.
You've written some Redux "thunk" functions where your reducers should be:
getData1: (state, action) => {
return dispatch => {
dispatch(loading1());
return axios.get('http://localhost:8081/data1')
.then(function (response) {
if (response.status === 200) {
dispatch(setResponse1(response.data));
}
}).catch(error => dispatch(displayError1(error)));
};
},
This is a thunk, not a reducer.
A reducer would be something like:
getData(state, action) {
return action.payload;
}
I'd specifically recommend reading through our brand-new "Redux Essentials" core docs tutorial, which teaches beginners "how to use Redux, the right way", using our latest recommended tools and practices like Redux Toolkit. It specifically covers how reducers should work, how to write reducers with createSlice, and how to write and use thunks alongside createSlice:
https://redux.js.org/tutorials/essentials/part-1-overview-concepts
If you're interested in creating async actions, let me recommend you an npm package that I created and use. It is saga-toolkit that allows async functions to get resolved by sagas.
slice.js
import { createSlice } from '#reduxjs/toolkit'
import { createSagaAction } from 'saga-toolkit'
const name = 'example'
const initialState = {
result: null,
loading: false,
error: null,
}
export const fetchThings = createSagaAction(`${name}/fetchThings`)
const slice = createSlice({
name,
initialState,
extraReducers: {
[fetchThings.pending]: () => ({
loading: true,
}),
[fetchThings.fulfilled]: ({ payload }) => ({
result: payload,
loading: false,
}),
[fetchThings.rejected]: ({ error }) => ({
error,
loading: false,
}),
},
})
export default slice.reducer
sagas.js
import { call } from 'redux-saga/effects'
import { takeLatestAsync } from 'saga-toolkit'
import API from 'hyper-super-api'
import * as actions from './slice'
function* fetchThings() {
const result = yield call(() => API.get('/things'))
return result
}
export default [
takeLatestAsync(actions.fetchThings.type, fetchThings),
]