So based on a recent answer, I've started using ReduxStarterKit, which uses Immer in it's slice function. Overall, I think the basic idea is brilliant.
Unfortunately, when I try to actually make use of immer to make my life easier, I run into issues. I've tried to simplify what I'm doing using my test reducer, and I'm still getting the same basic problem. I've also turned off all my middleware (ElectronRedux) to make sure it's not a problem there. The following is a simplified test reducer I'm using for testing:
const CounterSlice = createSlice({
name: 'counter',
reducers: {
increment: (state)=>{state.value = state.value + 1},
decrement: (state)=>{state.value = state.value - 1}
},
initialState: { value: 0 },
})
The code above is pretty simple, and as far as I can tell exactly what Immer/ReduxStarterKit wants me to write. Despite this, when I call the code I'm getting an error: Uncaught Error: Immer drafts cannot have computed properties
What am I doing wrong?
Edit:
I just put together a simple demo app, just for the purposes of testing the basics. The counter slice has coded here works perfectly. I guess this is an interaction between Immer and another package -- just not sure where to go about debugging which one. Is it a redux version issue, is it electron, electron redux, typescript, webpack, the list goes on and (painfully) on.
I may have to recreate my basic app environment and test this one painful step at a time. Ugh!
Turns out the problem wasn't in the code I was sharing, it was way over to the side when I was setting up my initial state. Electron was manipulating the data I sent back and forth via getState(), adding getter & setter methods. Those getter & setter methods were (quite rightly) triggering this error.
const initialState = remote.getGlobal('state');
console.log("initial state: ", initialState);
const store = CreateStore({initialState:{...initialState}, main: false})
Expected log output: { counter: { value: 1 } ...rest}, but actual output was { counter: {value: 1, getValue: function(), setValue: function() }, getCounter: function(), setCounter: function(), ...rest.
Woops. Now I just need to 'clean' my state up, since apparently state storage somehow persists this failure. And then figure out how to strip the (nested!) getters/setters. Ugh.
Edit: Rather than stripping the getters / setters, I just needed to use the built-in getInitialStateRenderer from electron-redux. So change the above code to:
const initialState = getInitiateStateRenderer()
const store = CreateStore({initialState: initialState, main: false})
Related
I have a nuxt application. One of the components in it's mounted lifecycle hook is requesting a value from the state store, this value is retrieved from local storage. The values exist in local storage however the store returns it as undefined. If I render the values in the ui with {{value}}
they show. So it appears that in the moment that the code runs, the value is undefined.
index.js (store):
export const state = () => ({
token: process.browser ? localStorage.getItem("token") : undefined,
user_id: process.browser ? localStorage.getItem("user_id") : undefined,
...
Component.vue
mounted hook:
I'm using UserSerivce.getFromStorage to get the value directly from localStorage as otherwise this code block won't run. It's a temporary thing to illustrate the problem.
async mounted() {
// check again with new code.
if (UserService.getFromStorage("token")) {
console.log("user service found a token but what about store?")
console.log(this.$store.state.token, this.$store.state.user_id);
const values = await ["token", "user_id"].map(key => {return UserService.getFromStorage(key)});
console.log({values});
SocketService.trackSession(this, socket, "connect")
}
}
BeforeMount hook:
isLoggedIn just checks that the "token" property is set in the store state.
return !!this.$store.state.token
beforeMount () {
if (this.isLoggedIn) {
// This runs sometimes??? 80% of the time.
console.log("IS THIS CLAUSE RUNNING?");
}
}
video explanation: https://www.loom.com/share/541ed2f77d3f46eeb5c2436f761442f4
OP's app is quite big from what it looks, so finding the exact reason is kinda difficult.
Meanwhile, setting ssr: false fixed the errors.
It raised more, but they should probably be asked into another question nonetheless.
I am trying to use RTK Query mutations to upload a file to the API. Here is my mutation code:
addBanner: builder.mutation({
query(body) {
return {
url: `/api/banners`,
method: 'POST',
body,
}
},
})
Here is how I generate the data for request.
const [addBanner, { isBannerLoading }] = useAddBannerMutation();
const new_banner = new FormData();
new_banner.append("file", my_file);
new_banner.append("type", my_type);
new_banner.append("title", my_title);
addBanner(new_banner).unwrap().then( () => ...
But I get an error:
A non-serializable value was detected in the state, in the path: `api.mutations.L-Mje7bYDfyNCC4NcxFD3.originalArgs.file`...
I know I can disable non-serializable check entirely through middleware, but I don't think it is an appropriate way of using Redux Toolkit and RTK. Without a file all works fine. Is there any right way of uploading files with RTK?
Edit: This has been fixed with #reduxjs/toolkit 1.6.1 - please update your package
I just opened an issue for this: https://github.com/reduxjs/redux-toolkit/issues/1239 - thanks for bringing it up!
For now, you'll probably have to disable that check (you can do so for a certain path in the state while keeping it for the rest with the ignoredPath option).
Trying to understand the ngrx example app, got stuck and unable to figure out its use case.
What is the importance of State in the below code taken from ngrx-example-app
export interface State extends fromRoot.State {
[booksFeatureKey]: BooksState;
}
/** Provide reducer in AoT-compilation happy way */
export function reducers(state: BooksState | undefined, action: Action) {
return combineReducers({
[fromSearch.searchFeatureKey]: fromSearch.reducer,
[fromBooks.booksFeatureKey]: fromBooks.reducer,
[fromCollection.collectionFeatureKey]: fromCollection.reducer,
})(state, action);
}
Doing this has no run-time differences, it's only to make typescript aware you "have" a root state.
This is needed to make typescript happy if you access root state or root selectors from within your feature state selectors.
Following the style of this Facebook app sample using Redux and Flow together, I made an action type in this manner:
type Action =
| { type: 'ADD_FILES', files: Array<{ id: number, file: File }> }
| { type: 'HANDLE_IMAGE_PUBLISHED', id: number, name: string }
| { type: 'SET_IMAGE_UPLOAD_PROGRESS', id: number, progress: number }
;
But I've found that when I try to process my actions with a reducer, Flow complains if I try to access the name or progress properties, saying "Property not found in object type".
That is, in my reducer, if I check that action.type === 'HANDLE_IMAGE_PUBLISHED' and then access action.name, Flow complains. And the same thing goes for for accessing action.progress property when action.type === 'SET_IMAGE_UPLOAD_PROGRESS'. Both these property accesses should be legit under their respective circumstances, as far as I can tell, but Flow complains.
Yet for some reason it's OK for me to access action.id anywhere, even though one of the types in my union doesn't specify an id property. I'm very confused.
Here is a live demo in the Flow REPL. What am I doing wrong?
This is simply a case of a type refinement invalidation:
https://flow.org/en/docs/lang/refinements/#toc-refinement-invalidations
Because you are using the value in a callback, Flow pessimistically assumes that you could have re-assigned action before the callback runs (it does not know that the map callback is called immediately). It also does not do the analysis to see that there is no place, in fact, that you re-assign it.
All that's needed is to pull the action out as a const:
export default (state: Array<ImageRecordModel> = [], action_: Action): Array<ImageRecordModel> => {
const action = action_;
(tryflow link)
You may also want to consider enabling const params in your .flowconfig. This does basically what you expect: treats all params as const:
[options]
experimental.const_params=true
I am using redux-observable in my redux react app. Given below is the code where I wire everything up. I am using redux-dev tools as well.
const rootEpic = combineEpics(
storeEpic
);
const epicMiddleware = createEpicMiddleware(rootEpic);
//Combine the reducers
const reducer = combineReducers({
syncSpaceReducer,
routing: routerReducer
});
const loggerMiddleware = createLogger();
const enhancer = compose(
applyMiddleware(thunkMiddleware, loggerMiddleware, epicMiddleware),
DevTools.instrument()
);
const store = createStore(reducer, enhancer);
My epic has the following code
export const storeEpic = action$ =>
action$.ofType('FETCH_STORES')
.mapTo({
type: 'FETCHHING_STORES'
});
Now when I run the app I get the following error
Uncaught TypeError: Cannot read property 'apply' of undefined
What am I doing wrong here?
I'm not sure it's possible for us to say. The code you provided all seems fine. The easiest way to know what's wrong is to look at the stack trace for that error. Where does it happen? What led up to it happening? Set a breakpoint there or use "Pause on exceptions" in your debugger to see what exactly is trying to call apply on a variable that is undefined--why is it undefined? That's ultimately the real question.
Edit based on your comments.
The problem is that what you're passing to combineEpics isn't actually your epic, it's undefined. Often that happens when your epic import has a typo or is not exported as you expect. Pause your debugger right before combineEpics and you'll see which one is undefined (or all of them).
Just import storeEpic in the file where combineEpics is called and it will work fine