State changes in Redux-Saga - reactjs

I have a simple React App. It allows a user to edit a form to update data in a database. On Submit the page generates an action with the form data. A Redux Saga yields to the action and asynchronously updates the database. This all works.
However in one case the update is slightly more involved. Not only must new data be added but any data deleted on the form must be deleted from the database in a series of API calls. To do this I need to know what has changed (e.g. what has been deleted) rather than just the new state.
How can my saga have access to this information? I could calculate it in the reducer because that has access to the previous state but it is commonly held to be an anti-pattern for the reducer to then dispatch a new action.

Sagas have a select effect available, which just runs a selector function and returns the extracted state. You can use this to retrieve the old and new items from the Redux store, and deal with the changes from there:
function* handleFormUpdates() {
const oldItem = yield select(selectOldItem);
const newItem = yield select(selectNewItem);
const changes = diffTheItems(oldItem, newItem);
// make API calls to deal with changes appropriately
}
Overall, this is a good reason to keep the "temporary" or "draft" state in Redux, so that you can make use of both the "current" and "draft" values in your logic.
I discussed some related concepts in my blog posts Practical Redux, Part 7: Form Change Handling, Data Editing, and Feature Reducers and Practical Redux, Part 8: Form Draft Data Management

...any data deleted on the form must
be deleted from the database in a series of API calls. To do this I
need to know what has changed (e.g. what has been deleted) rather than
just the new state.
If I understand correctly you have form state saved in a redux store and you need to know when and what has changed. You could create your own watcher saga:
function* watchFormUpdates(){
while (true) {
const oldFormState = yield select(selectors.selectFormState);
const action = yield take (actionTypes.FORM_UPDATE); // wait until action is dispatched
const newFormState = yield select(selectors.selectFormState); // at this point store has been updated
// compare oldFormState with newFormState...
if(oldFormState.hasSubscription && !newFormState.hasSubscription) {
yield fork(deleteSubscriptionSaga); // start non-blocking worker task
// or just dispatch action - yield put(actionCreators.deleteSubscription());
}
}
}

Related

In Redux, is there the concept of Data Provider?

Basically I want to implement an architecture of the type
UserManager.getUser(22)
getUser() -> ReduxStore -> (Does not contain an user with ID 22) -> Goes to User Provider -> User Provider goes to API and returns User object.
Redux Store then saves for subsequent requests and returns User object.
Redux has unidirectional data flow, so the writing and the reading of data are decoupled.
Components read Redux data by subscribing to the store via connect or useSelector, and they write data via disptaching actions in the store.
A selector takes in state and returns a subset of the state, but it does not change the state. A dispatched action can change the state, but it does not return any state.
CQRS (Command Query Responsibility Segregation) is one of the motivations behind Redux. The idea of CQRS is basically to:
use a different model to update information than the model you use to read information
In Redux the update-model is the actions and the read-model is the selectors. To combine them into a single "provider" would be to defeat the purpose of Redux's design.
But if you absolutely had to conflate the two concerns, it might be possible to somehow combine a selector and action-dispatch with a thunk. Again, though, it would not be idiomatic Redux.
Yes we call them action creators. Let's say you're using redux thunk for side effects so getUser will be an action creator that'll first query redux store to see if there is data available if not it'll fetch it from server and store in the redux store like this:
function getUser(id) {
// getState is a function that gives us access to entire redux store
return (dispatch, getState) => {
try {
let user = getState().users.find(x => x.id === id) || null;
if (user) {
dispatch({ type: 'GET_USER', payload: user })
return;
}
user = fetchUserFromServer(id);
dispatch({ type: 'GET_USER', payload: user })
} catch(error) {
// handle error here
}
}
}
Now when next time getUser is called there will be data for that user in the redux store and a call to server will be avoided.
Hope it helps :)

Await value returned from a redux-thunk dispatch rather than using state

