Share actions between slices with Redux Toolkit - javascript

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
},
});

Related

Attempted to assign to readonly property Error when using redux

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?

Is there a way to separate reducer actions in a different file from createSclice in redux?

I have this redux slice setup in store/slices/sourcesSlice.ts
import { addInitialSources} from "store/slices/sourcesActions.ts";
export const sourcesSlice = createSlice({
name: "sourcesSlice",
initialState,
reducers: {
addSource: (state, action: PayloadAction<SourceInterface>) => {
state.sources.push(action.payload);
},
changeName: (state) => {
state.naming = "Bob Marley";
},
},
extraReducers: (builder) => {
builder
.addCase(addInitialSources.fulfilled, (state, { payload }) => {
state.loading = false;
state.sources = payload;
})
},
});
export const { addSource, changeName } = sourcesSlice.actions;
Note how I have a number of actions in store/slices/sourcesSlice.ts and a number of actions in store/slices/sourcesActions.ts
All addSource, changeName, addInitialSources are related actions. **Shouldn't they all be accessible from one endpoint? So when I import any of those, I think it's cleaner, easier to remember if I can do this
import { addInitialSources, addSource, changeName} from "store/slices/sourcesActions.ts";
Rather than this
import { addInitialSources} from "store/slices/sourcesActions.ts";
import { addSource, changeName} from "store/slices/sourcesSlice.ts";
Is such a thing possible? To leave export const sourcesSlice = createSlice... in sourcesSlice.ts but to call all the actions from sourcesActions.ts
AND IS IT RECOMMENDED

How to use single action type in different reducer function with createSlice method in redux-toolkit

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'
})
}
});

Redux action to reset - states are equal

I have the following initialState For React Redux:
const inistialStateRedux = {
configuredFilters: {
data: {
countries: [],
divisions: [],
companies: [],
locations: [],
fields: [],
search: '',
},
},
};
Now I want to create a RESET reducer.
It looks like that:
export const createReducer = (initialState, handlers) => (
state = initialState,
action
) => {
if (action.type in handlers) {
return handlers[action.type](state, action);
}
return state;
};
export const multiUse = (reducer, name = '') => (state = null, action) => {
if (action.name !== name) return state;
return reducer(state, action);
};
import {
createReducer
} from '../helper';
import * as Action from './actions';
import inistialStateRedux from '../inistialStateRedux';
export default createReducer({
data: {},
}, {
[Action.RESET_CONFIGURED_FILTERS]: (state) => ({
...state,
data: {
...inistialStateRedux.configuredFilters.data,
},
}),
});
But Redux Devtools shows, that the states are equal. What am I doing wrong ?
In the Actions you can use a dispatcher to reset the Form.
Like this:
import axios from 'axios';
import { ADD} from '../modelType';
import { reset } from 'redux-form';
export const add= formValues => async dispatch => {
const res = await axios.post('/api/model/', { ...formValues });
dispatch({
type: ADD,
payload: res.data
});
dispatch(reset('yourUniqueFormName'));
};

Reset state to initial with redux-toolkit

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);
};

Categories