@bengo.co/typescript-web-starter
Version:
A simple website project written in TypeScript. Use this as a starting point for your project.
168 lines (149 loc) • 4.72 kB
text/typescript
/**
* Store - objects that store all the data for our app components, and handle dispatched actions that change the data
*/
import { connectRouter, routerMiddleware } from "connected-react-router";
import { createMemoryHistory, History } from "history";
import {
AnyAction,
applyMiddleware,
combineReducers,
compose,
createStore,
Middleware,
Reducer,
ReducersMapObject,
Store,
} from "redux";
import { default as createSagaMiddleware, SagaMiddleware } from "redux-saga";
import { default as thunkMiddleware } from "redux-thunk";
declare const module: { hot: any }; // tslint:disable-line:no-reserved-keywords no-any
const configureMiddlewares = (
oldMiddlewares: Middleware[],
history?: History,
): Middleware[] => {
const mw: Middleware[] = [...oldMiddlewares];
if (history) {
mw.push(routerMiddleware(history));
}
return mw;
};
interface StoreOptions<State, Action extends DefaultAction> {
history?: History;
initialState?: State;
middlewares?: Middleware[];
reducers: ReducersMapObject<State, Action>; // tslint:disable-line:use-default-type-parameter
}
export type DefaultAction = AnyAction;
export type DefaultState = {};
export type DefaultStore = Store<DefaultState, DefaultAction>
interface MainStoreResult<StoreType> {
store: StoreType;
}
/**
* Create a redux Store to hold data for a tree of Components
*/
export const mainStore = <
StoreState extends DefaultState,
StoreAction extends DefaultAction,
SagaContext = {},
>(
o: StoreOptions<StoreState, StoreAction>,
): MainStoreResult<Store<StoreState, StoreAction>> => {
const history = o.history;
const result = injectableStore(
o.reducers,
o.initialState,
history,
configureMiddlewares([], history),
);
return {
store: result.store,
};
};
export const store = mainStore;
interface StoreWithInjectors<State, Action extends DefaultAction, ThisSagaContext> {
store: Store<State, Action>;
runSaga: (SagaMiddleware<ThisSagaContext>)["run"];
injectedReducers: ReducersMapObject<State, Action>; // tslint:disable-line:use-default-type-parameter
injectedSagas: any; // tslint:disable-line:no-any
history: History;
}
/**
* Build a root reducer for a redux store
*/
function createRootReducer<
State extends DefaultState,
ActionType extends DefaultAction
>(
reducers: ReducersMapObject<State>,
history: History,
): Reducer<State, ActionType> {
const combined = combineReducers<State>(reducers);
return connectRouter(history)<State>(combined);
}
interface DevtoolsExtensionComposeOptions {
shouldHotReload: boolean;
}
type ReduxDevtoolsExtensionCompose = (
o: DevtoolsExtensionComposeOptions,
) => typeof compose;
declare global {
interface Window {
__REDUX_DEVTOOLS_EXTENSION_COMPOSE__: ReduxDevtoolsExtensionCompose;
}
}
/**
* Create a redux store where sagas/reducers can be injected after time of creation
*/
function injectableStore<
StoreState extends DefaultState,
StoreAction extends DefaultAction,
StoreType extends Store<StoreState, StoreAction>,
SagaContext = {},
>(
reducers: ReducersMapObject<StoreState, StoreAction>,
initialState?: StoreState,
history: History = createMemoryHistory(),
middlewares: Middleware[] = [],
): StoreWithInjectors<StoreState, StoreAction, SagaContext> {
// Create the store with 3 middlewares
// 1. sagaMiddleware: Makes redux-sagas work
// 2. thunkMiddleware: Makes redux-thunk work
const sagaMiddleware = createSagaMiddleware();
const middlewaresToApply = [sagaMiddleware, thunkMiddleware, ...middlewares];
const enhancers = [applyMiddleware(...middlewaresToApply)];
// If Redux DevTools Extension is installed use it, otherwise use Redux compose
const composeEnhancers: typeof compose =
typeof window === "object" && window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__
? window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__({
// Try to remove when `react-router-redux` is out of beta, LOCATION_CHANGE should not be fired more than once after hot reloading
// Prevent recomputing reducers for `replaceReducer`
shouldHotReload: false,
})
: compose;
const rootReducer = createRootReducer(reducers, history);
const regularStore: Store<StoreState, StoreAction> = createStore(
rootReducer,
initialState || {},
composeEnhancers(...enhancers),
);
const s: StoreWithInjectors<StoreState, StoreAction, SagaContext> = {
store: regularStore,
runSaga: sagaMiddleware.run,
injectedReducers: reducers,
injectedSagas: {},
history,
};
return s;
}
import { log } from "../../log";
const main = async () => {
const { reducers } = await import('../reducers')
const { store: s } = mainStore({ reducers });
log("info", s.getState());
};
if (require.main === module) {
main().catch(error => {
throw error;
});
}