forked from treehouse/mastodon
refactor: Make all reducers sync (#4125)
parent
f68fa930ea
commit
37c832cdf7
|
@ -16,10 +16,3 @@ export function hydrateStore(rawState) {
|
||||||
state,
|
state,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
export function hydrateStoreLazy(name, state) {
|
|
||||||
return {
|
|
||||||
type: `${STORE_HYDRATE_LAZY}-${name}`,
|
|
||||||
state,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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 () {
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -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'
|
||||||
|
|
Loading…
Reference in New Issue