Why I created
Andrey Sitnik, Evil Martians
![](https://s3.amazonaws.com/media-p.slid.es/uploads/467124/images/2583932/Iv5oal8.png)
![](https://s3.amazonaws.com/media-p.slid.es/uploads/467124/images/3325758/logux-text.png)
@sitnikcode
![](https://s3.amazonaws.com/media-p.slid.es/uploads/467124/images/2369968/gett.png)
product development consultancy
![](https://s3.amazonaws.com/media-p.slid.es/uploads/467124/images/6965915/upload-5ea9af60-7add-11e8-9304-a7ced6737faa.png)
I created at Evil Martians
Autoprefixer
PostCSS
Browserslist
@sitnikcode
Autoprefixer was started in 2013
![](https://s3.amazonaws.com/media-p.slid.es/uploads/467124/images/5124147/o7wb457.png)
@sitnikcode
Now Autoprefixer and PostCSS grew up
![](https://s3.amazonaws.com/media-p.slid.es/uploads/467124/images/5269911/yPr28kM.png)
@sitnikcode
![](https://s3.amazonaws.com/media-p.slid.es/uploads/467124/images/5124225/003.jpg)
Image: CLM BBDO Paris
They don’t need new features
@sitnikcode
It is a time for а new adventure
![](https://s3.amazonaws.com/media-p.slid.es/uploads/467124/images/5127757/maxresdefault.jpg)
Image: Fallout 1
Part 1. The problem.
![](https://s3.amazonaws.com/media-p.slid.es/uploads/467124/images/4224153/1945-life-36-hour-war-2.jpg)
Image: Invasion Base on the Moon (Apr, 1948)
The 36-Hour War: Life Magazine, 1945
Front-end and back-end are great
@sitnikcode
Back-end
Front-end
RoR, Django
Rust, Go, Node.js
Serverless
Svelte
The problem is hidden between them
@sitnikcode
Back-end
Front-end
Communication
AJAX is enouph, isn’t it?
@sitnikcode
fetch('/comments.json')
.then(response => response.json())
.then(list => {
setComments(list)
})
Simple AJAX works only on localhost
@sitnikcode
![](https://s3.amazonaws.com/media-p.slid.es/uploads/467124/images/3575227/mPV8Lo8.png)
Image author: Nikita Prokopov
AJAX wasn’t created for the real Internet
@sitnikcode
Image author: Nikita Prokopov
![](https://s3.amazonaws.com/media-p.slid.es/uploads/467124/images/3575245/P0Y2RR6.png)
A lot of code even for simple edge cases
@sitnikcode
+ showLoader()
fetch('/comments.json')
.then(response => response.json())
.then(list => {
+ hideLoader()
setComments(list)
})
+ .catch(err => {
+ hideLoader()
+ if (isNoNetwork(err)) {
+ showNetworkError(err)
+ } else {
+ showServerError(err)
+ }
+ })
Developers are lazy to think about edge cases
and it is OK
@sitnikcode
![](https://s3.amazonaws.com/media-p.slid.es/uploads/467124/images/4219187/512x512.png3b853521-e78a-43a5-b4e3-aac827518645Large.jpg)
Bad UX on real Internet is everywhere
@sitnikcode
There is no lazy developers,
there is only bad DX
Part 2. The dream.
![](https://s3.amazonaws.com/media-p.slid.es/uploads/467124/images/4222810/430583main_mccall01_full.jpg)
Image: Robert McCall
Let’s forget about AJAX…
fetch('/comments.json')
fetch('/comments.json')
fetch('/comments.json')
@sitnikcode
and dream about the perfect state sync for SPA
![](https://s3.amazonaws.com/media-p.slid.es/uploads/467124/images/3318614/posts___amplifr_2016-12-08_14-51-51.png)
![](https://s3.amazonaws.com/media-p.slid.es/uploads/467124/images/4222364/Screenshot_2015-07-19_15.17.01.png)
![](https://s3.amazonaws.com/media-p.slid.es/uploads/467124/images/4222369/screen-shot.png)
@sitnikcode
We have stores in our apps
import { comments } from './stores.js'
function onLike (commentId) {
comments.update(state => {
state[commentId].like = !state[commentId].like
})
}
@sitnikcode
With extra code to sync state with server 😒
import { comments } from './stores.js'
async function onLike (commentId) {
loading = true
try {
await fetch(`/like?comment=${ commentId }`)
} finally {
loading = false
}
comments.update(state => {
state[commentId].like = !state[commentId].like
})
}
@sitnikcode
Feature 1: sync state without extra code
comments[10].text = 'A'
+----+------+
| id | text |
+----+------+
| 10 | A |
+----+------+
@sitnikcode
Feature 2: sync state between tabs by default
![](https://s3.amazonaws.com/media-p.slid.es/uploads/467124/images/3582060/tabs.png)
3
3
@sitnikcode
Feature 3: built-in authorization
@sitnikcode
{
userId: 380,
token: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMj' +
'M0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2M' +
'jM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c'
}
Feature 3a: built-in client version
@sitnikcode
API
Front-end
Client
Version 1
Version 1
Version 1
Developer
Load
Deploy
Version 2
Version 2
API call
API call
Version 1
Version 1
Version 2
Feature 4: Optimistic UI
![](https://s3.amazonaws.com/media-p.slid.es/uploads/467124/images/4219187/512x512.png3b853521-e78a-43a5-b4e3-aac827518645Large.jpg)
Continue to work
Save
![](https://s3.amazonaws.com/media-p.slid.es/uploads/467124/images/4221837/bouncer1.jpg)
![](https://s3.amazonaws.com/media-p.slid.es/uploads/467124/images/4221839/bouncer2.jpg)
Continue to work
Save
@sitnikcode
You need to be pessimistic
to make good Optimistic UI
Offline: keep changes,
show warning in global UI
Server error: revert changes,
show error in global UI
@sitnikcode
Feature 4a: global UI for network errors
@sitnikcode
![](https://s3.amazonaws.com/media-p.slid.es/uploads/467124/images/3321815/screen_shot_2016-12-09_at_09.55.50_1024.png)
Feature 4b: Offline
@sitnikcode
![](https://s3.amazonaws.com/media-p.slid.es/uploads/467124/images/4219312/_______.jpg)
![](https://s3.amazonaws.com/media-p.slid.es/uploads/467124/images/7134115/2015-07-23-mr-robot.jpg)
Expectations
Reality
no stable Internet in subway
Feature 5: Collaborative First Apps
@sitnikcode
![](https://s3.amazonaws.com/media-p.slid.es/uploads/467124/images/7134162/collaboration-team-photo.jpg)
Even single user apps have multiple “users”
@sitnikcode
![](https://s3.amazonaws.com/media-p.slid.es/uploads/467124/images/4222344/responsive-graphic.png)
1
2
@sitnikcode
Feature 5a: Real-Time Updates
changes
Feature 5b: The same changes order
@sitnikcode
Rename to A
Final: B
Rename to B
Rename to B
Final: A
Rename to A
![](https://s3.amazonaws.com/media-p.slid.es/uploads/467124/images/4222790/1-TV6vEgojNX8QFufqzwozeA.jpeg)
Part 3. Implementations.
My dream
@sitnikcode
- Sync global state without extra code
- Cross-tab sync out of the box
- Authorization and client versions
-
Optimistic UI
- Global UI for errors
- Offline First
-
Collaborative First
- Real-Time
- Conflicts Resolution
Current solutions
Sync state
Cross-tab
Auth & versions
Optimistic UI
Collaborative
GraphQL
Firebase
Automerge
@sitnikcode
We often forget about these important criterias
@sitnikcode
Size
Vendor lock-in
GraphQL
Firebase
Automerge
219 KB
60 KB
25 KB
![](https://s3.amazonaws.com/media-p.slid.es/uploads/467124/images/4222812/Soyuz_Spacecraft_in_Orbit_-_GPN-2002-000155.jpg)
Part 4. The reality.
My own solution for client-server sync
@sitnikcode
Proxy server for Web Sockets
@sitnikcode
WebSocket
HTTP
Your back-end
Redux API on client-side
- import { createStore } from 'redux'
+ import createLoguxCreator from '@logux/redux/create-logux-creator'
+ const createStore = createLoguxCreator({
+ subprotocol: '1.0.0',
+ server: 'wss://logux.example.com',
+ userId: localStorage.userId,
+ credentials: localStorage.userToken
+ })
const store = createStore(reducer, preloadedState, enhancer)
+ store.client.start()
only 12 KB
@sitnikcode
MobX and svelte/store APIs are coming
![](https://s3.amazonaws.com/media-p.slid.es/uploads/467124/images/7134302/Screenshot_from_2020-03-04_01-19-58.png)
@sitnikcode
Auth and client versioning
const createStore = createLoguxCreator({
server: url,
subprotocol: '1.0.0',
userId: localStorage.userId,
credentials: localStorage.userToken
})
@sitnikcode
Sync state with server and tabs
- store.dispatch({
+ store.dispatch.sync({
type: 'users/add',
user
})
@sitnikcode
Optimistic and Pessimistic UI
// Optimistic UI
store.dispatch.sync(addUser)
// Pessimistic UI
loader = true
await store.dispatch.sync(addUser)
loader = false
@sitnikcode
Changes reverting and global UI for errors
import badge from '@logux/client/badge'
![](https://s3.amazonaws.com/media-p.slid.es/uploads/467124/images/3321815/screen_shot_2016-12-09_at_09.55.50_1024.png)
@sitnikcode
Logux Proxy re-send changes to clients
@sitnikcode
WebSocket
HTTP
WebSocket
Changes have time mark to keep order
@sitnikcode
store.log.on('action', (action, meta) => {
// Distributed time
meta.id //=> [1489328408677, '10:bfj58s0w:au68-dd', 0]
// Local time on this node
meta.time //=> 1489328404561
})
@sitnikcode
![](https://s3.amazonaws.com/media-p.slid.es/uploads/467124/images/4235335/557331main_iss027e036710_full.jpg)
Part 5. Future
Logux solves my dreams
@sitnikcode
Sync state
Cross-tab
Auth & versions
Optimistic UI
Collaborative
Logux
Questions?
Why I created Logux
By Andrey Sitnik
Why I created Logux
- 2,808