How React Redux component can subscribe on state change - javascript

I have some "ChangeMainItem" Action (in my case it is dispatched by external system or possibly one of components). This action (e.g. {type:Change_Main_Item, itemId:5}) updates only one property of state in reducer (e.g. mainItemId).
My Component A and B needs to react on this state change: show loading indicator, fetch additional data and show results. Sequential actions can be done via some async action library - but where should I place dispatching async action? Obviously, i can't dispatch async action in reducer of Component A and B, neither i want to change original Action to async so it can make any necessary requests for my components.
What is the right way to achieve this?

I suggest using sagas to listen to your defined actions and manage your async calls/actions from there. Redux-saga is awesome.
import { put, takeEvery } from 'redux-saga/effects'
import {Change_Main_Item, ANOTHER_ACTION, THIRD_ACTION} from '../actions'
function* doSomething() {
yield put({type: "ANOTHER_ACTION", payload: data});
}
function* doAnotherThing() {
yield put({type: "THIRD_ACTION", payload: data});
}
function* mySaga() {
yield takeEvery("Change_Main_Item", [doSomething, doAnotherThing]);
}
Please see https://github.com/redux-saga/redux-saga

Well, you have multiple approaches to such a question.
You can use a redux-thunk so you can dispatch multiple actions and have your state react to all such dispatches. Thunk middleware is useful when you need to perform async actions.
example:
function changeMainItem(id) {
return (dispatch, getState) {
dispatch(requestChangeMainItem); //This tells the state that it's loading some ajax; you presumably want to add some boolean, indicating a loading icon.
return makeSomeRequest(id)
.then(data => {
dispatch(changeMainItem(data.id)) //presumably, this is where you update your item
// perhaps dispatch another function to do more stuff.
})
}
}
You then will need to figure out which components need to be subscribed/connected to certain properties in your state.
Read about async action, redux-thunks, and this article on how to connect your components to your state

Related

Any benefits to use Redux-Saga instead of writing async func in react components?

react version is 16.13.1.
I wondering if there are some benefits to use redux-saga for async methods.
const component = () => {
const asyncFunc = async() => { // <- this part should be moved out to redux-saga?
await callMethod();
}
return (
<div onClick={asyncFunc}>button</div>
)
}
I have no idea that asyncFunc should be called in redux-saga or in react component.
Which is better or more beneficial?
In my opinion, I prefer to call async method in components.
In simpler words redux-saga is beneficial in the case where we need to achieve some async operation during a redux action.
Now what you are doing is handling the side effect in the component so the action you'll dispatch will only update the store.
It is a very simple use case where you handled it in the component, consider a scenario where you need this same functionality from 2 different components.. you will have to copy the logic in 2 different components.
The testing will become difficult.
Now consider the same scenario again but the problem is since you can trigger the API calls from 2 components, let's consider a scenario that the user triggered the API call from both the components simultaneously, it is wastage of resource to handle both the API calls if the first API call is still pending.
for all this scenario redux-saga provide methods like takeLatest, takeEvery etc.
the benefit of using almost each and everything of redux is to organize the code and keep all the states in store, if you use async function in one component and by chance you want to use that async function again for some other component then you have to write the entire code again and again , in case of redux-saga you will write async one time and can call that action anywhere in your whole react project, for now you might be creating 5-10 components but it might be possible that in future you will create 5000 components at that time redux and its middlewares come into play .
Redux-saga is a middleware to act on an action before it reaches the reducer.
Basically, all side effects will be handled in the middleware and gives you more control over the effects.
This way, it has clear separation of concerns that the middleware is going to handle the side effects and not the component. A saga is not dependent on the lifetime of a component.
In a saga, fetch will look something like this:
function* fetchItems(action) {
try {
const result = yield call(axios.post, ...);
yield put ({ type: 'FETCH_SUCCESS', payload: { result } });
} catch (e) {
yield put ({ type: 'FETCH_FAILED', error: { msg: e } });
}
}
yield takeEvery(FETCH_ITEMS, fetchItems);
However for complex systems with background processing, you can implement different patterns that uses fork() and cancel()
function* doSync() {}
function* main() {
while ( yield take(START_SYNC) ) {
const task = yield fork(doSync) // returns a task
yield take(STOP_SYNC)
yield cancel(task) // cancel a task if syncing is stopped
}
}
Thus, all that said, redux-saga's power lies when your system is getting more complex and event-driven.

How to use Redux Sagas to load single object?

