Dispatching new action after global state changes in react redux - reactjs

Lets say i have a form where user is about to click on combination of buttons.
Each button triggers an action of type T and reducer R then updates its state and new combination is rendered on a website.
Now comes the tricky part:
I have my business logic implemented in reducer which applies new state which is about to be rendered. What i need now is when that state accepts a condition, i want to dispatch new action (api request).
What is the right approach to accomplish this kind of problem?
Set a flag into state, and call new action in component after?
Somehow dispatch a function in reducer?
...?

Redux Thunk allows you to dispatch multiple actions and dispatch asynchronous actions inside your action creators. For your scenario, you can do something like this:
function myAction() {
return (dispatch, getState) => {
...
dispatch(firstAction);
const state = getState();
// Check your state conditions from first action here.
dispatch(secondAction);
...
}
}

In this case you could use library redux-saga.
Using redux-saga, you'll be able to create a saga that you call from a component like actions. In the saga you will be able to call several redux-actions. If your state will be valid from the saga, you can call the API request.
A popular alternative to redux-saga is also redux-thunk.

Related

what happens after dispatch function and reducer function in react-redux?

suppose if i dispatch an action using dispatch() , i know that reducer() is called which has an action object and current state as parameters . i want to know what calls that reducer function ? which functions are called before reducer function and after dispatch function ? after reducer function returns the new state ,which functions are called after that ? where does this new state goes ? does usestate() and useselector() also returns something after reducer function returns new state ?
i want to know what calls that reducer function ?
which functions are called before reducer function and after dispatch function
dispatch() indeed 'call' every reducers. It uses an event system and all reducers are listening to this event (this is all behind the scene).
Still, you can write a piece of code that will be inserted between the dispatch call and the reducers catching actions.
It's called a middleware.
A middleware can intercept any action triggered by a dispatch, also it has access to the store.
At the end of your middleware your just use a callback to tell the flow to continue.
You need to pass your action to it so your reducers can finally be called and receive the action.
Here is an example of middleware that log any actions that are sent by any dispatch()
const logger = store => next => action => {
console.log('dispatching', action)
let result = next(action)
console.log('next state', store.getState())
return result
}
To make it work you need to pass your middleware to your redux configuration, so they can be called.
after reducer function returns the new state ,which functions are
called after that, where does this new state goes ?
If you look at your redux configuration you'll see somewhere that you combine all of your reducers ( often called root reducer).
const combinedReducers = combineReducers({ reducerA, reducerB })
const store = createStore(combinedReducers )
Redux use this combination of reducers to fill the store, so anytime a reducer return it's result, it can be save in 'the store'
does usestate() and useselector() also returns something after reducer function returns new state ?
useSelector() is a hooks that has the ability to read in the store. (Store that contains the fresh result of your reducers and is updated every time there is a modification in the store)
useState() is not related to redux. It's related to React. With useState you can read and write in the local state of a component.
It returns you a piece a your state and a setter for this piece of state.
const {myPieceOfState, setMyPieceOfState} = useState({ something :'a default value for myPieceOfState'})

Using react-redux, what is the best practice to pass a callback function to a generic Component through the redux state?

Here's a simple case, I want to create a generic Modal component with a confirm button. Once the button was clicked, it will fire the onConfirm function we provided via the redux state. And the openModal action can be dispatch inside a thunk action.
//Example using thunk after click submit a form
const handleSubmitForm = dispatch => e => {
dispatch(openModal({
title: "confirm modal",
onConfirm: () => { /* dispatch other stuff, async etc..)*/ }
}))
}
However, the issue is that the redux suggest not to pass any non-serializable value like function to the state. And when using redux-toolkit, it will generate a error message "A non-serializable value was detected in the state". So I wonder what is the best practice to perform this type of action.
Other use case:
dispatch a callback function to the state and contact a web socket server. Then the websocket server communicate back with something that trigger the callbcak.
Simple Modal CodeSanbox: https://codesandbox.io/s/black-mountain-wnytg?file=/src/containers/App.js
The best practice is not to do store any functions in redux (though it will technically work). Instead you should store whatever arguments you need to invoke the callback and use an action creator function to actually make the call. In your example you might store modalType, modalTitle, etc.

