Redux+Websockets: Why manage this using middleware? - reactjs

I've been reading about the best way to integrate WebSockets into a React/Redux app, and I'm finding answers but with some sentence along the lines of "The best place for websocket implementation is usually middleware."
My question is WHY this is preferred? What is the benefit of doing this vs setting up the websocket/having listeners dispatch actions in the outer App-level React container (in componentWillMount for instance)?
This seems like it would be equivalent in terms of lasting throughout the lifecycle of the app, etc. What am I missing here?

There are couple of pros with placing such logic in middle-wares instead of actual components.
The main reasons in my opinion are:
Each connected component will instantiate a WebSocket or you will
need a global declaration of the connection which will be
independently from the store, in other words, not part of the redux
flow.
Middle-Wares have access to the store and are part of the redux
flow.
You also get access to the entire store, hence you can pass
forward more data then initially dispatched.
You decouple the components from the web-socket logic, hence you can
centralize your code and reuse your components.
All in all, there are no special reasons that are specific regarding using web-sockets with middle-wares, using middle-wares have great advantages in general.
Edit
As a followup to your comment
How would you suggest managing a case where you might want a
particular component to initialize a websocket connection, but want to
manage whether it's already connected, etc... would it be just as
simple as having a flag in the store that says it's connected or is
there a better approach?
As i said, I wouldn't initialize a web-socket connection within a component, rather I would do it in the entry point of my application. like index.js for example.
If your concern is to make sure you won't try to connect when there is already a connection, then a simple socketStart method that get invoked at the point when you create the store and initialize all your App data, you can pass it a callback that will do the rendering and store update through dispatch.
A simple example (keep in mind this is a pseudo code ):
Our Socket-start Method:
export function socketStart(store, callback) {
// this is only a pseudo code!
// register to server -> client events
_socketClient.someFunction = (data) => {
store.dispatch({ type: "Server_Invoked_Some_Function", data });
}
_socketClient.someFunction2 = (data) => {
store.dispatch({ type: "Server_Invoked_Some_Function2", data });
}
// connect to the server via the web-socket client API
_socketClient.connect(() => callback());
}
We can use it in our index.js file:
let store = createStore(
// your reducers here...
// ...
applyMiddleware(socketMiddleware) // our web socket middleware
)
// the callback will invoked only if the connection was successful
// the React-Dom's render function is our callback in this case
socketStart(store, () => { render(<App />, document.getElementById("root")); });

With middleware, you can easily unfold/relay messages between Redux and Web Socket. Also, you can use Redux middleware without React, that means you can write API using Redux on server-side code (probably with Redux saga).
I would agree lifetime management as a React component is easier than a Redux middleware. But if you want to reconnect (destroy/recreate), you will need to use key props to make the reconciler to consider it as a new object, which is a little bit weird.
You can look at redux-websocket-bridge, which unfold Web Socket messages into Redux actions, and relay Redux actions to Web Socket.
On your Web Socket server, you send an action:
ws.on('connection', conn => {
conn.send(JSON.stringify({
type: 'GREETING',
payload: { now: Date.now() }
}));
});
You will get the GREETING action on your Redux reducer. And vice versa, when you want to relay an action to Web Socket, you mark your action with meta.send with true:
this.props.dispatch({
type: 'SIGN_IN',
meta: { send: true },
payload: { authToken: '...' }
});

Related

Communication Between Component and API using Redux- Saga

