refactor: Make all reducers sync (#4125)

This commit is contained in:
Sorin Davidoi 2017-07-09 12:16:08 +02:00 committed by Eugen Rochko
parent f68fa930ea
commit 37c832cdf7
8 changed files with 18 additions and 90 deletions

View file

@ -16,10 +16,3 @@ export function hydrateStore(rawState) {
state, state,
}; };
}; };
export function hydrateStoreLazy(name, state) {
return {
type: `${STORE_HYDRATE_LAZY}-${name}`,
state,
};
};

View file

@ -23,8 +23,7 @@ const { localeData, messages } = getLocale();
addLocaleData(localeData); addLocaleData(localeData);
export const store = configureStore(); export const store = configureStore();
const initialState = JSON.parse(document.getElementById('initial-state').textContent); const hydrateAction = hydrateStore(JSON.parse(document.getElementById('initial-state').textContent));
export const hydrateAction = hydrateStore(initialState);
store.dispatch(hydrateAction); store.dispatch(hydrateAction);
export default class Mastodon extends React.PureComponent { export default class Mastodon extends React.PureComponent {

View file

@ -1,40 +1,13 @@
import { store } from '../../../containers/mastodon';
import { refreshNotifications } from '../../../actions/notifications';
import { injectAsyncReducer } from '../../../store/configureStore';
// NOTE: When lazy-loading reducers, make sure to add them
// to application.html.haml (if the component is preloaded there)
export function EmojiPicker () { export function EmojiPicker () {
return import(/* webpackChunkName: "emojione_picker" */'emojione-picker'); return import(/* webpackChunkName: "emojione_picker" */'emojione-picker');
} }
export function Compose () { export function Compose () {
return Promise.all([ return import(/* webpackChunkName: "features/compose" */'../../compose');
import(/* webpackChunkName: "features/compose" */'../../compose'),
import(/* webpackChunkName: "reducers/compose" */'../../../reducers/compose'),
import(/* webpackChunkName: "reducers/media_attachments" */'../../../reducers/media_attachments'),
import(/* webpackChunkName: "reducers/search" */'../../../reducers/search'),
]).then(([component, composeReducer, mediaAttachmentsReducer, searchReducer]) => {
injectAsyncReducer(store, 'compose', composeReducer.default);
injectAsyncReducer(store, 'media_attachments', mediaAttachmentsReducer.default);
injectAsyncReducer(store, 'search', searchReducer.default);
return component;
});
} }
export function Notifications () { export function Notifications () {
return Promise.all([ return import(/* webpackChunkName: "features/notifications" */'../../notifications');
import(/* webpackChunkName: "features/notifications" */'../../notifications'),
import(/* webpackChunkName: "reducers/notifications" */'../../../reducers/notifications'),
]).then(([component, notificationsReducer]) => {
injectAsyncReducer(store, 'notifications', notificationsReducer.default);
store.dispatch(refreshNotifications());
return component;
});
} }
export function HomeTimeline () { export function HomeTimeline () {
@ -110,15 +83,7 @@ export function MediaModal () {
} }
export function OnboardingModal () { export function OnboardingModal () {
return Promise.all([ return import(/* webpackChunkName: "modals/onboarding_modal" */'../components/onboarding_modal');
import(/* webpackChunkName: "modals/onboarding_modal" */'../components/onboarding_modal'),
import(/* webpackChunkName: "reducers/compose" */'../../../reducers/compose'),
import(/* webpackChunkName: "reducers/media_attachments" */'../../../reducers/media_attachments'),
]).then(([component, composeReducer, mediaAttachmentsReducer]) => {
injectAsyncReducer(store, 'compose', composeReducer.default);
injectAsyncReducer(store, 'media_attachments', mediaAttachmentsReducer.default);
return component;
});
} }
export function VideoModal () { export function VideoModal () {

View file

@ -23,7 +23,7 @@ import {
COMPOSE_EMOJI_INSERT, COMPOSE_EMOJI_INSERT,
} from '../actions/compose'; } from '../actions/compose';
import { TIMELINE_DELETE } from '../actions/timelines'; import { TIMELINE_DELETE } from '../actions/timelines';
import { STORE_HYDRATE_LAZY } from '../actions/store'; import { STORE_HYDRATE } from '../actions/store';
import Immutable from 'immutable'; import Immutable from 'immutable';
import uuid from '../uuid'; import uuid from '../uuid';
@ -134,7 +134,7 @@ const privacyPreference = (a, b) => {
export default function compose(state = initialState, action) { export default function compose(state = initialState, action) {
switch(action.type) { switch(action.type) {
case `${STORE_HYDRATE_LAZY}-compose`: case STORE_HYDRATE:
return clearAll(state.merge(action.state.get('compose'))); return clearAll(state.merge(action.state.get('compose')));
case COMPOSE_MOUNT: case COMPOSE_MOUNT:
return state.set('mounted', true); return state.set('mounted', true);

View file

@ -14,6 +14,10 @@ import status_lists from './status_lists';
import cards from './cards'; import cards from './cards';
import reports from './reports'; import reports from './reports';
import contexts from './contexts'; import contexts from './contexts';
import compose from './compose';
import search from './search';
import media_attachments from './media_attachments';
import notifications from './notifications';
const reducers = { const reducers = {
timelines, timelines,
@ -31,13 +35,10 @@ const reducers = {
cards, cards,
reports, reports,
contexts, contexts,
compose,
search,
media_attachments,
notifications,
}; };
export function createReducer(asyncReducers) {
return combineReducers({
...reducers,
...asyncReducers,
});
}
export default combineReducers(reducers); export default combineReducers(reducers);

View file

@ -1,4 +1,4 @@
import { STORE_HYDRATE_LAZY } from '../actions/store'; import { STORE_HYDRATE } from '../actions/store';
import Immutable from 'immutable'; import Immutable from 'immutable';
const initialState = Immutable.Map({ const initialState = Immutable.Map({
@ -7,7 +7,7 @@ const initialState = Immutable.Map({
export default function meta(state = initialState, action) { export default function meta(state = initialState, action) {
switch(action.type) { switch(action.type) {
case `${STORE_HYDRATE_LAZY}-media_attachments`: case STORE_HYDRATE:
return state.merge(action.state.get('media_attachments')); return state.merge(action.state.get('media_attachments'));
default: default:
return state; return state;

View file

@ -1,36 +1,15 @@
import { createStore, applyMiddleware, compose } from 'redux'; import { createStore, applyMiddleware, compose } from 'redux';
import thunk from 'redux-thunk'; import thunk from 'redux-thunk';
import appReducer, { createReducer } from '../reducers'; import appReducer from '../reducers';
import { hydrateStoreLazy } from '../actions/store';
import { hydrateAction } from '../containers/mastodon';
import loadingBarMiddleware from '../middleware/loading_bar'; import loadingBarMiddleware from '../middleware/loading_bar';
import errorsMiddleware from '../middleware/errors'; import errorsMiddleware from '../middleware/errors';
import soundsMiddleware from '../middleware/sounds'; import soundsMiddleware from '../middleware/sounds';
export default function configureStore() { export default function configureStore() {
const store = createStore(appReducer, compose(applyMiddleware( return createStore(appReducer, compose(applyMiddleware(
thunk, thunk,
loadingBarMiddleware({ promiseTypeSuffixes: ['REQUEST', 'SUCCESS', 'FAIL'] }), loadingBarMiddleware({ promiseTypeSuffixes: ['REQUEST', 'SUCCESS', 'FAIL'] }),
errorsMiddleware(), errorsMiddleware(),
soundsMiddleware() soundsMiddleware()
), window.devToolsExtension ? window.devToolsExtension() : f => f)); ), window.devToolsExtension ? window.devToolsExtension() : f => f));
store.asyncReducers = { };
return store;
}; };
export function injectAsyncReducer(store, name, asyncReducer) {
if (!store.asyncReducers[name]) {
// Keep track that we injected this reducer
store.asyncReducers[name] = asyncReducer;
// Add the current reducer to the store
store.replaceReducer(createReducer(store.asyncReducers));
// The state this reducer handles defaults to its initial state (stored inside the reducer)
// But that state may be out of date because of the server-side hydration, so we replay
// the hydration action but only for this reducer (all async reducers must listen for this dynamic action)
store.dispatch(hydrateStoreLazy(name, hydrateAction.state));
}
}

View file

@ -22,19 +22,10 @@
= javascript_pack_tag 'common', integrity: true, crossorigin: 'anonymous' = javascript_pack_tag 'common', integrity: true, crossorigin: 'anonymous'
= javascript_pack_tag 'features/getting_started', integrity: true, crossorigin: 'anonymous', rel: 'preload', as: 'script' = javascript_pack_tag 'features/getting_started', integrity: true, crossorigin: 'anonymous', rel: 'preload', as: 'script'
= javascript_pack_tag 'features/compose', integrity: true, crossorigin: 'anonymous', rel: 'preload', as: 'script' = javascript_pack_tag 'features/compose', integrity: true, crossorigin: 'anonymous', rel: 'preload', as: 'script'
= javascript_pack_tag 'reducers/compose', integrity: true, crossorigin: 'anonymous', rel: 'preload', as: 'script'
= javascript_pack_tag 'reducers/media_attachments', integrity: true, crossorigin: 'anonymous', rel: 'preload', as: 'script'
= javascript_pack_tag 'reducers/search', integrity: true, crossorigin: 'anonymous', rel: 'preload', as: 'script'
= javascript_pack_tag 'features/home_timeline', integrity: true, crossorigin: 'anonymous', rel: 'preload', as: 'script' = javascript_pack_tag 'features/home_timeline', integrity: true, crossorigin: 'anonymous', rel: 'preload', as: 'script'
= javascript_pack_tag 'features/notifications', integrity: true, crossorigin: 'anonymous', rel: 'preload', as: 'script' = javascript_pack_tag 'features/notifications', integrity: true, crossorigin: 'anonymous', rel: 'preload', as: 'script'
= javascript_pack_tag 'reducers/notifications', integrity: true, crossorigin: 'anonymous', rel: 'preload', as: 'script'
= javascript_pack_tag 'features/community_timeline', integrity: true, crossorigin: 'anonymous', rel: 'preload', as: 'script' = javascript_pack_tag 'features/community_timeline', integrity: true, crossorigin: 'anonymous', rel: 'preload', as: 'script'
= javascript_pack_tag 'features/public_timeline', integrity: true, crossorigin: 'anonymous', rel: 'preload', as: 'script' = javascript_pack_tag 'features/public_timeline', integrity: true, crossorigin: 'anonymous', rel: 'preload', as: 'script'
= javascript_pack_tag "locale_#{I18n.locale}", integrity: true, crossorigin: 'anonymous' = javascript_pack_tag "locale_#{I18n.locale}", integrity: true, crossorigin: 'anonymous'