I am using react, redux, and redux-thunk have a set of queries I have to run in the "logic layer". The results of each "action" call determine the path I take, so I am actually awaiting the promises returned from the dispatch call to the redux-thunks.
const handleClick = async (args) => {
let foo = await dispatch(fetchFoo(args));
if(foo) { do something... }
else {
let bar = await dispatch(fetchBar(args));
if(bar) { do another thing... }
else { or do another thing... }
}
};
Example thunk:
export const fetchFoo = args => async (dispatch) => {
let foo = await api.fetchFoo(args);
dispatch(fetchSuccess(foo));
// !!!!!!!!!!
return foo;
// !!!!!!!!!!
}
If I don't do this, it's pretty awkward to wait until a re-render (maybe) puts "foo" in the redux state prop, then wait again until a re-render (maybe) puts "bar" in the redux state, etc...
I've never really seen this pattern before although I have seen awaiting void promises return from thunks.
Is it acceptable to return the value from the redux-thunk action and use it rather than getting the values from a redux state selector? This seems to break the rules of the "single source of truth." If not, what do I do?
Using this approach it would be much harder to refactor state and change flow how data is loaded. Say in case requirements are changed and fetchBar() should be called by interval or need to be loaded by parent and passed through props. Or you want to throttle loading. Or cache data for some time.
I used this pattern with Promise returned and found it makes things rather complicated. The only advantage I found: I didn't need having isLoading or loadingError flags in redux storage to know if data is already loaded or not.
But also it means I have to call some methods redundantly just to retrieve brand new Promise await for - since promises can be resolved only once - in order to wait until some data loaded. And once some action triggers few API calls in sequence I ended with awaiting for data I did not even need.
In opposite if we rely only on data in redux it would be as easy as const isStillLoading = props.isData1Loading && props.isData2Loading
Consider moving condition for loading data into action itself. If you find API calls sequence becomes too complex you may switch to redux-saga or redux-loop which both provide better control over execution flow.

Where is the best place to store an RTCPeerConnection object in a React/Redux app?

RTCPeerConnection is an object with certain methods that when called mutate the object (for example, setLocalDescription, addIceCandidate). These methods get called based on received signaling from the other side of a WebRTC connection (like when you receive an offer, or an ice candidate).
Therefore, this object does not seem well-suited for being in a redux store, since the developer doesn't at a first approximation have control over the mutations, and in a redux reducer you can't just create a copy of an RTCPeerConnection as this would eliminate your previous webRTC session.
However, in a WebRTC app that uses React, perhaps different components need access to the RTCPeerConnection object (for instance, maybe it is instantiated on mount of the top level component in the app, but then in some UI component like a modal deep in the tree that accepts a call, you want to call a method on RTCPeerConnection to create an answer to the webRTC offer that was received. Or maybe a deeply nested component needs to initiate a call). Is it the case that the only solution is to pass the object as props down the component tree? Is there no way to use redux with a complex object like this?
UPDATE: considering the answer below about using middleware for handling socket.io, let me reframe my original question: would it make sense, if I have an RTCPeerConnection object as state in a top-level component, to build middleware that that handles dispatch calls that ultimately must receive some way some how a reference to the original RTCPeerConnection to make a method call such as setRemoteDescription?
The standard place for "socket"-like connection objects (websockets, Firebase, etc) in a Redux app is in a middleware. Any part of the application that needs to tell the socket to do something can dispatch an action that is intercepted by the middleware, and the middleware can dispatch actions to update the state in response to received messages.
There's dozens of existing examples of middleware for various sockets in the Middleware - Sockets and Adapters section of my Redux addons catalog.
update
Here's a quick example of what an RTC middleware might look like. The code is completely untested, but this should illustrate the idea:
function createRtcMiddleware() {
return (store) => {
let rtcPeerConnection = null;
return (next) => action => {
switch(action.type) {
case "RTC_CONNECTION_CREATE": {
const {rtcConfig} = action;
rtcPeerConnection = new RTCPeerConnection(rtcConfig);
rtcPeerConnection.somecallback = () => {
// maybe dispatch a Redux action in response to
// a received message
};
// Do not pass the action down the pipeline, since only
// this middleware cares about it
return;
}
case "RTC_CONNECTION_SET_DESCRIPTION": {
if(rtcPeerConnection) {
rtcPeerConnection.setDescription(action.description);
}
return;
}
}
// If we don't care about it, pass it down the pipeline
return next(action);
}
}
}

