
My name is Vitaliy Rizo, I am a senior front-end developer at Amplifer. I’ll share how we use Logux in a web application: we organize real-time data exchange, error notifications without reloading the page, communication between browser tabs and integration with Redux.
Amplifer is a service for publishing on social networks. It was necessary to quickly and reliably notify users of errors without reloading the page: if suddenly it was not possible to process the image, the VKontakte API fell, or Facebook decided again not to post. Looking ahead, I’ll say that we also planned to use Logux to discuss publications and rewrite the cross-posting popup from RSS. But first, about why we were not satisfied with the available solutions.
Possible solutions: WebSocket, Firebase and Swarm.js
Usually, WebSocket is used to implement auto-update information. With it, you do not need to send requests for information every second, as you would have to do with traditional HTTP requests, and the header size will be small. However, WebSocket has disadvantages:
- . , , , . , : , ;
- , . ;
- , , ;
- , (CRDT), .
Firebase, , — . Firebase CRDT, Redux . CRDT Swarm.js, Redux , .
Logux
, — Logux. , Redux, CRDT . - , API-: , .
, , « ». :
imports-api.js
update (project, id, data) {
return put(project, `settings/imports/${ id }`, convert(data))
}
imports.js
onUpdate (projectId, importId, changed) {
return dispatch({
projectId,
importId,
import: changed,
type: 'amplifr/imports/update'
})
}
:
imports.js
let dispatch = useDispatch()
let onUpdate = (projectId, importId, changed) => dispatch.sync({
projectId,
importId,
import: changed,
type: 'amplifr/imports/update'
}, { channels: ['imports/' + projectId] })
sync
dispatch
, , , . , Logux .
Logux

— . , . , .
, Logux-. .
Logux Redux. Redux createStore
. Logux, «» createLoguxCreator
:
import { createLoguxCreator } from '@logux/redux'
let createStore = createLoguxCreator({
credentials: loguxToken,
subprotocol: '0.6.5',
userId,
server: 'wss://logux.amplifr.com'
})
let store = createStore(reducers)
store.client.start()
createLoguxCreator
:
- . , . gon;
- Logux-;
- . , , .
Logux- , . , - posts/1
, . , WebSocket.
? — , , posts/1
. subscribe
— :
import useSubscription from '@logux/redux/use-subscription'
let Notices = props => {
let isSubscribing = useSubscription([`projects/${ props.id }`])
if (isSubscribing) {
return <Loader />
} else {
}
}
— , HTTP- Redux- Logux, Logux- :
def schedule_logux
LoguxNotificationWorker.perform_async(
{ type: 'amplifr/notices/add', notice: Logux::NoticePresenter.new(notice).to_json },
channels: ["projects/#{project.id}"]
)
end
, . - , , . , :
import { useDispatch } from 'react-redux'
let dispatch = useDispatch()
let onNoticeHide = noticeId => dispatch.sync({
type: 'amplifr/notices/hide',
noticeId
}, {
channels: [`projects/${ projectId }`]
})
, , Logux:

-
Logux , . , - , . , . , , , .
Logux . «» , , localStorage. , Firefox Safari localStorage! Logux , , . , .
, , Logux , , . , , .
, Logux, .
Logux

, Logux, . — , :
let PostEditor = { isApprovable, postId, … } => {
let isSubscribing = useSubscription(isApprovable ? [`posts/${ postId }`] : [])
if (isSubscribing) {
return <Loader />
} else {
}
}
notes
:
import { useDispatch, useSelector } from 'react-redux'
let dispatch = useDispatch()
let notes = useSelector(notes.filter(note => note.postId === postId))
let onNoteAdd = note => dispatch.sync({
type: 'amplifr/notes/add',
note
}, {
channels: [`posts/${ postId }`]
})
. , :
export default function notes (state = [], action) {
if (action.type === 'amplifr/notes/add') {
return state.concat([action.note])
} else if (action.type === 'amplifr/posts/close') {
return state.filter(i => i.postId !== action.postId)
} else {
return state
}
}
. Logux, .

, @subscribe
. , isSubscribing: true
. , , . .
, . , squid 3, WebSocket ( Logux WebSocket). , , . — .
, Logux AJAX-. , «» «» Firefox.
AJAX Logux RSS. RSS- . RSS-, .

. :
import { useDispatch } from 'react-redux'
let dispatch = useDispatch()
let onCreate = (projectId, importId, import) => {
return dispatch.sync({
importId,
import,
type: 'amplifr/imports/add'
}, { channels: ['imports/' + projectId] })
}
let onUpdate = (projectId, importId, changed) => {
return dispatch.sync({
importId,
changed,
type: 'amplifr/imports/update'
}, { channels: ['imports/' + projectId] })
}
, , , Logux Optimitstic UI — . , , . - , .

«» .
«», , RSS- . , . Logux (dispatch.sync(…).catch(…)
), . -, , .
: catch()
JSON, try { JSON.parse(…) } catch { … }
. .
Logux ?
Logux WebSocket, , SPA . , . , , :
import status from '@logux/client/status'
let connected = false
status(store.client, state => {
if (state === 'synchronized') connected = true
}
setTimeout(() => {
if (!connected) {
sentry.track(new Error('No Logux connection for 10 minutes'))
}
}, 60 * 1000)
100 , . , - , :

, WebSocket: -, , , , , AdBlocker Kaspersky Protection. , , , Logux, .
Logux ,
Logux , . , , . RSS-, , . - , , , , .
, . — Logux. -. , , , , :
import log from '@logux/client/log'
let store = createStore(reducers)
log(store.client)
window.store = store
:
- RSS- ;
- , ;
- RSS-, ;
- ;
- !
:
window.store.client.log.store.created.filter(i => i[0].type === 'amplifr/popups/set')
, - : meta.tab
undefined
. , . , client.id
client.tabId
@logux/redux
id
tabId
. , , Logux , , .
, - «» Logux, :
Logux : «? , , ?». , — , , Redux. « », , . Logux .

:
- Logux, Redux, CRDT ;
- C Logux , ;
- Logux , ;
- Logux has its drawbacks: the system is not simple, there are still bugs and solutions are not always found quickly;
- In Amplifer, the advantages of Logux outweighed the cons. We plan to continue to use it when implementing suitable projects.

⌘⌘⌘
I hope Logux finds use in your project. If you have any questions, please write to me on Twitter or by mail .
Thanks to Alexander Marfitsin and Andrei Sitnik for their help in preparing this article.