Redux toolkit get access to full state from extra reducers - redux

How can I get the full state from the extraReduces section?
createSlice({
name: 'foos',
initialState: {
foos: [],
},
reducers: {},
extraReducers: builder => {
builder.addCase(
barAction,
(state, action) => {
const barId = action.payload.id; // barAction handler, the payload is the bar's id
const bar = ??? // getStore().bars[barId]
state.foo[bar.fooId].baz = true;
}
);
},
});
I want to manipulate the Foo slice from Bar action. But the Bar action payload is just the id of the Bar entity.
How can I retrieve the Bar entity from the Foo slice if I only have the Bar ID? Is there any way to get the full store?

Redux slice reducers only have access to their own slice of the state, by definition.
However, we do encourage multiple reducers responding to the same dispatched action.

Related

How to update or change the entire propery of a particular redux object

Am having issues updating a redux store in NEXTJS. am building a CV platform with the feature to preview users' input almost immediately into a preview page. this cv platform has the experience, education etc that a normal cv platform should have and am using the react hook form package to manage forms and also to enhance dynamic forms.
so because the preview component will be another project on its own, I need the best way to pass data from my app into the preview app. Then I thought of passing every form input, cv styles, and data to a redux store so the preview component can just get the user's data from the store
as I said earlier, am using the react hooks form library to manage my form, so to update the store in real-time whenever the user inputs anything, I imported the useWatch hook from react hook form to watch my form in case of any data changes. so I set up a useEffect to listen for any useWatch change to dispatch the whole useWatch data to the store. NB: this data contains an array of objects
my challenge right now is that anytime I dispatch the data to store, redux toolkit or probably immer frowns at what am doing and will always break the app, returning back this error message
TypeError: Cannot assign to read only property 'jobTitle' of object '#<Object>'
at set (index.esm.mjs?b902:507:1)
at onChange (index.esm.mjs?b902:1749:1)
at HTMLUnknownElement.callCallback (react-dom.development.js?ac89:4164:1)
at Object.invokeGuardedCallbackDev (react-dom.development.js?ac89:4213:1)
at invokeGuardedCallback (react-dom.development.js?ac89:4277:1)
at invokeGuardedCallbackAndCatchFirstError (react-dom.development.js?ac89:4291:1)
at executeDispatch (react-dom.development.js?ac89:9041:1)
at processDispatchQueueItemsInOrder (react-dom.development.js?ac89:9073:1)
at processDispatchQueue (react-dom.development.js?ac89:9086:1)
at dispatchEventsForPlugins (react-dom.development.js?ac89:9097:1)
at eval (react-dom.development.js?ac89:9288:1)
at batchedUpdates$1 (react-dom.development.js?ac89:26140:1)
at batchedUpdates (react-dom.development.js?ac89:3991:1)
at dispatchEventForPluginEventSystem (react-dom.development.js?ac89:9287:1)
at dispatchEventWithEnableCapturePhaseSelectiveHydrationWithoutDiscreteEventReplay (react-dom.development.js?ac89:6465:1)
at dispatchEvent (react-dom.development.js?ac89:6457:1)
at dispatchDiscreteEvent (react-dom.development.js?ac89:6430:1)
Below is the redux store and how am setting the experience
const initialState: IResume = {
templatePrimaryColor: "#335384",
top: 0,
showOverlay: false,
cv_object: {
templateId: 1,
personalInformation: {} as PersonalInformation,
experiences: [] as Experience[],
educations: [] as Education[],
skills: [] as Skill[],
awards: [] as Award[],
certificates: [] as Certificate[],
publications: [] as Publication[],
references: [] as Reference[],
},
};
export const resumeSlice = createSlice({
name: "resume",
initialState,
reducers: {
setExperience: (state, action: PayloadAction<Experience[]>) => {
// state.cv_object.experiences = [...action.payload];
state.cv_object.experiences = Object.assign(state.cv_object.experiences, action.payload);
},
},
});
Below is how am setting the forms and how am dispatching it
//React hooks form initialSetup
const { register, control, handleSubmit } = useForm<CvObject>({
defaultValues: {
experiences: [{ ...ExperienceDefaultValues }],
},
});
//usefieldArray for dynamic forms
const { append, fields, remove } = useFieldArray({ control, name: "experiences" });
//dispatch the entire form data to experience if any changes is being made
const formValues = useWatch({ control, name: "experiences" });
const [currentFormIndex, setCurrentFormIndex] = useState(0);
useEffect(() => {
if (!useAi) dispatch(hideOverlay());
else dispatch(showOverlay());
}, [useAi]);
useEffect(() => {
dispatch(setExperience(formValues));
}, [formValues]);
const handleAddAnotherExperience = () => {
setCurrentFormIndex((prev) => prev + 1);
append({ ...ExperienceDefaultValues });
};
const handleDelete = (index: number) => {
remove(index);
if (currentFormIndex > 0) setCurrentFormIndex((prev) => prev - 1);
};
const handleEdit = (index: number) => {
setCurrentFormIndex(index);
};
This is the sample object of the experience am passing but Array of Experience
export interface Experience {
companyName: string;
fromYear: string;
toYear: string;
fromMonth: string;
toMonth: string;
currentlyWorking: boolean;
achievements: string;
description: string;
city: string;
country: string;
index: number;
jobTitle: string;
}
So what am really expecting from this is how to change the store or how to replace the previous experience that is in the store with the incoming experience that is being dispatched.
React hook form is the guy handling new object, removing new object with their useFieldArray hooks.
First of all, you shouldn't directly mutate the data in the redux store, so you can use the object spread operator to create new objects and secondly you should always have a return statement in your slice. So your resumeslice should actually be like this
export const resumeSlice = createSlice({
name: "resume",
initialState,
reducers: {
setExperience: (state, action: PayloadAction<Experience[]>) => {
state = {
...state,
cv_object: {
...state.cv_object,
experiences: action.payload
}
}
return state
},
},
});
I believe this should work

