I am new to react/redux. I am trying to figure out how all the pieces in redux interact. The one thing giving me trouble is understanding the relation between actions and reducers,store.
It's pretty simple when you think about it:
Store - Is what holds all the data your application uses.
Reducer - is what manipulates that data when it recieves an action.
Action - is what tells reducer to manipulate the store data, it carries the name and (not required) some data.
Reducer is usually in a format of a switch statement, that switches between all possible Actions (Cases) and then manipulates the Store data based on action. When a reducer data changes within the redux, the properties in your components are changed and then the re-render ocurrs.
Store -> A Globalized state
Action -> What you wanna do, eg: event click
Reducer -> Describes how your action transfers state into the next state.
It checks which action took place and based on the action it updates the store.
Dispatch -> Way how we execute the action. eg: Dispatch the action to the reducer. Then reducer will check what to do and the store gets updated.
Store
An Object that holds the applications state data
Reducer
A function that returns some state data. Is triggered by an action type
Action
An object that tells the reducer how to change the state. It must contain a type property. It can optionally contain a payload property
The actions, reducers and stores are the three building blocks of redux.
Actions: Actions are the only source of information for the store. Actions have a type field that tells what kind of action to perform and all other fields contain information or data. And there is one other term called Action Creators, these are the function that creates actions. So actions are the information (Objects) and action creator are functions that return these actions.
Reducers: As we already know, actions only tell what to do, but they don’t tell how to do, so reducers are the pure functions that take the current state and action and return the new state and tell the store how to do.
Store: The store is the object which holds the state of the application.
I found this link to be particularly helpful - https://www.geeksforgeeks.org/introduction-to-redux-action-reducers-and-store/
Imagine a situation where you want your class based components to share data among each other. They may even bring changes to the data. One may provide data to others in the form of props. But it very difficult to keep track of the name of the props and the structure of data.
The Store actually simplifies this stuff. You set up your application architecture in such a way that the components will get their data from the supply what is known as the Store. Actually, the mechanism is so smart the component will re-render itself when the data changes since the components are all ears.
And Actions are nothing but the carriers of data from your application to the store.
And it is very difficult to articulate the concept of reducers. You may imagine a real store where one puts different stuff for future use. The store is of no use when the stuff is put haphazardly. One may spend hours inside but may not find anything. The Reducers in simple terms manage the way data is kept in store provided by the actions.
according to redux documents:
store: The whole global state of your app is stored in an object called store.
dispatcher: To change something in the state, you need to dispatch an action. (and that is what dispatcher does)
action: An action is a plain JavaScript object that describes the kind of change to make (as dictated by action.type) to the store and the relevant payload required for that change.
reducer: to tie state and actions together, we write a function
called a reducer. it’s just a (pure) function that takes state and action as arguments and returns the next state of the app.
for a deeper understanding look at the diagram in this link.
Actions: Actions are a plain JavaScript object that contains information. Actions are the only source of information for the store. Actions have a type field that tells what kind of action to perform and all other fields contain information or data.
Example :
function addTask(task) {
return {
type: 'ADD_TODO',
task: task
}
}
Reducers: As we already know, actions only tell what to do, but they don’t tell how to do, so reducers are the pure functions that take the current state and action and return the new state and tell the store how to do.
Example:
function task(tasks = [], action) {
if (action.type === 'ADD_TODO') {
return [...tasks, action.task];
} else if (action.type === 'REMOVE_TODO') {
return tasks.filter(task => task !== action.task);
}
return tasks;
}
Store: The store is the object which holds the state of the application.
ref: https://www.geeksforgeeks.org/introduction-to-redux-action-reducers-and-store/
Related
Mobx and Redux will normally not persist any data. They will maintain a temporary global state while the app is running.
I know there are redux-persist and mobx-persist packages within both communities. But unfortunately these persisting solutions do not seem good at all. They only stringify or serialize a global state tree and persist it using some sort of key-value storage. Right?
The problem:
When such an app is open again, the stringified store will be parsed and structured back to its original data structure (JSON, for instance) and then fully loaded into the RAM memory. Am I right?
If yes, this is a problem. It is not good to always have a full "database" aka "global state" loaded in-memory. It will probably never be faster to filter data within a long array in my global state... compared to querying a table on SQLite, right?
I have been looking for some repository-like solution for persisting global state for either redux or mobx. I am yarning for some solution for persisting and querying data on some well-known mobile database like SQLite or others.
Any answers will be very much appreciated.
Indeed you can use repository pattern.
On your repository, you may have a save method.
save(group: GroupLocalStorageModel): Promise<boolean> {
let created;
this._localStorage.write(() => {
created = this._localStorage.create<GroupLocalStorageModel>("Group", group);
});
return Promise.resolve(true);
}
This method will literally save your entity to some local storage you set. In the example above, we are saving a group object to a Group collection which are like tables. We are using realm which is no-sql.
Once you have your repository, if you are using either redux or mobx, you will probably call your save method on your action. Both redux and mobx work with actions, right?
export const GroupStoreModel = types
.model("GroupStore")
.props({
groups: types.optional(types.array(GroupModel), []),
})
.extend(withEnvironment)
.actions((self) => {
return ({
_addGroupToStore(group: GroupLocalStorageModel) {
self.groups.push(group)
},
_deleteAllFromStore() {
self.groups.clear()
},
_addGroupsToStoreBatch: (groups: GroupLocalStorageModel[]) => {
self.groups.concat(groups);
},
})
})
/* Async actions */
.actions((self) => {
let groupRepository = self.environment.groupRepository;
return ({
addGroup(group: GroupLocalStorageModel) {
groupRepository.save(group).then(result => self._addGroupToStore(group))
},
getAllGroupsPaginated(page: number) {
groupRepository.getAllPaginated(page).then(groups => self._addGroupsToStoreBatch(groups));
},
deleteAll() {
groupRepository.deleteAll();
self._deleteAllFromStore();
}
})
})
In this example, we are using mobx-state-tree. And this addGroup action will update firstly our database, and then update also the global state.
We still want to use our global state so our views will be re-rendered automatically according to either connect for redux or observable for mobx.
See more informations here on the repository:
https://github.com/Hadajung/poc-react-native-database-example
AFAIK, there are two options for using sqlite with redux persist.
redux-persist-sqlite-storage: By maintainer's own word
By default redux-persist uses AsyncStorage as storage engine in react-native. This is a drop-in replacemet of AsyncStorage.
The library is inspired by react-native-sqlite-storage.
Please, remember, to use this, you need to install an additional package installed react-native-sqlite-storage
redux-persist-sqlite: By maintainer's own word
A redux-persist storage adapter that writes to sqlite.
This is adapted from https://github.com/prsn/redux-persist-sqlite-storage, but uses Node.js sqlite3 rather than react-native.
Great for Electron apps that are backed by Redux.
UPDATE: react-native-mmkv : This is developed by WeChat. As it says in its about section
An extremely fast key/value storage library for React Native. ~30x faster than AsyncStorage!
I'm not really sure what you need but if I understood you correctly you need to persist large amounts of data, and also to load that same data, but only in batches.
I believe that this kind of problem can be solved with a repository pattern and SOLID design principles.
You will need:
store class (mobx store) that holds your business logic.
repository class which is responsible for retrieving and persisting data.
The store gets the repository injected into it via the constructor.
Then when you call the initialize method on your store, it talks to the repository and retrieves the initial data. Now, initial data can be only a subset of all the data that is persisted. And you can implement some kind of paging on the store and repository, to retrieve data in batches as needed. Later you can call other methods to load and save additional data as needed.
pseudo code:
class Repository(){
initialize()// load the first batch
load(next 10 models)
save(data)
}
class Store{
constructor(repository)
initialize(){
repository.initialize()
}
load(){
repository.load()
}
save(){
repository.save()
}
}
Now your application data shouldn't be one giant object, rather it should consist of multiple stores, where each store is responsible for a part of the data. For example, you would have one store and repository for handling todos and another pair that handles address book contacts etc.
Addendum:
The reason the repository is injected into the store is so you could easily swap it for some other implementation (the store doesn't care how the data is persisted and retrieved) and also, unit testing is very easy.
You could also have a root store that would hold all other stores, so in essence you have your complete state in one place. So if you call serialize on the root store, it serializes all stores and returns one big object.
I think the best solution would be bloc hydrated or cubit hydrated package from flutter_bloc.
https://pub.dev/packages/hydrated_bloc
In background it uses Hive DB, very performant DB, and only keys are stored in memmory so it should not add huge bloat to the app like SQLite.
If you could make all you APP logic in blocks/cubits, then extra DB calls would be irelevant.
I understand that it is generally bad practice to manipulate the Vuex state directly from outside of the store (e.g from a component); but I'm strugging to understand what is best practice to read/change the state from within an action.
I understand that actions inherently have a context object as an argument, from which you can get the state, as well as getters, commit, and dispatch as properties. But I'm confused as to what is the best practice for using these. For example:
testAction: ({commit, getters, state}) => {
if(state.foo === 'bar'){ // is this bad practice?
// ...do a thing
}
if(getters.getFoo === 'bar'){ // should I always read the state with a getter like this instead?
// ...do a thing
}
commit('setFoo', 'foobar'); // do I always need to set the state via commit like this
state.foo = 'foobar' // or is it fine to just do this?
}
Please see the comments. What is the 'correct' way to manipulate state within an action? Do I always need to use commit and getters? And if so, why does the context object even expose state; when would you ever use this in an action? Thanks.
By looking at the documentation mutations and actions are defined as such:
Actions
Actions are similar to mutations, the differences being that:
Instead of mutating the state, actions commit mutations.
Actions can contain arbitrary asynchronous operations.
Mutations
The only way to actually change state in a Vuex store is by committing a mutation. Vuex mutations are very similar to events: each mutation has a string type and a handler. The handler function is where we perform actual state modifications, and it will receive the state as the first argument:
(...) One important rule to remember is that mutation handler functions must be synchronous
It is imperative that state modifications are done using mutations, else the updates wont be dispatched properly through the Vue app and the components won't update accordingly. But you are not forced to use actions to commit mutations. You could think of actions as a composition of mutations that allow you to handle complex changes to your state and asynchronous methods. So, if your action is simple enough use a mutation, else use an action. Although, be aware that as mutations are synchronous, they may freeze your front-end until the requested action is over, if the action is a heavy one (nice explication of Akryum, vuejs core member here).
In a similar manner the getters are a way of "formatting" data retrieved from the store, as stated in the docs:
Sometimes we may need to compute derived state based on store state, for example filtering through a list of items and counting them.
Meaning that if you just need a simple key from the state you don't need to create a getter, you can simply retrieve the info you need from the state.
Taking a look to your example:
testAction: ({commit, getters, state}, testData) => {
if(state.foo === 'bar'){ // is this bad practice? ==> Good to go !
// ...do a thing
}
if(getters.getFoo === 'bar'){ // should I always read the state with a getter like this instead? ==> Good to go ! but it depends on the way you can retrieve data from the state (like if you need to know the number of items within a list)
// ...do a thing
}
commit('setFoo', 'testData'); // do I always need to set the state via commit like this ==> **YES** !
state.foo = 'foobar' // or is it fine to just do this? ==> never do this
}
Also for an easier integration of VueX in your components, the api exposes a set of functions to access the store properties and use them as computed properties or methods: mapActions, mapMutations, mapGetters and mapState
For example I have simple TODO app.
Task represented by Task component. As storage I use Vuex.
For each task I store structure like
{
id: 999,
label: 'My super task',
done: true
}
Questions is what I have to pass to the component as property - only id (and then get other data from store) or the whole data array?
Passing whole data will make the your Task component independent from store.
Passing only ids of tasks will make your Task component dependent on store for tasks data.
Since Task is representing a single card, I think making it dependent on the store will increase the calls to the store since n number of tasks will call store for data.
It is better to get the data from store once and then enumerating the data using v-for directive and passing whole task object to your Task component.
So you can pass it however you like. Keep in mind, if you're using Vuex, you don't need to pass anything as a prop since you have a central source of truth. Instead, you can call straight from the state or use a getter.
Inside your child component, you can refer to the store with $store in the template and this.$store in the script.
For example:
const myTasks = this.$store.state.tasks (if tasks is an array or collection of some sort)
const myTask = this.$store.state.tasks[0]
Then you have myTask.id, myTask.label, etc.
If you need a more specific example, please post more code and let me know.
I have a mobile app made in React Native, and I've just run into a best practice dilemma i've encountered many times while using Redux/Redux Saga. I would love if i could get someone else's thoughts on this.
For a new piece of functionality i'm implementing, i need to be able to tell how many times the app has been launched. This involves asynchronously retrieving how many times the app was previously launched from the device storage. If there's a new launch happening, i also need to add +1 to the number and store that in the device storage.
This is how i currently do it:
Dispatch appLaunched() action when app launches.
Redux Saga takes event.
Inside Saga: Retrieve how many times app was previously launched (appLaunchCount) from device storage (wait for async to finish).
Add +1 to previous appLaunchCount.
Store new appLaunchCount in device storage (wait for async to finish).
Dispatch put() with new appLaunchCount to reducer.
Update state with new appLaunchCount inside reducer.
My problem with this method is step 6. Technically any part of my app could dispatch a new app launch count to my reducer, with any integer, and the reducer would update the state just the same even though it didn't come from the saga.
My question is this: How can i protect my Reducers/Sagas/Actions so that only my saga can dispatch the action with the current appLaunchCount ?
P.S The only solution i can think of is writing my saga and reducer in the same file, and use private actions that only the saga and reducer can access. I would really hate to have to keep all that code together though.
Private actions aren't really a thing. The store is, by design, a global object. And since actions are just objects with a type property, anyone who can construct an action object of the right type can in principle dispatch an action and kick off your reducer.
What you could do is make the action have a type that makes it obvious that it's meant to be private. For example, maybe the action looks like:
{
type: '__PRIVATE_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED__'
// You could tone it down a bit from this :)
}
That of course doesn't make it actually private, but at least if someone wants to use it, it's impossible for them to not realize your intent.
If you wanted to make it more secure, perhaps you could use a symbol as the type, and therefore only anyone with access to the symbol could construct the right action. For example:
const appLaunchCount = Symbol('appLaunchCount');
// action would look like:
{
type: appLaunchCount
}
But then the issue is making sure that symbol stays hidden, and can be accessed only by those who you want to access it. Similar to one of the things you mentioned, if you have the saga/reducer in the same file, then you could make sure that other files couldn't access this symbol; but once you start exporting it it becomes harder to control.
Is possible to get which component call some action Vuex ?
I return a promise from vuex actions and take some decisions in component as set errors messages on respective fields but I would like to set on component.$validator.errors asap I receive http response (in action method)
Is that possible and a good approach ?
In Vuex when you make a call to a mutation or action you can send an object alongside your dispatch call, if you wanted to track the component which called an action you could also send that as part of the object. Better yet you could have all objects sent via Vuex extend a specific class if your using typescript. Though note that finding which component called an action or mutation is not native behavior for Vuex.
Consider the following:
try{
let out = await this.$store.dispatch('someActionHandler', {referingComponent: this.$options.name, someParam:[1,2,3]})
} catch (e){
// Lets deal with the problem
}
In here we are sending the name of the component as a parameter, so it can be checked inside our action handler or alternatively you could just pass this straight to a mutation, though I think the former is a more likely case if you plan to build logic into this.
As for if this is a good approach, the answer to that is fairly subjective, I personally don't see any problems with the above approach. Though I would think it was an anti pattern if the majority of components are never checked or the added data ends up becoming meaningless fluff passed alongside every call.