In my React application i'm using Long-polling API. In order to automatically send requests on every response i use middleware. But before sending new request, i have to save received data in store. More than that, i want to dispatch another action inside my middleware. So my structure looks like this:
InitLongPoll() -> SendRequest(data) -> ReceiveResponse(data)* -> SendRequest(data)
'*' is my middleware. From there i'm saving data to the store using store.dispatch(responseData) and sending new request using store.dispatch(sendRequest(authData)).
Is it okay to receive that authData using store.getState().authReducer? As far as i know, my middleware should be a pure function and shouldn't depend on external data (store). Thanks in advance.
Is it okay to receive that authData using
store.getState().authReducer? As far as i know, my middleware should
be a pure function and shouldn't depend on external data (store).
Yes it is. Middleware is the way to introduce side effects into the redux loop, and it can't be a pure function. Your own middleware has a side effect - polling the server.
A redux middleware is the anti-thesis of a pure function, which is defined as:
The function always evaluates the same result value given the same argument value(s). The function result value cannot depend on any
hidden information or state that may change while program execution
proceeds or between different executions of the program, nor can it
depend on any external input from I/O devices (usually—see below).
Evaluation of the result does not cause any semantically observable side effect or output, such as mutation of mutable objects or output
to I/O devices (usually—see below).
You can also see in the redux-thunk source code, that it uses getState:
function createThunkMiddleware(extraArgument) {
return ({ dispatch, getState }) => next => action => {
if (typeof action === 'function') {
return action(dispatch, getState, extraArgument);
}
return next(action);
};
}
Related
I am using redux-saga with React. Lets say i'm fetching some data from server that need some external client side calculation. My question is where to put them.
my saga looks something like this:
function* someEffect(action){
try{
//removed none related stuff from the code.
const data = yield call(someRequestFucntionThatUsesAxios, requestparams);
if(data.status>=200&&data.status<300){
yield put({type:SUCCESS, payload:data.data)};
}
catch(err){
}
}
In the above code data.data needs some calculation(basic math mostly). Now, which one of these options makes more sense?
handling those calculations in my connected components componentDidUpdate method.
handling them right after receiving it (before dispatching to to reducer).
(I know that reducer is no place for such behaviour but since it doesn't kill to ask,) handling it inside my reducer.
As you have said reducer is not the best place to modify the API response, neither saga is.
I would suggest you to use transformResponse function in axios, to modify the response just right after it is returned from API.
axios.get('/', {
transformResponse: (data) => {
// do whatever you like with the data
},
});
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.
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);
}
}
}
Would there be anything wrong/anti-pattern-ish (in terms of 'thinking-in-react/redux') in added a callback to the action.data passed into an action?
// reducer
ACTION_FOR_REDUCER() {
var x = 123
if ( action.data.callback ) action.data.callback( x )
return {
something: action.data.somedata
}
},
Then accessing that data later in the App when the action is called (in an container perhaps)
// later in the app
this.props.dispatch(changeSomething({
somedata: somedata,
callback: (x) => { console.log(x) }
}))
The idea is not wrong, the implementation is.
Instead of adding a callback, return a promise from the action (you will need redux-thunk middleware or some similar alternative).
Then you can simply do:
dispatch(myAction).then(callback);
Of course, you can also simply dispatch another action in your callback, which usually end up being one compound action.
const myAction = ...
const callbackAction = ...
const compoundAction = () => dispatch => {
dispatch(myAction())
.then(() => dispatch(callbackAction()));
};
The third principle of Redux (listed in the introduction section of the docs) is that 'changes are made with pure functions'.
A pure function is a function that always returns the same result given the same inputs, and doesn't cause any side effects. Having a callback log something out most definitely would be a side effect!
The docs go on to say:
It's very important that the reducer stays pure. Things you should never do inside a reducer:
Mutate its arguments;
Perform side effects like API calls and routing transitions;
Call non-pure functions, e.g. Date.now() or Math.random().
The reasoning behind this is it makes your reducers predictable - you know that, given an action with a certain payload, you're guaranteed to have the same output. This makes your app both easier to understand as a developer, and easier to unit test.
If you want to perform side effects when an action takes place, you should install a middleware that allows you to do so - some common examples of these are:
redux-thunk, which allows you to dispatch functions that can in turn dispatch actions (commonly used to make API calls)
redux-logger, which allows you to log out actions that are dispatched.
redux-devtools-extension, which allows you to view your state and actions in a Chrome devtools extension.
If you can't find a middleware package that does what you need (rare, to be honest - there's lots out there already!), you can write your own quite easily.
Callbacks introduce side-effects, so when using Redux you should consider returning a new state with the result, for example with done: true flag and react to that in componentDidUpdate.
For managing asynchrony and effects you can use redux-saga, redux-thunk, RxJs or another library.
If you want to stick with callbacks, why even bother with Redux?
I keep reading that I should use redux-thunk or redux-saga to handle side effects.
Why not simply use action creators like that to dispatch multiple actions :
function loadProductActionCreator(dispatch) {
dispatch({
type: 'load_product',
})
fetch('api/product').then(
function (r) {
return r.json();
}
)
.then(function (res) {
dispatch({
type: 'loaded_product',
data: res
})
})
}
I tried that and it worked (complete code). So I guess there must be some inconvenients I'm not aware of.
You code is similar to what thunk does.
As per redux docs, actions should be pure. And they should always return same values for same input parameters. By using fetch you are allowing action to return not specific value, rather value from server and that mean action response may vary upon time.
That is called side effects. And it's something what shouldn't be in redux actions by default.
But why?
Yes, you can type it inside action like you have, in small apps it does not matter.
In larger application there are benefits of using redux-saga:
actions are predictable, they just return payload like
{
type: 'FETCH_POSTS',
params: {
category: 'programming'
}
}
and then you build middleware which will take actions with all data required to perform request to real API
Possible advantages:
Cleaner codebase (but may be overhead on smaller applications)
Separation of "dummy" actions with all required information to perform requests and actual API middleware
Request parameters are visible directly in redux dev tools
Possible to easily debounce, throttle fetches which may be really tricky with redux-thunk
Possible to easily combine actions (wait for another event/fetch, chain events)
Possible to stop running tasks
From personal experience, on one project (larger codebase) we have started with redux-thunk, but later we needed to integrate more advanced features, like throttle, and some dependencies between actions. So we rewrote everything to redux-saga and it worked well for us.
You are kind of replicating redux-thunk here. A pure redux action creator should return an action object to be dispatched and not dispatch an action itself (see redux doc on action creator).
To better understand why your technic is a replication of redux-thunk, look at this post from its author