Sending error message from reducer to user

I'm new to React and Redux and I'm trying to write a simple application where a person can submit a URL for an image and it will show up on the page. Note that there is no backend to the application as of yet.
export const addImage = (url) => {
return {
type: ADD_IMAGE,
key: Guid.create().toString(),
payload: url
}
}
Adding an image creates an action of type ADD_IMAGE and my reducer updates the state consequently. However I also check if the URL is already in the list.
switch (action.type) {
case ADD_IMAGE:
if (state.find(image => image.url === action.payload)) {
return state;
} else {
return(
[
...state,
{key: action.key, url: action.payload}
]
);
}
break;
default:
}
The problem is that when I deny a post because the URL is already in the state I also want to convey that message to the user by showing it in a div next to the form. From what I've read I think I'm not supposed to try to access React state from reducers (if that is even possible) and... well.. I'm just stuck. I've been trying to find a simple guide on how to do this but I find nothing I can quite understand. After adding a database I guess I will have to do this as part of the async process but as I have it now I guess there should be some kind of simple solution.
You are starting to introduce logic into your reducer and this will inevitably lead to situation where you need to process some state outside of the reducer's scope.
The solution is to transfer your reducer logic into a thunk using a middleware package such redux-thunk (or similar package). This 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 dispatch an error action under certain conditions is an excellent use-case for redux-thunk.
Below is a example of how you might pull the logic out of your reducer into a thunk. You should note that, unlike reducers, thunks explicitly support fetching state and dispatching subsequent actions via the getState and dispatch functions.
Thunk example
export const addImage = (url) => {
return (dispatch, getState) => {
const key = Guid.create().toString()
dispatch({
type: ADD_IMAGE,
key,
payload: url
})
const state = getState()
// you would want to use a `selector` here to locate the existing image
// within the state tree
const exists = selectors.images.exists(state, url)
if (exists) {
dispatch(actions.ERROR_IMAGE_EXISTS({key, url}))
}
}
}
A note on selectors
You will see that I am using a selector to determine if the image exists. In the same way that thunks are the place to put your dispatch logic, a selector is the place to put your state-traversal logic. They are used to return portions of the state-tree or provide simple state-utilities such as the exists function shown above. Packages are available to help, for example reselect.
Follow on questions from comments
Are selectors not a built-in thing in Redux?
No they are not. Selectors are an idea that builds on top of redux and the concept exists as a place to put your state searching, caching, reading logic. This extracts the sometimes complex state traversal logic out of your thunks and components and into a nice tidy, structured collection of selectors.
Why use a selector instead of state.images.find(i => i.url === url)?
If you use a selector package then you get far more benefit than just a good separation of concerns, you get a big performance improvement (see usage example below).
Here are the headlines from the popular reselect package:
Selectors can compute derived data, allowing Redux to store the minimal possible state.
Selectors are efficient. A selector is not recomputed unless one of its arguments change.
Selectors are composable. They can be used as input to other selectors.
Why doesn't actions.ERROR_IMAGE_EXISTS(url) work for me
Because I just made that up for the example. The point is that you can dispatch actions from within the thunk, how you declare or get access to the action is up to you. I tend to centralise all my shared actions into an actions object that I import.
Selector usage example
Here is an example from my real-life code that shows how I use selectors to passing portions of the state as props to a react component:
const mapStateToProps = (state) => ({
model: services.model.selector.getSelected(state),
build: services.build.selector.getLastBuild(state),
recommendations: services.recommend.selector.getRecommendations(state)
})
Each of these selectors is finding the correct portion of the state tree and delivering it back ready for use. Nice and tidy, and if you use reselector, very efficient.

