Are keys required for values in redux actions? - redux

The redux docs (under basics > actions) use this as their example for an action:
{
type: ADD_TODO,
text: 'Build my first Redux app'
}
yet they use this as their example for an action creator:
function addTodo(text) {
return {
type: ADD_TODO,
text
}
}
In the first example every value in the action object has an associated key (type and text) however, what is the difference between this and the second example in which the action only specifies the key for type and just includes the value of the parameter in the action? and when should each method be used?
Also, does this second method just mean that the variable name is used as a key? If so, can you do for multiple different variables in the same action creator?
EDIT: apologies for the ambiguity. Essentially what I am wondering is the difference between the two action objects, not about action vs action creator.
AKA this
function addTodo(text) {
return {
type: ADD_TODO,
text
}
}
versus
function addTodo(text) {
return {
type: ADD_TODO,
data: text
}
}

The 2nd example shows an example of using an action creator to generate the action in the first example.
Action creators carry the same benefits of using a function for generating an object in general, namely that you reduce redundancy and can easily modify the action type or payload in one place in the future.
For that reason it's common in Redux to see the pattern:
dispatch(addTodo('Do laundry'));
As opposed to:
dispatch({
type: ADD_TODO,
text: 'Do laundry',
});
Although they are identical in their result.
Edit after updated question:
The 2nd example uses es6 shorthand to define the text key. So {text} is identical to {text: text}.

Related

Vue3 reactivity and proxy objects behaving apparently random