reducer return state twice, how to update in store?

My reducer returns state in a async way, since it will interact with another component, how to make the component re-render after reducer update state in the .then?
switch (action.type) {
case RELEASE_PAYMENT:
(new PaypalContract())
.releasePayment(action.id)
.then(() => (new PaypalContract()).getState(action.id))
.then(function(e) {
console.log("status in orderReducer:",status[e])
return{
...state,
orderStatus: status[e]
}
})
return state
You can't return the state twice from the reducer function and you can't return the updated state from the reducer function in an asynchronous way.
Returning something from the callback function of the .then() method doesn't makes it a return value of the outer function, i.e. reducer function in your case.
Reducer functions are synchronous, they take in a action and based on the action return the updated state. All of this is done synchronously.
What you are doing in your reducer function won't work as you expect it to and the return statement at the of your reducer function will always execute before asynchronous code completes. This means that your reducer function always returns the same state which means that your redux store never updates.
Solution
What you need is a way to dispatch an action that triggers the data fetching and once the data is available, another action should be dispatched that is received by the reducer function. This new action should contain the data that is fetched asynchronously, as a payload and the reducer function will just update the state appropriately using the payload associated with the action.
Following are the couple of options that allow you to do just that:
redux thunk
Redux Saga
Using the first option is easier so i would recommend to start with that but you could also explore the second option which also help with advanced use cases.
Here are couple of resources to get you started with each of the options mentioned above:
Redux Thunk Explained with Examples
Redux Saga Example
You need to pass the data to the reducer after you get the response from the API. It will be simpler to control the flow of data. You can make use of async/await in this.
async function_name() {
const res = await API_CALL_HERE
res.then( data => INVOKE_YOUR_DISPATCH_HERE(data))
.catch(err => console.log('error'));
}
In reducer
case RELEASE_PAYMENT:
return {...state, orderStauts:data[e]

execute custom callbacks immediately after redux store update

How do I execute custom callback that is passed into an action through react comp, immediately after redux store update.
The idea is say, I trigger an action from react, which will make network request via thunk and dispatches the action with data. This will lead to reducer updating the store. Now, immediately after this I want to redirect to a different page (history.push()) which is a callback.
Using saga middleware it is much easier, but how to implement similar functly using thunk.
You can pass your callback defined in your component the redirect to different page to the thunk and call that after store update is complete. Like this:
function someThunkAction(callback) {
return (dispatch, getState) => {
// Update store logic...
// After update
callback();
};
}

In React with Redux, when should I save data to back end

In React with Redux, when there are some user operations, e.g., in facebook, user adds some comments, I will call dispatch() to send the add action to redux store, but when should I call back end API to save these data to database? do I need to do it together with dispatch()?
thanks
One solution would be to transfer your API logic into a thunk using a middleware package such redux-thunk (or similar).
Using thunks allows you to treat special kinds of actions as functions which means you can extend a plain action with specific action-related logic. The example you give of needing to serialize your state is an excellent use-case for redux-thunk.
You should note that, unlike reducers, thunks explicitly support fetching state and dispatching subsequent actions via the getState and dispatch functions.
Below is an ES6 example of how such a multi-purpose thunk might look.
To demo the getState() method the new item will only be saved via the api only if the redux state shouldSave value is truthy.
I would also use the async/await syntax to make sure the the api call succeeds before dispatching the local redux action.
Thunk Example - adding a new item
import api from './api'
export const addNew = async (item) => {
return (dispatch, getState) => {
try{
const state = getState()
if(state.shouldSave){
await api.save(item)
}
dispatch({
type: ITEM_ADD_NEW,
data: item
})
}catch(err){
const error = new Error("There was a problem adding the new item")
error.inner=err
throw(error)
}
}
}

Resources