-
-
Save markerikson/3df1cf5abbac57820a20059287b4be58 to your computer and use it in GitHub Desktop.
const createMySocketMiddleware = (url) => { | |
return storeAPI => { | |
let socket = createMyWebsocket(url); | |
socket.on("message", (message) => { | |
storeAPI.dispatch({ | |
type : "SOCKET_MESSAGE_RECEIVED", | |
payload : message | |
}); | |
}); | |
return next => action => { | |
if(action.type == "SEND_WEBSOCKET_MESSAGE") { | |
socket.send(action.payload); | |
return; | |
} | |
return next(action); | |
} | |
} | |
} | |
// later, in your app | |
function sendSocketMessage(message) { | |
return { | |
type : "SEND_WEBSOCKET_MESSAGE", | |
payload : message | |
} | |
} | |
class MyComponent extends React.Component { | |
handleClick = () => { | |
this.props.sendSocketMessage("This goes to the server"); | |
} | |
} | |
export default connect(null, {sendSocketMessage})(MyComponent) |
Can you explain why you'd rather create a web socket connection as a middleware rather than capturing it in a side-effect handler?
That avoid socket connection affect by component life cycle
Hey, thanks for this example, it helped me a lot. However, sometimes you can get error cause message cannot be send during socket connection. To avoid this I add folowing function:
function waitForSocketConnection(socket, callback) {
setTimeout(
function () {
if (socket.readyState === 1) {
if (callback != null) {
callback();
}
} else {
console.log("waiting for socket connection...")
waitForSocketConnection(socket, callback);
}
}, 100)
}
and then I replaced line 14
of you code with:
waitForSocketConnection(socket, () => {
socket.send(action.payload);
})
Thanks to this approach we are not blocking redux
, however if anyone wants to wait for msg, then we can await waitForSocketConnection(socket, () => {...})
@ulou do you have any reference project/ working example for the given approach seems good option for handling socket errors.
@markerikson Would you elaborate more on how this middleware is being applied to the store and used in the app?
I'm trying to implement this in Redux Toolkit and am having a difficult time coming up with an effective solution.
@n-ii-ma : not sure I understand the question.
Setup-wise this would be added to the middleware
option in configureStore
as usual.
Can you clarify what you're asking?
@markerikson: Thank you for your response Mark. Well, I've used your example here and on Stackoverflow as a base to configure the Socket middleware
with Socket IO
and Laravel Echo
in this way and just as the docs recommends I use .concat
to append the middleware to the store:
(Each section is in its own file)
// Actions
// Get data
export const getData = createAction('socket/getData', data => {
return {
payload: {
data,
id: nanoid(),
createdAt: new Date().toISOString(),
},
};
});
// Send data
export const sendData = createAction('socket/sendData', data => {
return {
payload: {
data,
id: nanoid(),
createdAt: new Date().toISOString(),
},
};
});
// Disconnect
export const disconnect = createAction('socket/disconnect');
// Socket middleware
export const createSocketMiddleware = url => {
let socket;
let echo;
return storeAPI => next => action => {
switch (action.type) {
// Connect after user is authenticated
case 'user/login/fulfilled': {
socket = socketIOClient(`${url}:3010/socket`);
// Create a new Echo class instance
echo = new Echo({
host: `${url}:6001`,
broadcaster: 'socket.io',
client: socketIOClient,
auth: {
headers: {
Authorization: axios.defaults.headers['Authorization'],
},
},
});
// Connect and listen
echo.private('transport.1').listen('.order-pending', ev => {
storeAPI.dispatch(getData(ev));
});
break;
}
// Send data
case 'socket/sendData': {
socket.emit('order-pending', action.payload);
break;
}
// Disconnect
case 'socket/disconnect': {
echo.disconnect();
break;
}
}
return next(action);
};
};
// Reducer
const socketReducer = createReducer(initialState, builder => {
builder
.addCase(getData, (state, action) => {
state.data = action.payload.data;
})
.addCase(disconnect, (state, action) => {
return initialState;
});
});
// Store
export const store = configureStore({
reducer: persistedReducer,
middleware: getDefaultMiddleware =>
getDefaultMiddleware({
serializableCheck: {
ignoredActions: [FLUSH, REHYDRATE, PAUSE, PERSIST, PURGE, REGISTER],
},
}).concat(createSocketMiddleware(url)),
devTools: process.env.NODE_ENV === 'development' && true,
});
Everything is working as expected yet I myself am a bit cautious regarding this method due to the lack of adequate examples and am wondering if it indeed adheres to the Redux standards.
One more question I was wondering about is why is redux requiring the WebSocket connection to be as a middleware and how is RTK Query handling this issue with its Streaming Updates that plain Redux Toolkit is unable to do.
@n-ii-ma looks fine at first glance.
You might want to look at the FAQ entry that describes why we normally recommend that websocket-type connections should go into a middleware:
@markerikson Many thanks Mike!
Upon further consideration and after a conversation with Lenz, I decided to use the listenerMiddleware
, yet I'm still trying to figure out how to stop a particular listener
@n-ii-ma probably best to ask over in #redux
in Reactiflux, but typically you'd dispatch an action and have the listener do an await take(thatAction)
and then cancel itself / return.
Can you explain why you'd rather create a web socket connection as a middleware rather than capturing it in a side-effect handler?