How can we get the data from the saga directly into our component ?
or
Is this a pattern we should not follow and directly make a service/ api
call from the component using some service layer.
I have been following this issue on git on if this is possible there are too many permutations and combinations and i am a bit confused ..
I tried this small example by refering this
stackblitz.
In this case when i try and return this
function* helloSaga() {
return new Promise(function(resolve, reject) {
resolve('start of new Promise');
});
}
and access it like this
let response = dispatch(action('SHOW')).then(data => {
console.log(data); // i cannot get this to work say if this were a api response .
})
Nothing happens .
Is this pattern acceptable if yes then how can we make it work and what am i missing it
And if this pattern is an anti-pattern then making service calls from a layer like getData() should be enough from componentDidMount() .
A generator function doesn't really return a value (or the promise in your case) as you'd expect with any "regular" function. It returns a generator-object that you can pause, continue, cancel etc.
Check https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/function*
The way to go with redux-saga in a web app would be:
Trigger an action like "SHOW" in your example
Have a saga take your action "SHOW"
The saga would then do the async API request
Once the API request is done, the saga dispatches another action (something like FETCH_SUCCEEDED
Handle the action in your reducer and store the response in the redux store.
Once it's stored in the redux store, you can select it from any react component you want to (use connect from redux for that)
Here's your stackblitz with a minimal example:
https://stackblitz.com/edit/react-redux-sagas-demo-app-4tpevb?file=index.js
For a production app, you'd want to wrap the API request in a try{..}catch(){..} and handle the error with a proper action like FETCH_FAILED and display an error message in your FE (if it effects the user).

Synchronously read from the store?

So I'm using Apollo for my app's state, and am so far a little taken aback there's no equivalent to mapStateToProps or something.
As far as I understand, to have any data globally accessible in my store, once I get the data, I need a query to write the data to the store, then another query in my other component to go and get it.
By this point, the other component has very much mounted and rendered, so content just sort of flickers in and out.
In Redux, I can just add new data to the store in my reducers, then anything that's connected with mapStateToProps has access to it.
Is there an equivalent? Or does everything need to go through asynchronous queries? Does anyone else kind of find this an enormous pain?
For example, in one component I'm getting some invitation data:
this.props.client.query({
query: REQUEST_ACTION_DATA,
variables: {
id: actionData.id,
type: actionData.type
}
}).then(data => {
this.props.client.writeQuery({query: GET_ACTION_DATA, data: {
action: {
type: data.data.actionData.type,
object: data.data.actionData.invitation,
__typename: 'ActionDataPayload'
}
}})
this.props.history.push('/auth/register')
})
... then in my other component I have this:
componentWillMount() {
const authToken = localStorage.getItem(AUTH_TOKEN);
if (authToken) {
this.props.history.push('/')
}else{
this.props.client.query({
query: GET_ACTION_DATA
}).then(data => {
if(data.data && data.data.action && data.data.action.type == 'invite'){
this.setState({
invitation: data.data.action.object
})
}
console.log(data)
})
}
}
ignoring the fact that it's hugely unwieldy to write all this for something so simple, is there just a way to access store data without having to wait around?
The graphql higher order component from react-apollo is a supercharged connect from redux. It will do the following:
When the component mounts it will try and execute the query on the Apollo cache. You can think of the query as a DSL for selecting data not only from the server but also from the store! If this works the component is pretty much instantly filled with the data from the store and no remote requests are executed. If the data is not available in the store it triggers a remote request to receive the data. This will then set the loading property to true.
The Apollo cache is a normalised store very similar to your redux store. But thanks to the well thought through limitations of GraphQL the normalisation can happen automatically and you don't have to bother with the structure of the store. This allows the programmer to forget about stores, selectors and requests altogether (unless you are heavily performance optimising your code). Welcome to the declarative beauty of GraphQL frontend frameworks! You declare the data you want and how it gets there is fully transparent.
export default graphql(gql`{ helloWorld }`)(function HelloWorld({ data }) {
if (data.loading) {
return <div>Loading the simple query, please stand by</div>;
}
return <div>Result is: {data.helloWorld}</div>;
});
No selector, no denormalisation happening. This is done by Apollo Client! You will get updated data when the cache updates automatically similar to Redux.
No componentWillMount checks for data is in store already and triggering request actions. Apollo will make the checks and trigger queries.
No normalisation of response data (also done by Apollo Client for you)
If you do any of the above you are probably using Apollo Client wrongly. When you want to optimise your queries start here.

ReactJS redux subscribe statements

Is store.subscribe( () => {} ) the only way to watch for changes in the Redux stores?
I fetch my users asynchronously and by the time the app is loaded the Users store is most likely empty so I have to do something like this:
store.subscribe( () => {
var users = store.getState().users;
if( users.length > 0 ) {
this.setState({ ... })
}
});
Now I have 3 or 4 components I have to use the same logic in each of them. My question: is there a more elegant solution to this problem? Some way to listen to the changes in Redux on a 'global level'?
Two answers here.
Yes, store.subscribe() is the only way to watch the store for changes (with the nitpicking exception that the store also implements Symbol.observable, so you if you're using observables you could also subscribe that way).
However, the connect function from the official React-Redux already handles the process of store subscriptions and extracting data that your components need. Don't write store subscription logic yourself - use connect. (I wrote a long comment on Reddit explaining why you should use React-Redux instead of manually subscribing .)

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);
}
}
}

Where to put network calls in a react+redux app

I'm trying to get some thoughts on what people would consider the best practices for how people organize network calls in their react+redux apps. I usually let my components make the calls, get data and then pass that into an action that will get reduced. Is this the best practice or is it better to separate networking out of my components and place that logic somewhere else in the app, maybe in the reducers?
The best place to make network calls is in your action creators. However, you're going to need some middleware to make that work best. Take a look at this promise-middleware (in fact, I'd suggest checking out that whole tutorial). If you use that middleware, you can have action creators that return a promise and also have three action types - one for the request, one to handle successful responses, and one to handle failed requests. Then you just listen for those 3 actions in your reducers.
So with that middleware, you could have an action creator like this:
function networkCall() {
return {
types: ['MAKE_REQUEST', 'REQUEST_SUCCESS', 'REQUEST_FAILURE'],
promise: () => {
return new Promise((resolve, reject) => {
$.ajax({
url: 'example.com/api'
type: 'GET'
});
})
}
}
}
Obviously you are free to build your own promise middleware, but that should set you in the right direction.
I think this is one thing that was done right in Angular. You have all your network calls neatly placed in your services. This can easily be done in Redux.
The docs rightly suggest that network calls are in your actions. I'd factor them out into a separate place, you can call it "services". There you would define all your constants such as your API server URL, authentication-related stuff, etc. This would be the only place that is aware of the implementation details of your network calls - which library you use (jQuery, axios, superagent, etc).
Your actions files would import function from those services and call them. If you decide later on to swap out your networking library, you wouldn't have to change your actions.
You could use an API middleware, either redux-api-middleware or something of your own (it isn't very hard to write one).
Then, for example, your action creators could return actions like
{type: 'API_GET', url: '/api/userList', nextType: 'USER_LIST'}
...that would be later handled by a middleware that would send the actual request and then dispatch a new action like:
{type: 'USER_LIST_FETCHED', status: 200, payload: [{id: 1, ...}, ...]}
{type: 'USER_LIST_FAILED', status: 404, payload: {message: '...'}}
I pretty much follow the pattern of the actions in the redux tutorials for Async Actions. It makes most sense to me to keep everything async in the actions -- away from both the components and the store/reducers.
I also use Redux Crud to standardize the actions related to network actions.

Resources