I've been working on a react project, using redux and sagas.
So far I was able to create a structure where my component would dispatch an action requesting a list, then sagas takes this action and calls a function that would make the request to the backend. I then store this list on redux, and my component reads the list from redux.
Now I need to load a single object from backend. I thought about not using sagas for that request, but I guess it makes sense to make this request using sagas and storing this single object on redux.
I can't figure how to make the rootSaga function accept to take my function with 'id' as parameter.
To ilustrate the scenario, here is some code:
this is the root saga function, it would make the API request and call the action to store on redux
export function* loadEvent({ id: number }) {
try {
console.log('load single event');
yield put(loadEventSuccess(event));
} catch (err) {
yield put(loadFailure());
}
}
This is the rootSaga function that is supposed to get the action I dispatched and trigger my saga function. The problems is that it does not accept me to call the function with a parameter. Even if I could, I don't know how to pass a parameter to it.
export default function* rootSaga() {
return yield all([
takeLatest(EventsTypes.LOAD_EVENT_REQUEST, loadEvent),
]);
}
Of course, before all that there is an action I dispatched from my function component.
Another thing that has been confusing me is if the approach I've been using is ok. I could make this request from my function component and store the data in its state. By doing that I wouldn't involve redux nor sagas. Would that make sense?
Your approach is completely right as you should try to make every api call throw your sagas, but you just made a small mistake
function* loadEvent({ payload }) {
const { id } = payload;
try {
const response = yield call(api.get, `localhost:4000/api/${id}`);
yield put(loadEventSuccess(event));
} catch (err) {
yield put(loadFailure());
}
};
You also don't need to export the generator functions, and in my example I'm simulating an api get call using axios. And don't forget to import { call } from "redux-saga/effects";

redux-saga injected twice

I have a redux-saga which is called once, but it is executing twice.
This is the action that starts the saga:
export function createRequest (data) {
return {
type: CREATE_REQUEST,
payload: {data}
};
}
and my sagas.js file looks this way:
export function* create (x) {
try {
const response = yield call(request, URL_TO_API, Object.assign({}, buildBaseHeaders('en'), {
method: 'post',
body: JSON.stringify(x.payload.data)
}));
yield put(createSuccess(response));
} catch (error) {
yield put(createFailure(error));
}
}
... my other sagas
export default function* defaultSaga () {
yield takeLatest(CREATE_REQUEST, create);
... my other calls
}
The way I'm injecting the sagas into my React component is this:
const withConnect = connect(mapStateToProps, mapDispatchToProps);
const withReducer = injectReducer({key: 'myComponent', reducer});
const withSaga = injectSaga({key: 'myComponent', saga});
export default compose(withReducer, withSaga, withConnect) MyComponent;
But the saga is injected twice. So, what am I missing here? How can I inject the saga only once no matter on how many times MyComponent gets rendered?
But the saga is injected twice. So, what am I missing here?
Solution is dependent on how redux-sagas-injector npm library works. In general case, asynchronous loading and applying for sagas is difficult thing, because saga is consists of "live" process manager, which can not be disposed on some function call or object deletion.
It implies from saga's ability to launch custom tick callback domains (Promises, AJAX/XHR, setImmediate, etc), which can not be disposed from custom external code (That's also reason, why HMR does not work with sagas in partial mode and should reload whole page).
So, if you perform saga injection on router switching, check two things: that old saga has implicit action to dispose from outer side, like special inner technical dispose action, and that there is not misconfiguration in router side - maybe same page had been launched, for example, twice.

React + Redux, How to render not after each dispatch, but after several?

