Redux CableCar is Redux middleware connecting Redux actions to Rails Action Cable. It uses Action Cable's websocket connection to automatically pass specific redux actions from the client to the server, and converts messages coming from the server into client-side redux actions.
yarn add redux-cablecar
Create cablecar route and middleware
import { createStore, applyMiddleware } from '@reduxjs/toolkit'
import { createCableCarRoute } from 'redux-cablecar'
const cableCarRoute = createCableCarRoute()
const cableCarMiddleware = cableCarRoute.createMiddleware()
Add middleware to list of redux middleware
const middlewares = [cableCarMiddleware]
const store = createStore(reducer, applyMiddleware(middlewares))
Initialize the cablecar to the redux store with the Rails ActionCable channel
const options = {
params: { room: 'game' },
permittedActions: ['SERVER', 'RAILS', /.+ALSO_TO_SERVER$/]
}
const cableCar = cableCarRoute.connect(store, 'MainChannel', options)
class MainChannel < ApplicationCable::Channel
def subscribed
stream_from "#{params[:room]}"
end
end
provider
- custom provider (not necessary)webSocketURL
- custom WS url (not necessary)
createCableCarRoute({
provider: myCustomProvider,
webSocketURL: 'ws://custom:8080'
})
Redux store object.
Name of the ActionCable channel (ie. 'ChatChannel').
params
- object sent to ActionCable channel (ie.params[:room]
)permittedActions
- string, RegExp, (string|RegExp)[], function - filters actions that get sent to the servermatchChannel
- boolean optional shortcut for using multiple channelssilent
- boolean creates one-way communication to Rails (filtered client actions get sent to the server, but no server messages will dispatch redux actions)
initialized
connected
disconnected
rejected
Actions must be permitted to be sent to Rails.
By default this is any action of with a type prefix RAILS
.
Example: { type: 'RAILS_ACTION' }
It can be customized with the permittedActions
option.
cableCarRoute.connect(store, 'channelName', { permittedActions: 'my_prefix/' })
This will match my_prefix/anyaction
.
cableCarRoute.connect(store, 'channelName', { permittedActions: /suffix$/ })
cableCarRoute.connect(store, 'channelName', { permittedActions: ['prefix', /orsuffix$/] })
cableCarRoute.connect(store, 'channelName', {
permittedActions: action => action.server === true
})
A shortcut for a use case with multiple channels
cableCarRoute.connect(store, 'channelOne', {
matchChannel: true
})
cableCarRoute.connect(store, 'channelTwo', {
matchChannel: true
})
This is the equivalent of writing:
cableCarRoute.connect(store, 'channelOne', {
permittedActions: (action) => action.meta.channel === 'channelOne'
})
cableCarRoute.connect(store, 'channelTwo', {
permittedActions: (action) => action.meta.channel === 'channelTwo'
})
The CableCar object has the following other functions:
Disconnects and destroys cablecar. This is useful if changing channels/params.
const cableCarRoute = createCableCarRoute()
const cableCar = cableCarRoute.connect(store, 'GameChannel', { params: { room: 1 }})
cableCar.destroy()
cableCarRoute.connect(store, 'GameChannel', { params: { room: 2 }})
Pauses the cablecar.
Resumes the cablecar.
Calls a Rails method directly. (See Rails documentation for more)
Example:
cableCar.perform('activate_something', { data: ... })
class ChatChannel < ApplicationCable::Channel
def subscribed
stream_from "chat"
end
def activate_something(payload)
...
end
end
(See ActionCable documentation for more)
Sends a direct message to Rails (outside of the Redux middleware chain)
Redux actions matching the permittedActions
criteria get sent to the Rails server.
However if isOptimistic: true
is in the action meta property, then the action will be sent to both the Rails Server, as well as being propagated thru the rest of the Redux middlewares. These actions are considered 'optimistic' updates, since when news comes back from the server it may conflict with changes that have already been made on the client.
Example:
{ type: 'RAILS_ACTION_ALSO_REDUX_SAME_TIME', meta: { isOptimistic: true }}
Dropped actions are permitted actions that cannot be sent with the ActionCable subscription, because the connection has not yet been initialized or connected, or has been disconnected.
Dropped actions are usually a sign of a timing issue that needs to be resolved, but if necessary a meta property isOptimisticOnFail
can be added to an action. These actions will be passed to redux only if dropped.
{ type: 'RAILS_SERVER_OR_REDUX_IF_DROPPED', meta: { isOptimisticOnFail: true }}
While unlikely scenarios, redux-cablecar
does support multiple channels, Redux stores, and even websocket connections.
Every Redux store should have a unique cable car route, with a unique middleware object created.
Only one consumer is maintained per unique webSocketURL, so separate routes may use the same webSocketURL.
Clone and run npm install
.
Link the package locally with npm link
and use npm run watch
to update package changes.
Pull requests welcome.
npm test
MIT