From 785ab39bbdcd6cbbaa1b6b546c7a948e85c0777d Mon Sep 17 00:00:00 2001 From: cha0s Date: Sun, 26 Jul 2020 17:31:36 -0500 Subject: [PATCH] refactor: state++ --- src/client/store/index.js | 23 +++-------- src/common/state/app.js | 5 ++- src/common/state/chat.js | 10 +++-- src/common/state/index.js | 27 +++++++++++++ src/common/state/storage.js | 17 ++++++-- src/common/state/user.js | 76 +++++++++++++++++++---------------- src/common/state/usernames.js | 12 ++++-- 7 files changed, 105 insertions(+), 65 deletions(-) create mode 100644 src/common/state/index.js diff --git a/src/client/store/index.js b/src/client/store/index.js index 635616f..6c10089 100644 --- a/src/client/store/index.js +++ b/src/client/store/index.js @@ -1,27 +1,14 @@ import merge from 'deepmerge'; -import {combineReducers} from 'redux'; -import {connectRouter, routerMiddleware} from 'connected-react-router'; +import {routerMiddleware} from 'connected-react-router'; + +import {stateReducer, subscription} from '~/common/state'; -import app from '~/common/state/app'; -import chat from '~/common/state/chat'; -import createHistory from '~/common/state/history'; -import {storageSubscription} from '~/common/state/storage'; -import user from '~/common/state/user'; -import usernames from '~/common/state/usernames'; import createCommonStore from '~/common/store'; import {middleware as effectsMiddleware} from './effects'; export default function createStore(options = {}) { const {history} = options; - const reducer = combineReducers({ - app, - chat, - history: createHistory(history), - router: connectRouter(history), - user, - usernames, - }); const store = createCommonStore( merge( options, @@ -30,10 +17,10 @@ export default function createStore(options = {}) { routerMiddleware(history), effectsMiddleware, ], - reducer, + reducer: stateReducer(history), }, ), ); - store.subscribe(storageSubscription(store)); + store.subscribe(subscription(store)); return store; } diff --git a/src/common/state/app.js b/src/common/state/app.js index 203e837..f70caff 100644 --- a/src/common/state/app.js +++ b/src/common/state/app.js @@ -25,12 +25,13 @@ export const rightIsOpenSelector = createSelector( ); const slice = createSlice({ - name: 'app', - initialState: storage(appSelector) || { + name: 'reddichat/app', + initialState: { leftActiveIndex: 0, leftIsOpen: true, rightActiveIndex: 0, rightIsOpen: true, + ...(storage(appSelector) || {}), }, /* eslint-disable no-param-reassign */ extraReducers: {}, diff --git a/src/common/state/chat.js b/src/common/state/chat.js index 4dcbb32..5627545 100644 --- a/src/common/state/chat.js +++ b/src/common/state/chat.js @@ -1,11 +1,10 @@ -/* eslint-disable no-param-reassign */ import { createSelector, createSlice, } from '@reduxjs/toolkit'; import hydration from './hydration'; -import storage from './storage'; +import storage, {localStorage} from './storage'; export const chatSelector = (state) => state.chat; @@ -45,7 +44,7 @@ export const channelMessagesSelector = createSelector( ); const slice = createSlice({ - name: 'chat', + name: 'reddichat/chat', initialState: { channels: {}, input: {}, @@ -53,6 +52,10 @@ const slice = createSlice({ ...(hydration('chat') || {}), ...(storage(chatSelector) || {}), }, + /* eslint-disable no-param-reassign */ + extraReducers: { + [localStorage]: ({input}) => ({input}), + }, reducers: { addMessage: ({ channels, @@ -117,6 +120,7 @@ const slice = createSlice({ submitLeave: () => {}, submitMessage: () => {}, }, + /* eslint-enable no-param-reassign */ }); export const { diff --git a/src/common/state/index.js b/src/common/state/index.js new file mode 100644 index 0000000..69eb4cb --- /dev/null +++ b/src/common/state/index.js @@ -0,0 +1,27 @@ +import {connectRouter} from 'connected-react-router'; +import {combineReducers} from 'redux'; + +import app from './app'; +import chat from './chat'; +import createHistory from './history'; +import {storageSubscription} from './storage'; +import user from './user'; +import usernames from './usernames'; + +export const stateReducer = (history) => combineReducers({ + app, + chat, + history: createHistory(history), + router: connectRouter(history), + user, + usernames, +}); + +const subscriptionReducer = combineReducers({ + app, + chat, + user, + usernames, +}); + +export const subscription = (store) => storageSubscription(store, subscriptionReducer); diff --git a/src/common/state/storage.js b/src/common/state/storage.js index c375d03..04cd315 100644 --- a/src/common/state/storage.js +++ b/src/common/state/storage.js @@ -1,11 +1,22 @@ import throttle from 'lodash.throttle'; +import {createAction} from '@reduxjs/toolkit'; -export const storageSubscription = (store) => ( - throttle(() => localStorage.setItem('redux-state', JSON.stringify(store.getState())), 1000) +export const localStorage = createAction('reddichat/localStorage'); + +export const storageSubscription = (store, reducer) => ( + throttle( + () => ( + window.localStorage.setItem( + 'redux-state', + JSON.stringify(reducer(store.getState(), localStorage())), + ) + ), + 1000, + ) ); export default (selector) => { - const state = localStorage.getItem('redux-state'); + const state = window.localStorage.getItem('redux-state'); if (!state) { return undefined; } diff --git a/src/common/state/user.js b/src/common/state/user.js index ff02530..a6dc656 100644 --- a/src/common/state/user.js +++ b/src/common/state/user.js @@ -1,4 +1,5 @@ import {LOCATION_CHANGE} from 'connected-react-router'; +import merge from 'deepmerge'; import { createSelector, createSlice, @@ -7,6 +8,7 @@ import { import {addMessage, join, leave} from './chat'; import hydration from './hydration'; +import storage, {localStorage} from './storage'; export const userSelector = (state) => state.user; @@ -104,20 +106,46 @@ export const unreadForChannelSelector = createSelector( ); const slice = createSlice({ - name: 'user', - initialState: { - blocked: [], - favorites: [], - focus: '', - friendship: [], - id: 0, - isAnonymous: false, - recent: [], - redditUsername: 'anonymous', - unread: {}, - ...(hydration('user') || {isAnonymous: true}), - }, + name: 'reddichat/user', + initialState: merge.all([ + { + blocked: [], + favorites: [], + focus: '', + friendship: [], + id: 0, + isAnonymous: false, + recent: [], + redditUsername: 'anonymous', + unread: {}, + }, + hydration('user') || {isAnonymous: true}, + storage(userSelector) || {}, + ], {arrayMerge: (target, source) => Array.from(new Set(source.concat(target)).values())}), /* eslint-disable no-param-reassign */ + extraReducers: { + [addMessage]: ({focus, unread}, {payload: {channel}}) => { + if (focus !== channel) { + unread[channel] = (unread[channel] || 0) + 1; + } + }, + [join]: ({recent}, {payload: {channel}}) => { + if (-1 === recent.indexOf(channel)) { + recent.push(channel.substr(3)); + } + }, + [leave]: ({unread}, {payload: {channel}}) => { + delete unread[channel]; + }, + [localStorage]: ({recent}) => ({recent}), + [LOCATION_CHANGE]: (state, {payload: {location: {pathname}}}) => { + const {unread} = state; + state.focus = pathname.match(/^\/chat\//) ? pathname.substr('/chat'.length) : ''; + if (unread[state.focus]) { + delete unread[state.focus]; + } + }, + }, reducers: { addFriendship: ({friendship}, {payload}) => { const index = friendshipIndex(friendship, payload); @@ -156,28 +184,6 @@ const slice = createSlice({ blocked.splice(blocked.indexOf(payload), 1); }, }, - extraReducers: { - [addMessage]: ({focus, unread}, {payload: {channel}}) => { - if (focus !== channel) { - unread[channel] = (unread[channel] || 0) + 1; - } - }, - [join]: ({recent}, {payload: {channel}}) => { - if (-1 === recent.indexOf(channel) && channel.charCodeAt(1) === 'r'.charCodeAt(0)) { - recent.push(channel.substr(3)); - } - }, - [leave]: ({unread}, {payload: {channel}}) => { - delete unread[channel]; - }, - [LOCATION_CHANGE]: (state, {payload: {location: {pathname}}}) => { - const {unread} = state; - state.focus = pathname.match(/^\/chat\//) ? pathname.substr('/chat'.length) : ''; - if (unread[state.focus]) { - delete unread[state.focus]; - } - }, - }, /* eslint-enable no-param-reassign */ }); diff --git a/src/common/state/usernames.js b/src/common/state/usernames.js index 70576a3..f3aa801 100644 --- a/src/common/state/usernames.js +++ b/src/common/state/usernames.js @@ -9,6 +9,7 @@ import { } from '~/common/state/chat'; import hydration from './hydration'; +import storage from './storage'; export const usernamesSelector = (state) => state.usernames; @@ -17,10 +18,13 @@ export const usernameSelector = createSelector( (usernames, id) => usernames[id], ); -/* eslint-disable no-param-reassign */ const slice = createSlice({ - name: 'usernames', - initialState: hydration('usernames') || {}, + name: 'reddichat/usernames', + initialState: { + ...(hydration('usernames') || {}), + ...(storage(usernamesSelector) || {}), + }, + /* eslint-disable no-param-reassign */ extraReducers: { [join]: (state, {payload: {usernames}}) => ({...state, ...usernames}), [joined]: (state, {payload: {id, name}}) => ({...state, [id]: name}), @@ -28,8 +32,8 @@ const slice = createSlice({ reducers: { setUsernames: (state, {payload}) => ({...state, ...payload}), }, + /* eslint-enable no-param-reassign */ }); -/* eslint-enable no-param-reassign */ export const { setUsernames,