slice, action and reducers, what are they?

I'm trying to learn Redux, and i encountered this code:
reducers: {
loginStart: (state) => {
//...
},
loginSuccess: (state, action) => {
//...
},
loginFailure: (state) => {
//...
},
logout: (state) => {
//...
},
},
});
export const { loginStart, loginSuccess, loginFailure, logout } = userSlice.actions;
export default userSlice.reducer;
I can't understand well what are .actions, Slice, .reducer or reducers from different web sources.
So kindly can any expert in Redux here explain in a simplified way what are theses and their roles?
Every state of your app (which is global) lives in an object tree stored in a single store.
Actions are simply JavaScript objects that have a type with a payload of data illustrating exactly what is happening. what they do? they are the only way to manage our state tree. pay attention: no state has been mutated so far.
Reducers are just responses to our corresponding called action to perform on our immutable state and thus returning a new state. optionally you might also want to check Array.reduce() method to better understand reducers.
What is slice then? as it comes with redux-toolkit, slice contains all the reducer logics and actions for a single feature.
it auto generates your action creators and types which you have to define them as constants before redux-toolkit. check createSlice for the full explanation.
In your example the object called reducers goes into your createSlice with also an initial state and a name.
Based on all that being said, this is your final example of your question:
const initialState = {}
const authSlice = createSlice({
name: 'authentication',
initialState,
reducers: {
loginStart: (state) => {
//...
},
loginSuccess: (state, action) => {
//...
},
loginFailure: (state) => {
//...
},
logout: (state) => {
//...
},
},
})
export const { increment, decrement, incrementByAmount } = counterSlice.actions
export default counterSlice.reducer

useReducer and state managing

