refactor: state++

This commit is contained in:
cha0s 2020-07-26 17:31:36 -05:00
parent eb46f3a591
commit 785ab39bbd
7 changed files with 105 additions and 65 deletions

View File

@ -1,27 +1,14 @@
import merge from 'deepmerge'; import merge from 'deepmerge';
import {combineReducers} from 'redux'; import {routerMiddleware} from 'connected-react-router';
import {connectRouter, 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 createCommonStore from '~/common/store';
import {middleware as effectsMiddleware} from './effects'; import {middleware as effectsMiddleware} from './effects';
export default function createStore(options = {}) { export default function createStore(options = {}) {
const {history} = options; const {history} = options;
const reducer = combineReducers({
app,
chat,
history: createHistory(history),
router: connectRouter(history),
user,
usernames,
});
const store = createCommonStore( const store = createCommonStore(
merge( merge(
options, options,
@ -30,10 +17,10 @@ export default function createStore(options = {}) {
routerMiddleware(history), routerMiddleware(history),
effectsMiddleware, effectsMiddleware,
], ],
reducer, reducer: stateReducer(history),
}, },
), ),
); );
store.subscribe(storageSubscription(store)); store.subscribe(subscription(store));
return store; return store;
} }

View File

@ -25,12 +25,13 @@ export const rightIsOpenSelector = createSelector(
); );
const slice = createSlice({ const slice = createSlice({
name: 'app', name: 'reddichat/app',
initialState: storage(appSelector) || { initialState: {
leftActiveIndex: 0, leftActiveIndex: 0,
leftIsOpen: true, leftIsOpen: true,
rightActiveIndex: 0, rightActiveIndex: 0,
rightIsOpen: true, rightIsOpen: true,
...(storage(appSelector) || {}),
}, },
/* eslint-disable no-param-reassign */ /* eslint-disable no-param-reassign */
extraReducers: {}, extraReducers: {},

View File

@ -1,11 +1,10 @@
/* eslint-disable no-param-reassign */
import { import {
createSelector, createSelector,
createSlice, createSlice,
} from '@reduxjs/toolkit'; } from '@reduxjs/toolkit';
import hydration from './hydration'; import hydration from './hydration';
import storage from './storage'; import storage, {localStorage} from './storage';
export const chatSelector = (state) => state.chat; export const chatSelector = (state) => state.chat;
@ -45,7 +44,7 @@ export const channelMessagesSelector = createSelector(
); );
const slice = createSlice({ const slice = createSlice({
name: 'chat', name: 'reddichat/chat',
initialState: { initialState: {
channels: {}, channels: {},
input: {}, input: {},
@ -53,6 +52,10 @@ const slice = createSlice({
...(hydration('chat') || {}), ...(hydration('chat') || {}),
...(storage(chatSelector) || {}), ...(storage(chatSelector) || {}),
}, },
/* eslint-disable no-param-reassign */
extraReducers: {
[localStorage]: ({input}) => ({input}),
},
reducers: { reducers: {
addMessage: ({ addMessage: ({
channels, channels,
@ -117,6 +120,7 @@ const slice = createSlice({
submitLeave: () => {}, submitLeave: () => {},
submitMessage: () => {}, submitMessage: () => {},
}, },
/* eslint-enable no-param-reassign */
}); });
export const { export const {

27
src/common/state/index.js Normal file
View File

@ -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);

View File

@ -1,11 +1,22 @@
import throttle from 'lodash.throttle'; import throttle from 'lodash.throttle';
import {createAction} from '@reduxjs/toolkit';
export const storageSubscription = (store) => ( export const localStorage = createAction('reddichat/localStorage');
throttle(() => localStorage.setItem('redux-state', JSON.stringify(store.getState())), 1000)
export const storageSubscription = (store, reducer) => (
throttle(
() => (
window.localStorage.setItem(
'redux-state',
JSON.stringify(reducer(store.getState(), localStorage())),
)
),
1000,
)
); );
export default (selector) => { export default (selector) => {
const state = localStorage.getItem('redux-state'); const state = window.localStorage.getItem('redux-state');
if (!state) { if (!state) {
return undefined; return undefined;
} }

View File

@ -1,4 +1,5 @@
import {LOCATION_CHANGE} from 'connected-react-router'; import {LOCATION_CHANGE} from 'connected-react-router';
import merge from 'deepmerge';
import { import {
createSelector, createSelector,
createSlice, createSlice,
@ -7,6 +8,7 @@ import {
import {addMessage, join, leave} from './chat'; import {addMessage, join, leave} from './chat';
import hydration from './hydration'; import hydration from './hydration';
import storage, {localStorage} from './storage';
export const userSelector = (state) => state.user; export const userSelector = (state) => state.user;
@ -104,8 +106,9 @@ export const unreadForChannelSelector = createSelector(
); );
const slice = createSlice({ const slice = createSlice({
name: 'user', name: 'reddichat/user',
initialState: { initialState: merge.all([
{
blocked: [], blocked: [],
favorites: [], favorites: [],
focus: '', focus: '',
@ -115,9 +118,34 @@ const slice = createSlice({
recent: [], recent: [],
redditUsername: 'anonymous', redditUsername: 'anonymous',
unread: {}, unread: {},
...(hydration('user') || {isAnonymous: true}),
}, },
hydration('user') || {isAnonymous: true},
storage(userSelector) || {},
], {arrayMerge: (target, source) => Array.from(new Set(source.concat(target)).values())}),
/* eslint-disable no-param-reassign */ /* 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: { reducers: {
addFriendship: ({friendship}, {payload}) => { addFriendship: ({friendship}, {payload}) => {
const index = friendshipIndex(friendship, payload); const index = friendshipIndex(friendship, payload);
@ -156,28 +184,6 @@ const slice = createSlice({
blocked.splice(blocked.indexOf(payload), 1); 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 */ /* eslint-enable no-param-reassign */
}); });

View File

@ -9,6 +9,7 @@ import {
} from '~/common/state/chat'; } from '~/common/state/chat';
import hydration from './hydration'; import hydration from './hydration';
import storage from './storage';
export const usernamesSelector = (state) => state.usernames; export const usernamesSelector = (state) => state.usernames;
@ -17,10 +18,13 @@ export const usernameSelector = createSelector(
(usernames, id) => usernames[id], (usernames, id) => usernames[id],
); );
/* eslint-disable no-param-reassign */
const slice = createSlice({ const slice = createSlice({
name: 'usernames', name: 'reddichat/usernames',
initialState: hydration('usernames') || {}, initialState: {
...(hydration('usernames') || {}),
...(storage(usernamesSelector) || {}),
},
/* eslint-disable no-param-reassign */
extraReducers: { extraReducers: {
[join]: (state, {payload: {usernames}}) => ({...state, ...usernames}), [join]: (state, {payload: {usernames}}) => ({...state, ...usernames}),
[joined]: (state, {payload: {id, name}}) => ({...state, [id]: name}), [joined]: (state, {payload: {id, name}}) => ({...state, [id]: name}),
@ -28,8 +32,8 @@ const slice = createSlice({
reducers: { reducers: {
setUsernames: (state, {payload}) => ({...state, ...payload}), setUsernames: (state, {payload}) => ({...state, ...payload}),
}, },
/* eslint-enable no-param-reassign */
}); });
/* eslint-enable no-param-reassign */
export const { export const {
setUsernames, setUsernames,