We created Logux with thoughts that good UX means that user has the same state in every browser tab. If the user added a product to Shopping Cart in one browser tab, it would see the same product in another tab.
To achieve that UX on client-side Logux separated all actions into two categories:
- Cross-tab actions: any changes in the global state. If the user changes a document or adds a comment, it should be a cross-tab action.
- Tab-specific action: everything related to the current context. For instance, open or close menu, start an animation, etc.
Logux will synchronize all cross-tab actions between all opened tabs.
In Redux client dispatch()
and dispatch.local()
create tab-specific actions:
// This action will be seen only in current tab
dispatch({ type: 'menu/open' })
dispatch.sync()
creates cross-tab action and send it to the server. dispatch.crossTab()
creates cross-tab action without sending it to the server.
// All tabs will receive this action
dispatch.crossTab({ type: 'notification/close' })
In Vuex client commit()
and commit.local()
create tab-specific actions:
// This action will be seen only in current tab
store.commit({ type: 'menu/open' })
commit.sync()
creates cross-tab action and send it to the server. commit.crossTab()
creates cross-tab action without sending it to the server.
// All tabs will receive this action
store.commit.crossTab({ type: 'notification/close' })
In pure JS Logux Client all actions are cross-tab by default.
// All tabs will receive this action
client.log.add({ type: 'notification/close' })
You need to set meta.tab
with client.id
to create tab-specific action:
// Only this client (this tab) will receive this action
client.log.add({ type: 'menu/open' }, { tab: client.id })
New Tab
Note that if a user opens a new tab, Logux will not load action from another tab. A new tab will load the latest state only in two cases:
- You use persistent log store on the client-side like
IndexedStore
. - The new tab loads the latest state from the server with subscriptions.
Server Actions
By default, all actions that came from a server are cross-tab action. All tabs will receive actions from the server, even if only a single tab subscribed to them.
All actions, which the client sends to the server, is cross-tab actions too.
// All tabs will receive this action
dispatch.sync({ type: 'users/rename', id, name })
We recommend you to create reducers with thinking about it. For instance, the reducer should ignore the users/rename
action if there is no user in the tab’s state.
export default function reduceUsers(state = { }, action) {
if (action.type === 'users/rename') {
const user = state[action.id]
if (user) {
return { ...state, [action.id]: { ...user, name: action.name } }
} else {
return state
}
}
}
// All tabs will receive this action
store.commit.sync({ type: 'users/rename', id, name })
We recommend you to create mutations with thinking about it. For instance, the mutation should ignore the users/rename
action if there is no user in the tab’s state.
export default {
…
'users/rename': (state, action) => {
const user = state.users[action.id]
if (user) {
state.users = { ...state.users, [action.id]: { ...user, name: action.name } }
}
}
}
// All tabs will receive this action
client.log.add({ type: 'users/rename', id, name }, { sync: true })
We recommend you to create reducers with thinking about it. For instance, the reducer should ignore the users/rename
action if there is no user in the tab’s state.
export default function reduceUsers(state = { }, action) {
if (action.type === 'users/rename') {
const user = state[action.id]
if (user) {
return { ...state, [action.id]: { ...user, name: action.name } }
} else {
return state
}
}
}