@kesha-antonov/react-native-action-cable
v2.0.0
Published
Connect React Native apps to Rails ActionCable for real-time bidirectional communication.
Downloads
8,362
Maintainers
Readme
✨ Features
- 🔌 WebSocket Connection - Automatic connection management with reconnection support
- 📡 Channel Subscriptions - Subscribe to multiple ActionCable channels
- 🔄 Auto-Reconnect - Automatically reconnects when connection is lost
- 🔐 Custom Headers - Support for authentication and dynamic headers
- 📱 React Native Ready - Works without
windowobject polyfills - 🛡️ Connection Reuse - Prevent duplicate connections during hot reloads
- ⚡ TypeScript - Full TypeScript support included
📖 Table of Contents
- ✨ Features
- 📖 Table of Contents
- 📦 Installation
- 🚀 Quick Start
- 📚 API Reference
- ⚙️ Advanced Usage
- 🧪 Testing
- 📂 Examples
- 🤝 Contributing
- 👏 Credits
- 📄 License
📦 Installation
Yarn
yarn add @kesha-antonov/react-native-action-cablenpm
npm install @kesha-antonov/react-native-action-cable🚀 Quick Start
1. Create a consumer
import { ActionCable, Cable } from '@kesha-antonov/react-native-action-cable'
const actionCable = ActionCable.createConsumer('ws://localhost:3000/cable')
const cable = new Cable({})2. Subscribe to a channel
const channel = cable.setChannel(
'ChatChannel',
actionCable.subscriptions.create({
channel: 'ChatChannel',
roomId: 1
})
)
channel
.on('received', (data) => console.log('Received:', data))
.on('connected', () => console.log('Connected!'))
.on('disconnected', () => console.log('Disconnected'))3. Send messages
channel.perform('send_message', { text: 'Hello!' })4. Cleanup
channel.unsubscribe()📚 API Reference
ActionCable
| Method | Description |
|--------|-------------|
| createConsumer(url, headers?) | Create a new consumer and connect |
| getOrCreateConsumer(url, headers?) | Reuse existing consumer or create new one |
| disconnectConsumer(url) | Disconnect and remove consumer from cache |
| startDebugging() | Enable debug logging |
| stopDebugging() | Disable debug logging |
Consumer Instance
| Method | Description |
|--------|-------------|
| subscriptions.create(params) | Create a channel subscription |
| connection.isOpen() | Check if connected |
| connection.isActive() | Check if connected or connecting |
| disconnect() | Disconnect from server |
Cable
| Method | Description |
|--------|-------------|
| setChannel(name, subscription) | Register a channel |
| channel(name) | Get channel by name |
Channel
| Method | Description |
|--------|-------------|
| on(event, callback) | Subscribe to events: received, connected, disconnected, rejected, error |
| removeListener(event, callback) | Remove event listener |
| perform(action, data) | Send message to server |
| unsubscribe() | Unsubscribe from channel |
⚙️ Advanced Usage
// Static headers
const actionCable = ActionCable.createConsumer('ws://localhost:3000/cable', {
'Authorization': 'Bearer token123'
})
// Dynamic headers (re-evaluated on each connection)
const actionCable = ActionCable.createConsumer('ws://localhost:3000/cable', () => ({
'Authorization': `Bearer ${getAuthToken()}`
}))Use getOrCreateConsumer to prevent duplicate connections during hot reloads:
// ❌ Creates new connection every time
const actionCable = ActionCable.createConsumer('ws://localhost:3000/cable')
// ✅ Reuses existing connection
const actionCable = ActionCable.getOrCreateConsumer('ws://localhost:3000/cable')channel.on('error', (error) => {
console.log('Connection error:', error)
// Handle: no internet, wrong URL, server down, auth failure
})function useActionCable(channelName: string, params: Record<string, unknown>) {
const [connected, setConnected] = useState(false)
useEffect(() => {
const channel = cable.setChannel(
channelName,
actionCable.subscriptions.create({ channel: channelName, ...params })
)
channel
.on('connected', () => setConnected(true))
.on('disconnected', () => setConnected(false))
.on('received', handleReceived)
return () => {
channel.removeListener('received', handleReceived)
channel.unsubscribe()
delete cable.channels[channelName]
}
}, [channelName])
return { connected, channel: cable.channel(channelName) }
}Messages with data.action attribute are emitted as separate events:
# Rails sends:
{ action: 'speak', text: 'hello!' }// React Native receives:
channel.on('speak', (data) => {
console.log(data.text) // 'hello!'
})🧪 Testing
Jest Mock
jest.mock('@kesha-antonov/react-native-action-cable', () => ({
ActionCable: {
createConsumer: jest.fn(() => ({
subscriptions: {
create: jest.fn(() => ({
on: jest.fn().mockReturnThis(),
removeListener: jest.fn().mockReturnThis(),
perform: jest.fn(),
unsubscribe: jest.fn(),
})),
},
connection: {
isActive: jest.fn(() => true),
isOpen: jest.fn(() => true),
},
disconnect: jest.fn(),
})),
},
Cable: jest.fn(() => ({
channels: {},
channel: jest.fn(),
setChannel: jest.fn(),
})),
}))See examples/testing for complete testing examples.
📂 Examples
| Example | Description | |---------|-------------| | Complete Chat App | Full Rails backend + React Native frontend | | Apollo GraphQL | ActionCable with GraphQL subscriptions | | Testing | Jest mocks and testing patterns |
🤝 Contributing
- Fork the repository
- Create your feature branch (
git checkout -b feature/amazing-feature) - Commit your changes (
git commit -m 'Add amazing feature') - Push to the branch (
git push origin feature/amazing-feature) - Open a Pull Request
👏 Credits
Based on action-cable-react. Code in lib/action_cable is adapted from Rails ActionCable.
Please note that this project is maintained in free time. If you find it helpful, please consider becoming a sponsor.