Should I use one or several action types to represent this async action?

I'm building a front-end for a search system where almost all user actions need to trigger the same async action to re-fetch search results. For example, if a user enters a keyword, then we need to fetch /api/search?q=foo, and if they later select a category we fetch /api/search?q=foo&categoryId=bar. I originally had separate action types for FETCH_RESULTS, SELECT_CATEGORY, DESELECT_CATEGORY, etc. I created one asynchronous action creator for FETCH_RESULTS, but the others are synchronous. The more I think about it, they all end up needing to re-fetching the results from the backend and update the app state based on the response from the backend.
Would it make sense for me to use the single async action-creator for any change? Or would it be better to use async action creators for each distinct user action (selecting a keyword, category, or filter)?
I think the advantage of granular actions would be the events more accurately reflect what the user did (e.g. the user selected a category) vs having to peer into the payload to figure out what actually changed, but they are all pretty similar.
This is of course something only you can really answer based on what you know about the project. I don't think that there is any inherent advantage to having the actions be more granular, and if there aren't any, its not worth the extra effort. I would have a generic FILTER_CHANGED event and not worry about being able to see what specifically changed--presumably the action isn't going to be complicated, so I'm not going to be debugging the action a lot. As the filter state becomes more complicated and diverse, it might make more sense to break out the actions. By default though, I don't really see much value.
I fully agree with Nathan’s answer.
I just want to add that in order to tell whether actions A and B are really one or two actions, you need to ask yourself: “If I change how some reducers react to A, will I also need to change how they react to B?”
When the handlers change together in the reducer code, it’s likely they should be a single action. When their changes may not affect each other, or if many reducers handle just one of them but not the other, they should probably stay separate.
I agree with Dan Abramov: if the text and categories are highly coupled in your interface, just fire FETCH_RESULTS with the text and categories as action payload.
If the text input and categories selection widget do not share a close parent component, it is complicated to fire a FETCH_RESULTS which contains the text and categories (unless passing a lot of props down the tree...): you then need the action granularity.
One pattern that I have found helpful when such granularity is needed is the Saga / Process manager pattern. I've written a bit about it here: https://stackoverflow.com/a/33501899/82609
Basically, implementing this on redux would mean there's a very special kind of reducer that can trigger side-effects. This reducer is not pure, but do not have the purpose of triggering React renderings, but instead manage coordination of components.
Here's an example of how I would implement your usecase:
function triggerSearchWhenFilterChangesSaga(action,state,dispatch) {
var newState = searchFiltersReducer(action,state);
var filtersHaveChanged = (newState !== state);
if ( filtersHaveChanged ) {
triggerSearch(newFiltersState,dispatch)
}
return newState;
}
function searchFiltersReducer(action,state = {text: undefined,categories: []}) {
switch (action.type) {
case SEARCH_TEXT_CHANGED:
return Object.assign({}, state, {text: action.text});
break;
case CATEGORY_SELECTED:
return Object.assign({}, state, {categories: state.categories.concat(action.category) });
break;
case CATEGORY_UNSELECTED:
return Object.assign({}, state, {categories: _.without(state.categories,action.category) });
break;
}
return state;
}
Note if you use any time-traveling (record/replay/undo/redo/whatever) debugger, the saga should always be disabled when replaying actions because you don't want new actions to be dispatched during the replay.
EDIT: in Elm language (from which Redux is inspired) we can perform such effects by "reducing" the effects, and then applying them. See that signature: (state, action) -> (state, Effect)
There is also this long discussion on the subjet.
EDIT:
I did not know before but in Redux action creators can access state. So most problems a Saga is supposed to resolve can often be solved in the action creators (but it creates more unnecessary coupling to UI state):
function selectCategory(category) {
return (dispatch, getState) => {
dispatch({type: "CategorySelected",payload: category});
dispatch({type: "SearchTriggered",payload: getState().filters});
}
}

Resources