I am trying to make multiple changes to the store, but not render till all changes are done. I wanted to do this with redux-thunk.
Here is my action creator:
function addProp(name, value) {
return { type:'ADD_PROP', name, value }
}
function multiGeoChanges(...changes) {
// my goal here is to make multiple changes to geo, and make sure that react doesnt update the render till the end
return async function(dispatch, getState) {
for (let change of changes) {
dispatch(change);
await promiseTimeout(2000);
}
}
}
I dispatch my async action creator like this:
store.dispatch(multiGeoChanges(addProp(1, "val1"), addProp(2, "val2"), addProp(3, "val3")));
However this is causing react to render after each dispatch. I am new to redux-thunk, I never used async middleware, but I thought it could help me here.
#Kokovin Vladislav's answer is correct. To add some additional context:
Redux will notify all subscribers after every dispatch. To cut down on re-renders, either dispatch fewer times, or use one of several approaches for "batching" dispatches and notifications. For more info, see the Redux FAQ on update events: http://redux.js.org/docs/faq/Performance.html#performance-update-events .
I also recently wrote a couple of blog posts that relate to this topic. Idiomatic Redux: Thoughts on Thunks, Sagas, Abstraction, and Reusability discusses the pros and cons of using thunks, and summarizes several ways to handle batching of dispatches. Practical Redux Part 6: Connected Lists, Forms, and Performance describes several key aspects to be aware of regarding Redux performance.
Finally, there's several other libraries that can help with batching up store change notifications. See the Store#Store Change Subscriptions section of my Redux addons catalog for a list of relevant addons. In particular, you might be interested in https://github.com/manaflair/redux-batch , which will allow you to dispatch an array of actions with only a single notification event.
There are ways to achieve the goal:
Classic way:
usually:
Actions describe the fact that something happened, but don't specify how the application's state changes in response. This is the job of reducers.
That also means that actions are not setters.
Thus, you could describe what has happened and accumulate changes, and dispatch one action
something like:
const multipleAddProp = (changedProps) =>({
type:'MULTIPLE_ADD_PROP', changedProps
});
And then react on action in reducer:
const geo=(state,action)=>{
...
switch (action.type){
case 'MULTIPLE_ADD_PROP':
// apply new props
...
}
}
Another way When rerendering is critical :
then you can consider to limit components, which could be rerendered on state change.
For example you can use shouldComponentUpdate to check whether component
should be rendered or not.
Also you could use reselect, in order to not rerender connected components
after calculating derived data...
Non standard way:
redux-batched-action
It works something like transaction.
In this example, the subscribers would be notified once:
import { batchActions } from 'redux-batched-actions';
const multiGeoChanges=(...arrayOfActions)=> dispatch => {
dispatch( batchActions(arrayOfActions) );
}
In react-redux 7.0.1+ batching is now built-in. Release notes of 7.0.1:
https://github.com/reduxjs/react-redux/releases/tag/v7.0.1
Batched Updates
React has an unstable_batchedUpdates API that it uses to group
together multiple updates from the same event loop tick. The React
team encouraged us to use this, and we've updated our internal Redux
subscription handling to leverage this API. This should also help
improve performance, by cutting down on the number of distinct renders
caused by a Redux store update.
function myThunk() {
return (dispatch, getState) => {
// should only result in one combined re-render, not two
batch(() => {
dispatch(increment());
dispatch(increment());
})
}
}
By design when the state, which is held by the store, changes the view should render.
You can avoid this by updating the state once.
If you are using promises you can use Promise.all to wait for all the promises to resolve and then dispatch a new action to the store with the calculated result. https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Promise/all
Something like this:
Promise.all([p1, p2, p3, p4, p5]).then(changes => {
dispatch(changes)
}, err => {
// deal with error
});
Of course you'll need an action that will deal with many props, something like addManyProps this should update the state once, resulting in one render.
redux-batched-actions
Batching action creator and associated higher order reducer for redux that enables batching subscriber notifications for an array of actions.
Coming to this a bit late, but I think this is a much nicer solution, which enables you to add meta.batch to actions you would like to batch together into a single react update. As a bonus this approach works with asynchronous actions.
import raf from 'raf'
import { batchedSubscribe } from 'redux-batched-subscribe'
let notify = null
let rafId = null
const shouldBatch = action => action?.meta?.batch
export const batchedSubscribeEnhancer = batchedSubscribe(freshNotify => (notify = freshNotify))
export const batchedSubscribeMiddleware = () => next => action => {
const resolved = next(action)
if (notify && rafId === null && !shouldBatch(action)) {
notify()
} else if (!rafId) {
rafId = raf(() => {
rafId = null
notify()
})
}
return resolved
}
Then connect up to your store
mport { applyMiddleware, compose, createStore } from 'redux'
import { batchedSubscribeMiddleware, batchedSubscribeEnhancer } from './batching'
const store = createStore(
reducer,
intialState,
compose(
batchedSubscribeEnhancer,
applyMiddleware(batchedSubscribeMiddleware)
)
)

Redux middleware change state before next()

I would like to modify the state before next() is called so every reducer applied after the middleware gets the new state. Is it possible? How?
The only idea that comes to my mind is very hacky and would be something like this:
export const myMiddleware = (store) => (next) => (action) => {
const oldReducer = ????
store.replaceReducer(myReducer);
store.dispatch(action);
const newState = store.getState();
store.replaceReducer(oldReducer);
return next(newState);
}
As I haven't seen any method to get the current reducer, it should be given to the middleware in any manner:
export const myMiddleware = (oldReducer) => (store) => (next) => (action) => {
...
}
const store = createStore(originalReducer, applyMiddleware(myMiddleware(originalReducer)));
Which seems even more hacky!
The main purpose is to build a package that maps an action object (action.payload) and a path (action.meta) in store state. In this scenario, the reducer is distributed in an npm package, so it should be "chained" somehow. So right now the reducer is detecting if there is a path and an object inside payload, and tries to reduce the new state from it.
The worst solution is to instruct the user to call the reducer from their own reducer, just before any other action inside the reducer. This is not a solid pattern. So at first, I was thinking in an as much agnostic as possible middleware that automatically does the work. And that's why I'm trying to modify state from middleware if possible.
You probably don't want to be calling individual reducers from within middleware. It sounds like you're condensing what should be multiple sequential actions into a single action, which is what's causing you problems. If you use something like redux-saga to manage chains of actions, you can likely accomplish what you're looking for pretty easily.
Here's a basic example of managing a sequence of actions with redux-saga:
import { takeEvery } from 'redux-saga'
import { put } from 'redux-saga/effects'
export function * watchForSomeAction () {
// Every time SOME_ACTION is dispatched, doSomethingElse() will be called
// with the action as its argument
yield * takeEvery('SOME_ACTION', doSomethingElse)
}
export function * doSomethingElse (action) {
// put() is redux-saga's way of dispatching actions
yield put({ type: 'ANOTHER_ACTION', payload: action.payload })
}
This example simply watches for SOME_ACTION, and when it happens, it dispatches ANOTHER_ACTION. With something like this, you can ensure that ANOTHER_ACTION's reducers are dealing with the new state resulting from SOME_ACTION's reducers.

Categories

Resources