I want to create a websocket on demand when certain components want to subscribe to data. How can I share the websocket instance in a redux fashion?
action.js
export function subscribeToWS(url) {
return dispatch => {
let websocket = new WebSocket(url)
websocket.on('connect', () => {
websocket.send("subscribe")
}
websocket.on('message', (message) => {
dispatch(storeNewData(message))
}
}
}
I could do something like this, but this would require a new instance for every new subscription.
The standard place to put things like persistent connection objects is inside middleware. And, in fact, there's literally dozens of existing middlewares that demonstrate that approach, with most of them listed over at https://github.com/markerikson/redux-ecosystem-links/blob/master/middleware.md#sockets-and-adapters . You should be able to use some of those as-is, or at least as examples.
You can look at redux-websocket-bridge. It unfold Web Socket messages into Redux action, and relay Redux action to Web Socket.
Upside of this approach: you can use Redux on your server as API endpoint, replacing standard REST API with less code.
Also, if your server do not send Flux Standard Action, you can still use redux-websocket-bridge for raw messages. It works with string, ArrayBuffer, and Blob. Of course, you can always write a small middleware to translate them into Flux Standard Action, e.g. messages from Slack RTM API.
Although this is quite an old question by now, it popped up several times when looking for an example. As #matthewatabet and #abguy mention, https://github.com/luskhq/redux-ws just mentions it has been deprecated and you can use Redux Thunk, without an example specific for web sockets.
For future reference, I found this article that outlines an example, that is implemented in a Github repo, starting on this file. This is for socket.io, but using web sockets directly should be similar.
Summarizing, in the Component call dispatch with addNewItemSocket:
<RaisedButton
label="Click to add!" primary={true}
onTouchTap={ () => {
const newItem = ReactDOM.findDOMNode(this.refs.newTodo.input).value
newItem === "" ? alert("Item shouldn't be blank")
: dispatch(addNewItemSocket(socket,items.size,newItem))
{/*: dispatch(addNewItem(items.size,newItem))*/}
ReactDOM.findDOMNode(this.refs.newTodo.input).value = ""
}
}
/>
In the actions file, declare addNewItemSocket as:
export const addNewItemSocket = (socket,id,item) => {
return (dispatch) => {
let postData = {
id:id+1,
item:item,
completed:false
}
socket.emit('addItem',postData)
}
}
To handle incoming messages from the socket, in the constructor of the component:
socket.on('itemAdded',(res)=>{
console.dir(res)
dispatch(AddItem(res))
})
And in the actoins file, declare AddItem as:
export const AddItem = (data) => ({
type: "ADD_ITEM",
item: data.item,
itemId:data.id,
completed:data.completed
})
For me this is still new, so any feedback is appreciated. I will also file a PR with https://github.com/luskhq/redux-ws to have an example listed there.
Related
I'm encounter myself writing a custom hook that exposes a socket connection, the fact is that the code is there and it is working but I have the following API.
const socket = useWebSocket(
companyId,
accessToken,
resourceUrl,
!settings.data.manualDispatch,
options
);
The hook encapsulate several things:
Authenticate the WebSocket connection by asking first for a ticket that is appended to the WebSocket url (so it actually does a first HTTP request that returns a ticket and then trigger other effect that create a connection and update the socket state that is being return)
As I mentioned previously it does a request GET to a ticket and create a WebSocket connection
Cleanup any existing WebSocket connection when a new one is created (through state management)
The problem is:
I am currently using a reference within the hook to avoid create new objects and ending up in an infinite loop BUT It will be that consider an anti-pattern?.
Use memo alongside the hook?
Yes sure is valid approach but I feel like I am forcing the developers using my hook to write the same code over and over (memoize the options before pass it to the hook).
In summary:
OPTION 1:
Don't memoize the options to use the hook and do that process within the hook (because options are made of things that are not mean to change, urls and callbacks).
const socket = useWebSocket(
companyId,
accessToken,
resourceUrl,
!settings.data.manualDispatch,
{
onMessageCallback: message => {
// do things with message
},
onUnexpectedCloseCallback: () => {
// do things on close
},
url: websocketUrl,
}
);
OPTION 2:
We memoize the options in order to use the hook because the option 1 is an anti-pattern.
const options = useMemo(
() => ({
onMessageCallback: message => {
// do things with message
},
onUnexpectedCloseCallback: () => {
// do things on close
},
url: websocketUrl,
}),
[websocketUrl]
);
const socket = useWebSocket(
companyId,
accessToken,
resourceUrl,
!settings.data.manualDispatch,
options
);
I'm using redux and redux-saga in an application to manage state and asynchronous actions. In order to make my life easier, I wrote a class that acts essentially as a saga manager, with a method that "registers" a saga. This register method forks the new saga and combines it with all other registered sagas using redux-saga/effects/all:
class SagasManager {
public registerSaga = (saga: any) => {
this._sagas.push(fork(saga));
this._combined = all(this._sagas);
}
}
This class is then used by my store to get the _combined saga, supposedly after all sagas are registered:
const store = Redux.createStore(
reducer,
initialState,
compose(Redux.applyMiddleware(sagaMiddleware, otherMiddleware)),
);
sagaMiddleware.run(sagasManager.getSaga());
However, I ran into the problem that depending on circumstances (like import order), this doesn't always work as intended. What was happening was that some of the sagas weren't getting registered before the call to sagaMiddleware.run.
I worked around this by providing a callback on SagasManager:
class SagasManager {
public registerSaga = (saga: any) => {
this._sagas.push(fork(saga));
this._combined = all(this._sagas);
this.onSagaRegister();
}
}
And then the store code can use this as
sagasManager.onSagaRegister = () => sagaMiddleware.run(sagasManager.getSaga());
This seems to work, but I can't find in the docs whether this is safe. I did see that .run returns a Task, which has methods for canceling and the like, but since my problem is only in that awkward time between when the store is constructed and the application is rendered I don't that would be an issue.
Can anyone explain whether this is safe, and if not what a better solution would be?
It may depend on what you mean by "safe". What exactly do you mean by that in this case?
First, here's the source of runSaga itself, and where it gets used by the saga middleware.
Looking inside runSaga, I see:
export function runSaga(options, saga, ...args) {
const iterator = saga(...args)
// skip a bunch of code
const env = {
stdChannel: channel,
dispatch: wrapSagaDispatch(dispatch),
getState,
sagaMonitor,
logError,
onError,
finalizeRunEffect,
}
const task = proc(env, iterator, context, effectId, getMetaInfo(saga), null)
if (sagaMonitor) {
sagaMonitor.effectResolved(effectId, task)
}
return task
}
What I'm getting out of that is that nothing "destructive" will happen when you call runSaga(mySagaFunction). However, if you call runSaga() with the same saga function multiple times, it seems like you'll probably have multiple copies of that saga running, which could result in behavior your app doesn't want.
You may want to try experimenting with this. For example, what happens if you have a counter app, and do this?
function* doIncrement() {
yield take("DO_INCREMENT");
put({type : "INCREMENT"});
}
sagaMiddleware.runSaga(doIncrement);
sagaMiddleware.runSaga(doIncrement);
store.dispatch({type : "DO_INCREMENT"});
console.log(store.getState().counter);
// what's the value?
My guess is that the counter would be 2, because both copies of doIncrement would have responded.
If that sort of behavior is a concern, then you probably want to make sure that prior sagas are canceled.
I actually ran across a recipe for canceling sagas during hot-reloading a while back, and included a version of that in a gist for my own usage. You might want to refer to that for ideas.
I have a React Native app I'm going to be building that uses WebSockets. I have a WebSocket library written in JavaScript and I'm simply re-using it for this project, which is fantastic.
My question is, being new to React/React Native, what is the best practice for setting up and maintaining all of the traffic going through the WebSocket?
Initially my idea was to create the websocket in the main App component, something like this:
export default class App extends Component {
constructor(props) {
super(props);
this.ws = new WebSocket;
}
componentWillMount() {
console.log(this.ws);
}
render() {
console.log("We are rendering the App component.....");
return (
<View style={styles.container}>
<Text style={styles.welcome}>Hello, world</Text>
</View>
);
}
}
The actual WebSocket class would contain all of the respective connection handling:
ws.onopen = () => {
// connection opened
ws.send('something'); // send a message
};
ws.onmessage = (e) => {
// a message was received
console.log(e.data);
};
ws.onerror = (e) => {
// an error occurred
console.log(e.message);
};
ws.onclose = (e) => {
// connection closed
console.log(e.code, e.reason);
};
My question is, since the data coming through WebSocket will be applicable for state through many components in the React Native app, but it is not a class that will extend React.Component, do I not interact with Redux in the WebSocket class? Do I move all of the WebSocket connection handling to the App component and dispatch actions there to Redux?
What's the common pattern here to instantiate my WebSocket class and ensure that all traffic in it is properly getting passed to Redux so all component's state will funnel correctly?
Great answers here so far. Just wanted to add that where you keep your data should really be a decision based on what type of data it is. James Nelson has an excellent article on this topic that I refer to regularly.
For your case, let's talk about the first 3 types of state:
Data
Communication State
Control State
Data
Your WebSocket connection is generic and could technically return anything, but it's likely that the messages you're receiving are data. For example, let's say you're building a chat app. Then, the log of all messages that have been sent and received would be the data. You should store this data in redux with a messages reducer:
export default function messages(state = [], action) {
switch (action.type) {
case 'SEND_MESSAGE':
case 'RECEIVE_MESSAGE': {
return [ ...state, action.message ];
}
default: return state;
}
}
We don't have to (and we shouldn't) have any WebSocket logic in our reducers, as they are generic and don't care where the data is coming from.
Also, note that this reducer is able to handle sending and receiving in exactly the same way. This is because the network communication is handled separately by our communication state reducer.
Communication State
Since you're using WebSockets, the types of communication state you want to track may differ from my example. In an app that uses a standard API, I would track when a request is loading, failed, or successful.
In our chat app example, you'll probably want to track these request states whenever you send a message, but there could be other things you want to classify as communication state as well.
Our network reducer can use the same actions as the messages reducer:
export default function network(state = {}, action) {
switch (action.type) {
case 'SEND_MESSAGE': {
// I'm using Id as a placeholder here. You'll want some way
// to tie your requests with success/failure receipt.
return {
...state,
[action.id]: { loading: true }
};
} case 'SEND_MESSAGE_SUCCESS': {
return {
...state,
[action.id]: { loading: false, success: true }
};
} case 'SEND_MESSAGE_FAILURE': {
return {
...state,
[action.id]: { loading: false, success: false }
};
}
default: return state;
}
}
This way, we can easily find the status of our requests, and we don't have to bother with loading/success/failure in our components.
However, you might not care about the success/failure of any given request since you're using WebSockets. In that case, your communication state might just be whether or not your socket is connected. If that sounds better to you, then just write a connection reducer that responds to actions on open/close.
Control State
We'll also need something to initiate the sending of messages. In the chat app example, this is probably a submit button that sends whatever text is in an input field. I won't demonstrate the whole component, as we'll use a controlled component.
The takeaway here is that the control state is the message before it's sent. The interesting bit of code in our case is what to do in handleSubmit:
class ChatForm extends Component {
// ...
handleSubmit() {
this.props.sendMessage(this.state.message);
// also clear the form input
}
// ...
}
const mapDispatchToProps = (dispatch) => ({
// here, the `sendMessage` that we're dispatching comes
// from our chat actions. We'll get to that next.
sendMessage: (message) => dispatch(sendMessage(message))
});
export default connect(null, mapDispatchToProps)(ChatForm);
So, that addresses where all of our state goes. We've created a generic app that could use actions to call fetch for a standard API, get data from a database, or any number of other sources. In your case, you want to use WebSockets. So, that logic should live in your actions.
Actions
Here, you'll create all of your handlers: onOpen, onMessage, onError, etc. These can still be fairly generic, as you've already got your WebSocket utility set up separately.
function onMessage(e) {
return dispatch => {
// you may want to use an action creator function
// instead of creating the object inline here
dispatch({
type: 'RECEIVE_MESSAGE',
message: e.data
});
};
}
I'm using thunk for the async action here. For this particular example, that might not be necessary, but you'll probably have cases where you want to send a message then handle success/failure and dispatch multiple actions to your reducers from within a single sendMessage action. Thunk is great for this case.
Wiring It All Together
Finally, we have everything set up. All we have to do now is initialize the WebSocket and set up the appropriate listeners. I like the pattern Vladimir suggested--setting up the socket in a constructor--but I would parameterize your callbacks so that you can hand in your actions. Then your WebSocket class can set up all the listeners.
By making the WebSocket class a singleton, you're able to send messages from inside your actions without needing to manage references to the active socket. You'll also avoid polluting the global namespace.
By using the singleton set up, whenever you call new WebSocket() for the first time, your connection will be established. So, if you need the connection to be opened as soon as the app starts, I would set it up in componentDidMount of App. If a lazy connection is okay, then you can just wait until your component tries to send a message. The action will create a new WebSocket and the connection will be established.
You can create dedicated class for WebSocket and use it everywhere. It's simple, concise and clear approach. Moreover you will have all stuff related to websockets encapsulated in one place! If you wish you can even create singleton out of this class, but the general idea is this:
class WS {
static init() {
this.ws = new WebSocket('ws://localhost:5432/wss1');
}
static onMessage(handler) {
this.ws.addEventListener('message', handler);
}
static sendMessage(message) {
// You can have some transformers here.
// Object to JSON or something else...
this.ws.send(message);
}
}
You have only run init somewhere in index.js or app.js:
WS.init();
And now you can loosely send message from any application layer, from any component, from any place:
WS.sendMessage('My message into WebSocket.');
And receive data back from WebSocket:
WS.onMessage((data) => {
console.log('GOT', data);
// or something else or use redux
dispatch({type: 'MyType', payload: data});
});
So you can use it everywhere even in redux in any action or somewhere else!
There are no official guidelines about that. I think using a component is confusing because it will not be rendered, and I guess if you use Redux you want to share the data from websocket anywhere in the application.
You can give the dispatch function to your Websocket manager.
const store = createStore(reducer);
const ws = new WebSocketManager(store.dispatch, store.getState);
And use this.dispatch inside your class methods.
// inside WebSocketManager class
constructor(dispatch, getState) {
this.dispatch = dispatch;
this.getState = getState;
}
You can also use middlewares to handle side effects, I think it is the recommended way. There are two great libraries that you can look :
redux-saga
redux-observable
So, let's say I have the next action:
export function login({ email, password, redirectTo, doNotRedirect }) {
return ({ dispatch }) => {
const getPromise = async () => {
const basicToken = Base64.encode(`${email}:${password}`);
const authHeaders = { Authorization: `Basic ${basicToken}` };
const { payload, error } = await dispatch(sendAuthentication(authHeaders));
if (error) throw payload;
const { username, token, fromTemporaryPassword } = payload;
const encodedToken = Base64.encode(`${username}:${token}`);
dispatch(persistence.set('authorizationToken', encodedToken));
dispatch(postGlobalId({ username }));
dispatch(setIsLoggedIn(true));
dispatch(setIsFromTemporaryPassword(fromTemporaryPassword));
await dispatch(clientActions.fetchClient);
if (doNotRedirect) return;
if (fromTemporaryPassword)
dispatch(updatePath('/profile/change-password'));
else
dispatch(updatePath(redirectTo || '/dashboard'));
};
return {
type: AUTHENTICATION_LOGIN,
payload: getPromise()
};
};
}
And I want to add tests for it, to add reliability to the code.
So, here are few things:
We send authentication headers and get data as a response
We throw an error if some error is present in the response
We set up all needed tokens, dispatch all needed actions to show that we are logged in now
Fetching client data
Based on params and received data, we redirect to needed route / don't redirect
The question is that it is really too hard to test and we need to stub literally everything, which is bad due to brittle tests, fragility and too much of implementation knowing (not to mention that it is pretty challenging to stub dispatch to work properly).
Therefore, should I test all of these 5 points, or to focus only on the most important stuff, like sending authorization request, throw error and check redirects? I mean, the problem with all flags that they can be changed, so it is not that reliable.
Another solution is just to separate these activities into something like following:
auth
setLoginInfo
handleRedirects
And to pass all needed functions to invoke through dependency injection (here just with params, basically)? With this approach I can spy only invoking of this functions, without going into much details.
I am quite comfortable with unit testing of pure functions and handling different edge-cases for them (without testing too much implementation, just the result), but testing complex functions with side-effects is really hard for me.
If you have very complex actions like that, I think an alternative (better?) approach is to have simple synchronous actions instead (you can even just dispatch payloads directly, and drop action creators if you like, reducing boiler-plate), and handle the asynchronous side using redux-saga: https://github.com/yelouafi/redux-saga
Redux Saga makes it very simple to factor out your business logic code into multiple simple generator functions that can be tested in isolation. They can also be tested without the underlying API methods even being called, due to the 'call' function in that library: http://yelouafi.github.io/redux-saga/docs/api/index.html#callfn-args. Due to the use of generators, your test can 'feed' values to the saga using the standard iterator.next method. Finally, they make it much easier for reducers to have their say, since you can check something from store state (e.g. using a selector) to see what to do next in your saga.
If Redux + Redux Saga had existed before I started on my app (about 100,000 JS(X) LOC so far), I would definitely have used them.
I'm trying to figure out how to update store when api returns changed data.
One of my Components is supposed to render "live" data when other users write data to api. What's the best approach? Poll async data on interval?
I'm using ReactJS/AltJS and right now i'm using jQuery for making async api calls in my Actions.
Thanks!
BookActions.js:
getBook(bookId) {
$.ajax({ url: '/api/books/' + bookId })
.done((data) => {
this.actions.getBookSuccess(data);
})
.fail((jqXhr) => {
this.actions.getBookFail(jqXhr);
});
}
BookStore.js
import alt from '../alt';
import BookActions from '../actions/BookActions';
class BookStore {
constructor() {
this.bindActions(BookActions);
this.books = [];
}
onGetBookSuccess(data) {
this.books = data;
}
onGetBookFail(errorMessage) {
toastr.error(errorMessage);
}
}
export default alt.createStore(BookStore);
First of all, you have to define what's 'live' data. After a user write some data to the server, how long can you wait until you know there's new data? If you want to be notified within 1 second, you also need to design your backend system carefully.
In your question, I assume that several seconds delay are tolerable. One of the simple solutions is polling. According to the react doc, you can create a timer in componentDidMount to invoke the API periodically, and clean up everything in componentWillUnmount.
Don't put the timer logic into the actions which may be shared by man components.