Using Flow union types for Redux actions - redux

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

Related

NuxtJS store returning local storage values as undefined

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.

Errors on logout of react-admin

When a user logs out, we get a number of errors in our components due to the redux store being cleared out before you are taken to the login page.
We have some components that use the useQueryWithStore. Here is an example:
const { data, total, error, loaded } = useQueryWithStore({
type: "getList",
resource: "tasks",
payload: {
pagination: { page: 1, perPage: 5 },
sort: { field: "date_end", order: "ASC" },
},
});
Then we are checking whether the data is loaded and there are no errors before rendering the component.
return (
{!loaded && !error ? (
<Loading />
) : (
...our component rendering data...
)
)
The problem is that when a user logs out, it first clears the redux store before navigating to the login page. That means that both !loaded and !error are true and it attempts to render the component. But because the redux store has been cleared, data is undefined.
How would you handle this best?
Add default value to data? - const {data = [], total, error, loaded}
Check if data is undefined? - {!loaded && !error && data}
Use optional chaining? - Any references to data could use optional chaining - data?.map(...map function...)
Make pull request to change order of logout? - Instead of clearing redux first we could navigate to login page or logged out page and then clear redux store.
A better way I haven't thought of?
Thoughts?

Why is immer complaining about computed properties?

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

Reset state of a Redux store causing incosistent selectors with React Router

I'm experiencing a number of issues trying to reset my state of Redux when changing routes.
On enter in routeA and click browser history to routeB, after firing ##router/LOCATION_CHANGE for routeB it performs the selectors of wrong routeA and causes various undefined state errors.
It should not run the routeA selector, as i'm getting into routeB and I believe this is the problem but I have no idea how to solve it.
In some even happened to be showing duplicate keys in the state, sometimes by the way I'm trying to reset the state wrong.
My scenery
In onEnter of the React Router i execute replaceReducers, see:
My routes onEnter callback:
function onEnter() {
replaceReducers({ book, dragAndDrop })
}
replaceReducers:
function replaceReducers(reducers) {
const appReducers = combineReducers({ ...reducers, ...defaultReducers })
store.replaceReducer((state, action) => {
if (action.type === 'RESET_STORE') {
const { routing, application } = state
state = { routing, application }
}
return appReducers(state, action)
})
store.dispatch({ type: 'RESET_STORE' })
apollo.setStore(store)
}
Steps I followed and what happened the error:
Enter routeA;
Go to routeB;
Click in browser history to routeA;
routeB is called incorrectly;
Error in console:
Uncaught TypeError: Cannot read property 'get' of undefined in routeB-selector.js
getItems: book => book.get('items')
This routeB selector can not be called and something has run it automatically.
Help me, please...

Is it insecure to just validate with SimpleSchema, and not use allow/deny rules?

I am using SimpleSchema (the node-simpl-schema package) in an isomorphic way. Validation messages show up on the client as well as from meteor shell.
My question is whether or not this set up is actually secure, and if I need to also write allow/deny rules.
For example:
SimpleSchema.setDefaultMessages
messages:
en:
"missing_user": "cant create a message with no author"
MessagesSchema = new SimpleSchema({
content: {
type: String,
label: "Message",
max: 200,
},
author_id: {
type: String,
autoform:
defaultValue: ->
Meteor.userId()
custom: ->
if !Meteor.users.findOne(_id: #obj.author_id)
"missing_user"
},
room_id: {
type: String,
}
}, {tracker: Tracker})
In meteor shell I test it out and it works as intended.
> Messages.insert({content: "foo", author_id: "asd"})
/home/max/Desktop/project/meteor/two/.meteor/local/build/programs/server/packages/aldeed_collection2-core.js:501
throw error; // 440
^
Error: cant create a message with no author
Should I duplicate this validation logic in my allow/deny rules? Or can I let my allow function always return true, like I'm doing now?
I have some very simple rules that ensures the application is secure:
Do not use allow/deny rules - deny all client-side write requests.
If the client needs to write something in the database, they must do so through Meteor methods.
Ideally, the Meteor methods would call a function (which can be shared code, or server-specific code), and then check for the validity of the database modifier (using the Schema) would be done inside these functions.
Optionally, you can also create client-side methods, which would clean the object and carry out its own validation using the schema before calling the server-side method.

Resources