I am having difficulties with Vue 3 reactivity and proxy objects. I am using Option API and I have a Vue Component that on button click execute following code
submitUser() {
let userCreated;
userCreated = {
id: this.user_tomodify.id,
firstName: this.firstName,
lastName: this.lastName,
email: this.email,
username: this.username,
};
this.$emit("onuserSubmitted", userCreated, this.usertype);
this.hide();
},
Now the code just create an object starting from the data of the component, firstName lastName etc are all v-model of input elements. The method called in the parent component emitting "onuserSubmitted" is the following
addUser(new_user, u_type) {
this.users[u_type].push(new_user);
MevoLogger.debug(this.users[u_type]);
}
Where this.users is an Object that associated to the field contained in u_type variable some arrays. For better undestanding this is the computed property:
users() {
switch (this.role) {
case "ROLE_SECRETARY":
return {
STUDENT: this.students,
TEACHER: this.teachers,
};
case "ROLE_TECHNICIAN":
return {
STUDENT: this.students,
TEACHER: this.teachers,
SECRETARY: this.secretaries,
};
case "ROLE_ADMIN":
return {
STUDENT: this.students,
TEACHER: this.teachers,
SECRETARY: this.secretaries,
TECHNICIAN: this.technicians,
ADMIN: this.admins,
};
And i have a list in the page rendered with v-for = user in users[type]
The problem I am having is that apparently without any reason, when I push new_user in the array sometimes Vue create a normal object and sometimes a Proxy object ( i checked this printing in the browser console the users[u_type] array so i'm sure that this is the problem, literally randomly i see sometimes added a Proxy while sometimes a normal {} object), in the first case reactivity is not triggered and so i don't see in the page the new item added, in the second case reactivity works and page is updated. How is that even possible? What can I do in order to make it always create Proxy Objects?
If users is a computed property - it is readonly by design so if you somehow change it, its value will be recalculated/rebuilt on the next re-render. Or it might not change at all. You should push to the underlying array(s) - not to the computed property. E.g. push to students, teachers, etc.
For example:
addUser(new_user, u_type)
{
{
STUDENT: this.students,
TEACHER: this.teachers,
SECRETARY: this.secretaries,
TECHNICIAN: this.technicians,
ADMIN: this.admins,
}[u_type].push(new_user);
}

Updating normalised data without causing more re-renders outside of the state slice that has been updated

I have some normalised data (items) within my redux store:
{
items: {
index: ['a','b'],
dict: {
a: {
title: "red",
},
b: {
title: "car",
}
}
},
...
}
So, if I want to update anything within an item object, the reducer looks like this:
...
const itemsReducer = (state = initialState.items, action) => {
switch (action.type) {
case itemsActions.types.UPDATE_ITEM: {
return {
...state,
[action.payload.itemId]: {
title: action.payload.title,
}
}
}
default: return state;
}
};
But this technique creates a new object for items, which can cause unnecessary components to re-render, when really it should only cause components that subscribe to state changes of the individual object to re-render.
Is there any way to get around this?
That is how immutable updates are required to work - you must create copies of every level of nesting that needs to be updated.
In general, components should extract the smallest amount of data that they need from the store, to help minimize the chance of unnecessary re-renders. For example, most of the time a component probably shouldn't be reading the entire state.items slice.
FWIW, it looks like you're hand-writing your reducer logic. You should be using our official Redux Toolkit package to write your Redux logic in general. RTK also specifically has a createEntityAdapter API that will do most typical normalized state updates for you, so you don't have to write reducer logic by hand.
I'll also note that the recently released Reselect 4.1 version has new options you can use for customizing memoized selectors as well.

Flow typing redux actions - performance issue

I followed flow docs and typed redux action creators using union (https://flow.org/en/docs/react/redux/#toc-typing-redux-actions)
so I have a file with ALL the actions gathered into 1 union like in example:
type Action =
| { type: "FOO", foo: number }
| { type: "BAR", bar: boolean }
| { type: "BAZ", baz: string };
Action type is imported in my reducers and used as in exxample from docs:
function reducer(state: State, action: Action): State {
switch (action.type) {
case "FOO": return { ...state, value: action.foo };
case "BAR": return { ...state, value: action.bar };
default:
(action: empty);
return state;
}
}
The problem:
As I mentioned I gathered ALL the actions in one file - currently ~600 actions in one union. I noticed that lately flow server takes crazy time to start (100+ seconds), rechecking flow is also a pain if change is related to reducer. According to flow logs, files that contain reducers are marked as "Slow MERGE" - 15 to 45s.
After experimenting, I noticed that changing my Action type to any cuts the time from 100s to 9s.
The question:
can this be related to huge Action union?
should I split it into a few smaller types which will contain only actions to import in particular reducer or this is a wrong way to fix my issue?
It's probably more likely that this one action type is used across your entire app. Any time you make a change to it, Flow needs to recheck a very large number of files. One way to help mitigate this is to ensure all your union actions are in files of their own that don't import other files. Flow can get slow if it has "cycles". One type imports another time which then imports the first time. This can happen if, for example, you define your reducer actions in the reducers themselves. This causes a cycle. Instead, move your action types to their own file.
Additionally, you can use flow cycle to output a dot file you can then visualize this file in something like Gephi https://gephi.org/ to detect cycles.

Flow doesn't infer type correctly

I define an multiple subtypes in the Action creator in redux:
Action creator:
export type Action = { type: "SELECT", index: number } | { type: "OTHER" };
Reducer:
module.exports = (state: string = "", action: Action): string => {
switch (action.type) {
case "SELECT":
return action.index;
default:
return state;
}
};
but if I define SELECT in a constant const select = "SELECT" and implement it in the code above I obtain an error message:
property `index`. Property not found in object type
Note: Taking flow-pattern as it is F8 app:
https://github.com/fbsamples/f8app/blob/master/js/actions/types.js
How should implement it by avoiding having "SELECT" keyword both in the action and in the reducer?
You would normally have another constant with the action type, which would be used for both your action and reducer.
const SELECT = 'SELECT';
or even better (to avoid any conflicts):
const SELECT = 'redux/<module>/SELECT';
In the same action file or in another (that's totally up to you). Then just export like export const SELECT, and import like import { SELECT } from './constants'.
You should also take a look at redux-ducks, might be of your interest.
EDIT:
They use bitwise OR to group all possible different action cases. That helps testing whether the code is syntactically correct with flow (by setting their dispatched actions to match type Action). See their full explanation here.
That does not take away the fact that they have to dispatch the action with their desired action type though. login

Understanding how Router.go() works in Meteor

I'm just learning Meteor now from the great Discover Meteor book and I'm struggling to understand something about how Router.go() functions which I thought might be something that other beginners could use an answer to.
Context: The code below does what it's supposed to - it picks up the url and title values from the postSubmit form (code not included for the form) and uses that to create a new post. Then it uses Router.go() to take the user to the postPage template at a posts/:_id url, displaying the information for the newly created post. This code all works.
My question is: I would expect that when you call Router.go(), as well as passing in the 'postPage' template, what you would need to pass in as the second parameter is the post id element in the form {_id: post._id} (which also works, I've tried it) as that is what the route requires. So why am I passing in the post var (which includes the url and title) rather than the ID?
Here's my code:
//post_submit.js
Template.postSubmit.events({ 'submit form': function(e) {
e.preventDefault();
var post = {
url: $(e.target).find('[name=url]').val(),
title: $(e.target).find('[name=title]').val()
};
post._id = Posts.insert(post);
//THE 'post' PARAMETER HERE INSTEAD OF '{_id: post._id}' IS WHAT I'M QUESTIONING
Router.go('postPage', post);
}
});
And the code for the router:
//Route for the postPage template
Router.route('/posts/:_id',
{name: 'postPage',
data: function(){ return Posts.findOne(this.params._id); }
});
Good question - I also found this confusing when I first saw it. Here's a sketch of what's going on:
The router parses /posts/:_id and figures out that it should be passed a context object which contains an _id field.
You call Router.go with a context object that contains an _id field.
The router takes your context object and copies the value of _id into this.params.
Because the router understands which fields are required (and ignores the rest), it doesn't actually matter if you pass in {_id: 'abc123'} or {_id: 'abc123', title: 'hello', author: 'bob'}. The fact that the latter works is simply a convenience so you don't have to extract the _id into a separate object.

Resources