hey guys im learning the useReducer hook and for the most part it seems to be quite similar to redux (minus the action being sent to the store etc)
the thing i seem to ALWAYS have problems with when i get more complex state management situations is trying to alter my state in the ways i would like to. in my code i am essentially trying to have a user select a track and add it to a list of favorite songs. my code seems to be replacing the state and not adding to it
here is my initial state and my useReducer and then lastly my add function (which when a button is pressed down below it sends in a track to be added to the list
const initialState = {
name:'',
duration:''
};
const [favorites, dispatch]=useReducer(reducer, initialState)
const add=(song)=>{
dispatch({type:'ADD', payload:song})
console.log(favorites)
}
THIS is the part that is confusing me. in my reducer i have this
export const reducer=(song, action)=>{
switch(action.type){
case 'ADD':
return {...song, name:action.payload}
}
WHICH is essentially adding a new object everytime called name: trackname BUT i do not want to overwrite the last item. i feel like i am using spread wrong and also returning the incorrect payload maybe?
my final state keeps looking like this
{name: "deep end", duration: ""}
when i want it to look something like this
``
[{name: "deep end", duration: ""}
{name: "ok", duration: ""}
{name: "testtrack", duration: ""}
]`
i have tried setting the initial state to somethingm like this
const initialState = {
favorites:{
[ADD TRACKS HERE]
}
};
BUT CANT seem to overwrite the state correctly so that it ADDS to the array. it keeps overwritting the last one
Redux's guide to Immutable Update Patterns is a great resource on how to update nested data in a way that doesn't mutate your state.
With an array there are two main ways to immutably add an element.
With spread syntax:
const newArray = [...songs, newSong];
With .concat(), which returns a new array that contains the additional items (that is different from .push() which mutates the array and just returns the length).
const newArray = songs.concat(newSong);
You can decide what you want the shape of your state to be. Storing the array to a property favorites is fine, but adds another layer of complexity to your updates.
export const reducer = (state, action) => {
switch (action.type) {
case "ADD":
return {
...state,
favorites: [...state.favorites, action.payload]
};
default:
return state;
}
};
const initialState = {
favorites: [] // initial state has an empty array
};
const [state, dispatch] = useReducer(reducer, initialState);
// here favorites is just a property of state, not the whole state
const favorites = state.favorites;
I would recommend that that state should just be the array of favorites itself.
export const reducer = (favorites, action) => {
switch (action.type) {
case "ADD":
return [...favorites, action.payload]
default:
return favorites;
}
};
// favorites is the whole state
// initial state is an empty array
const [favorites, dispatch] = useReducer(reducer, []);
In either case, we are expecting the action.payload to be a complete song object, not just the name.
dispatch({ type: "ADD", payload: { name: "Deep End", duration: "3:22" } });
You could extract that into a helper function. In Redux terms we call this function an Action Creator.
const addFavorite = (name, duration) => ({
type: "ADD",
payload: { name, duration }
});
dispatch(addFavorite("Deep End", "3:22"));

How i can get some ids from entity (redux toolkit)?

I have Redux Toolkit - slice, entityAdapter
How i can remove messages for one channal by channel ID?
const messagesAdapter = createEntityAdapter();
const messages = createSlice({
name: 'messages',
initialState: messagesAdapter.getInitialState(),
reducers: {
addMessage: messagesAdapter.addOne,
},
extraReducers: {
[channels.actions.removeChannel]: (state, action) => {
const idCahnnel = action.payload;
const idsForRemove = // how i can get ids here? if i have idCahannel only
// message look like { id: 1, idChannel: 2, nickname: 'nickname', text: 'sometext' }
// i cannot filter state.entity because state is a Proxy here
messagesAdapter.removeMany(state, idsForRemove);
},
},
});
You can do operations against the existing state and its nested fields like state.entities. Yes, the "draft state" has been wrapped in a Proxy, but you can interact with the values as if it were still a plain JS object like normal.
See the Redux Toolkit docs page on Writing Reducers with Immer for instructions on how to correctly work with Immer's draft state values.

How to use Redux Promise Middleware with slices in Redux Toolkit?

I'm using the slices feature of Redux Toolkit. As you may know, one slice returns an action for every created reducer.
I want to use the redux-promise-middleware library, which for a given ACTION_TYPE, it creates three possible new actions: ACTION_TYPE_FETCHING, ACTION_TYPE_FULFILLED, and ACTION_TYPE_REJECTED. How do I handle this from the point of view of the slices?
You'd need to add the expected action types to the extraReducers section of createSlice, like:
// could use a predefined action constant if desired:
const actionFetching = "ACTION_TYPE_FETCHING"
const usersSlice = createSlice({
name: "users",
initialState,
reducers: {
// specific case reducers here
},
extraReducers: {
// could use computed key syntax with a separate type constant
[actionFetching ]: (state, action) => {}
// or the actual field name directly matching the type string
ACTION_TYPE_FULFILLED: (state, action) => {}
}
})

Resources