馃憢 Hey There, first off thank you, this toolkit is extremely helpful!
Question:
Is there something special needed to read the top level state (including the initial state)?
When I attempt to console.log the state returnsnull instead of the expected empty object {}. I did not know if this was intentional and couldn't find it in the docs.
import { createSlice, configureStore } from "@reduxjs/toolkit";
const example = createSlice({
name: "example",
initialState: {},
reducers: {
setName: (state, action) => {
console.log(state); // null
state.name = action.payload;
}
}
});
const store = configureStore({
reducer: example.reducer
});
store.dispatch(example.actions.setName("test"));
Thanks for your time!
Hmm. I'm not seeing "null" when I step through this sandbox (and btw, _thank_ you for including a sandbox with this issue!). What I'm seeing is Proxy:

Where exactly are you seeing null? Can you provide a screenshot?
Thanks for checking it out!
In the console logs for codesandbox:

(Firefox)

I'm going to chalk this up to something about how logging Proxies works.
If I put a breakpoint in the reducer, and drill down inside of state, here's what I see as the contents inside the Proxy:

So there is an empty object inside there.
After the state.name = line, at the end of the function, here's what the resulting Proxy looks like:

and now Immer shows that a copy has been made including the changes.
The Redux DevTools show that both stores were correctly updated.
So, I think this is _just_ an issue with how the Proxies are getting logged , not a functionality problem.
Sounds good! Thanks again for looking into this, I am sorry it ended up being frivolous.
After looking again at the source project (the one that got me started looking into this). I made a mistake with the scope of slice. I mistook the slice state to be the global state 馃う鈥嶁檪 haha! Sorry again!
I have the same question as mentioned above. How can I access state from another slice inside createSlice if there is dependency between slices? I need something like this:
const example = createSlice({
name: "example",
initialState: {},
reducers: {
setName: (sliceState, action, globalState) => {}
}
});
Hey @iviireczech!
I think you may be able to take advantage of thunks being inside of @reduxjs/toolkit for global state! Below is a bit simple and likely would need a lot of help, but it does show the power of using Thunks for using global state before sending an action to a reducer. It doesn't give access to the state in the slice BUT it does give access to global state before it gets there!
Sincerely hope this helps your case!
import { createSlice, configureStore, combineReducers } from "@reduxjs/toolkit";
const books = createSlice({
name: "books",
initialState: {},
reducers: {
add: (state, action) => {
state[action.payload.id] = action.payload;
}
}
});
const movies = createSlice({
name: "movies",
initialState: {},
reducers: {
add: (state, action) => {
state[action.payload.id] = action.payload;
}
}
});
const whitelist = createSlice({
name: "whitelist",
initialState: {},
reducers: {
add: (state, action) => {
state.push(action.payload);
}
}
});
const store = configureStore({
reducer: combineReducers({
books: books.reducer,
movies: movies.reducer,
whitelist: whitelist.reducer
}),
preloadedState: {
books: {},
movies: {},
whitelist: ["The Hound of the Baskervilles"]
}
});
// Thunk has access to global state
function addNewItem(item) {
return (dispatch, getState) => {
const { whitelist } = getState();
if (!whitelist.includes(item.title)) {
console.log(`Rejected title ${item.title}, not in whitelist`);
return;
}
if (item.type === "book") {
dispatch(books.actions.add(item));
} else if (item.type === "movie") {
dispatch(movies.actions.add(item));
}
};
}
// adding the book fails because it is not in the whitelist
store.dispatch(
addNewItem({
type: "book",
title: "20000 Leags Under the Sea",
id: 9787538729368,
url:
"https://www.amazon.com/000-Leagues-Under-Signet-Classics/dp/0451531698"
})
);
// movie gets added to state because it is in the whitelist
store.dispatch(
addNewItem({
type: "movie",
title: "The Hound of the Baskervilles",
id: "tt0031448",
url: "https://www.imdb.com/title/tt0031448/"
})
);
// updates whitelist to include new title
store.dispatch(whitelist.actions.add("20000 Leags Under the Sea"));
// book gets added to state because it is in the whitelist
store.dispatch(
addNewItem({
type: "book",
title: "20000 Leags Under the Sea",
id: 9787538729368,
url:
"https://www.amazon.com/000-Leagues-Under-Signet-Classics/dp/0451531698"
})
);
console.log(JSON.stringify(store.getState(), null, 2));
But also keep in mind that you usually don't want to do that.
A slice should be self-contained and not rely on the contents of other slices, but only on the last state of the slice and the information that came in via the action.
If that doesn't work, the scope of your slice is probably wrong.
True!
@phryneas: I think that having dependent slices is perfectly valid. See this, this or this
Maybe it is too uncommon use-case but I would like to know if this can be achieved using createSlice only without need for reduce-reducer or custom root reducer.
As I write above, something like this would be great:
const example = createSlice({
name: "example",
initialState: {},
reducers: {
setName: (sliceState, action, globalState) => {}
}
});
@phryneas: I think that having dependent slices is perfectly valid. See this, this or this
Maybe it is too uncommon use-case but I would like to know if this can be achieved using createSlice only without need for reduce-reducer or custom root reducer.
As I write above, something like this would be great:
const example = createSlice({ name: "example", initialState: {}, reducers: { setName: (sliceState, action, globalState) => {} } });
These cases all describe parallel reducers, with patterns that add additional information to a reducer that is passed as an additional argument - but never with a pattern that passes the whole state as an argument.
If we introduced that "whole state" argument, people would probably make heavy use of it, even in cases where it wasn't necessary and it would lead to a lot of spaghetti code down the line.
And the reason for RTK is to provide an opinionated framework to prevent such code.
The approach offered in the comments you linked were (for a good reason) highly selected and narrowly-scoped.
My personal approach, if you really needed this, would be to add a middleware, that for some actions waited for the action to be processed, then take part of the derived state and put that (along with the old payload) onto a new action that would be processed by the second slice.
Or for a function that you can wrap around your two slices that does the same thing. Probably a middleware would not be necessary there.
But, more practically it might be work the thought to just create one slice that contained more information. If these states are so dependent on each other, most of the times they logically are the same slice and the divide between them is purely artificial.
@phryneas: Thanks for your ideas, they are interesting. Must give them a try
Per the linked issues, this question actually has nothing to do with the createSlice itself. It's really about how Redux's standard default approach for dividing up reducer logic is by "slice reducer" in general.
These two docs references summarize the suggested approaches for handling this:
https://redux.js.org/faq/reducers#how-do-i-share-state-between-two-reducers-do-i-have-to-use-combinereducers
https://redux.js.org/recipes/structuring-reducers/beyond-combinereducers
In addition, note that the Style Guide advice to model actions as "events", not "setters" also applies here.
Most helpful comment
Hey @iviireczech!
I think you may be able to take advantage of thunks being inside of
@reduxjs/toolkitfor global state! Below is a bit simple and likely would need a lot of help, but it does show the power of using Thunks for using global state before sending an action to a reducer. It doesn't give access to the state in the slice BUT it does give access to global state before it gets there!Sincerely hope this helps your case!