phoenix-react v0.1.0
phoenix-react
The package offers state support for Phoenix Channels using the React's Context API. The only external dependency is Phoenix js.
It provides :
- Central state for your events
- Simple channel and events registration
- HOC for easy coupling with the Provider
Running the example
- Clone the repository
git clone https://github.com/nicklayb/phoenix-react- Install npm packages
npm install
// or
yarn install- Run development environment
npm run devGetting started
Install the dependency
Using npm
npm install --save phoenix-reactUsing yarn
yarn add phoenix-reactSetting up the context Provider
import React from 'react'
import ChannelProvider from 'phoenix-react'
import channels from './channels'
// Refers the initial state you want
const state = {
messages: [],
}
// Refering to the `opts` parameter for Pheonix.Socket instanciation
const params = {
params: {
token: localStorage.token,
},
}
export default () => (
<ChannelProvider
url="ws://localhost:4000/socket"
channels={channels}
state={state}
params={params}
>
<App />
</ChannelProvider>
)Writings channels handler
This channels prop of the provider is an array of channel you create with the createChannel helper. The first parameter is the topic to subscribe and the second one is a callback that will setup the socket.
Within the callback function, you receive the channel as first paramter and the provider value which has mutate, fire, state and gettersprops. I highly recommend creating mutats
import { createChannel } from 'phoenix-react'
// Messages mutators. These are helpers that will mutate the state withing a payload. Even though these are not required, they are recommended.
const messageMutators = {
add: (state, message) => ({
...state,
messages: [
...state.messages,
message
],
}),
set: (state, messages) => ({
...state,
messages: messages,
}),
}
// The message channel, the mutate function performs a state mutation of the provider which will re-render the consumer components.
const messageChannel = createChannel('room', (channel, { mutate }) => {
// You can add handlers for different events. This one with add a message to the state when the `new_msg` event is broadcasted by the Phoenix server
channel.on('new_msg', ({ body }) => {
mutate(state => messageMutators.add(state, body.message))
})
// Don't forget to join the channel. You can also chain `receive(message, body)` to it to handle different server return code. See Phoenix.js documentation for more information
channel.join()
.receive('ok', ({ body }) => {
mutate(state => messageMutators.set(state, body.messages))
})
// Return that channels to it can be stored into the provider
return channel
})
export default [
messageChannel,
]Coupling components to the provider
To bind state mutations to components, there is two way. Wither use the Consumer or the HOC. In either way, you will receive the following payload:
fire(channel, event, body): function that fires an event to the server on the desired channel.state: The current state of your provider which should not be mutated. Use the mutate function instead.getters: The getter object you passed to the provider. These are "quick access functions" to your state.mutate(state => state): function that directly mutates the state.leave(topice): function that leaves the given channel.
Using the Consumer
Simply wrap the location where you want to use the provider state by the consumer.
import React from 'react'
import ChannelsProvider from 'phoenix-react'
export default () => (
<div className="app">
<div className="message-list">
<ul>
<ChannelsProvider.Consumer>
{({ state }) => state.messages.map(message => (
<li>[{message.author}]: {message.text}</li>
))}
</ChannelsProvider.Consumer>
</ul>
</div>
</div>
)Using the High-order component (HOC)
Just wrap the Component while edefault exporting it. The advantage of the HOC is that you can pass a mapState function as second parameter to map the only thing want from the payload.
import React from 'react'
import { withChannels } from 'phoenix-react'
const MessageList = ({ messages }) => (
<ul>
{messages.map(message => (
<li>[{message.author}]: {message.text}</li>
))}
</ul>
)
const WrappedMessageList = withChannels(MessageList, ({ state }) => ({
messages: state.messages
}))
export default () => (
<div className="app">
<div className="message-list">
<WrappedMessageList />
</div>
</div>
)API
ChannelsProvider
state
object that contains the initial state of the context
{
messages: [],
}getters
object that contains fucntion that receives params and state
{
unread: (_, state) => state.messages.filter(message => !message.read),
}callbacks
object that has onSocketError and onSocketClose keys for callbacks on socket events. These events will receive the provider value.
{
onSocketError: (error, provider) => { /* */ }
onSocketClose: (provider) => { /* */ }
}url
string that represents the websocket/longpolling host like ws://localohst:4000/socket
params
object that is passed to the socket constructor. There is a nested params key that represents the params sent to the websocket server. Refer to the Phoenix js documentation.
{
params: {
token: 'Bearer ...',
},
}channels
array of channel created within the createChannel helper.
[
createChannel('room:lobby', (channel, { mutate }) => {
/* setup channel */
}),
createChannel('room:1', (channel, { mutate }) => {
/* setup channel */
}),
]timeout
number that represents the timeout duration in milliseconds like 5000
withChannels
Component
React component that you want to map the provider value.
export default withChannels(Component)The component now receives state, fire, getters, mutate and leave.
mapState
function that receive the provider value and return only what you want to attach to your component
export default withChannels(Component, ({ state, fire }) => ({
messages: state.messages,
fire,
}))The component now receives on messages and fire.
createChannel(topic, callback: (channel, providerValue))
function that receives a topic string and a callback. That callback receives channel which is the socket.channel instance and the provider value. The callback must return the channel and make sure to call join before returning.
const channel = createChannel('room:lobby', (channel, { mutate }) => {
channel.on('new_msg', ({ message }) = {
mutate(state => {
...state,
messages: [
...state,
message
],
})
})
channel.join()
return channel
})7 years ago