Skip to content

Instantly share code, notes, and snippets.

@antoniopresto
Created October 14, 2025 22:14
Show Gist options
  • Save antoniopresto/875c4435e8f31a2c85f87d805ffac6fa to your computer and use it in GitHub Desktop.
Save antoniopresto/875c4435e8f31a2c85f87d805ffac6fa to your computer and use it in GitHub Desktop.
/**
* A plain object that represents an intention to change the state.
* Actions are the only way to get data into the store.
*/
export interface Action<T = any> {
type: T;
}
/**
* An Action type which accepts any other properties.
*/
export interface AnyAction extends Action {
[extraProps: string]: any;
}
/**
* A function that takes the current state and an action, and returns the next state.
* @template S The type of state maintained by the store.
* @template A The type of action which can be dispatched.
*/
export type Reducer<S = any, A extends Action = AnyAction> = (
state: S | undefined,
action: A
) => S;
/**
* A function to dispatch an action. It is the only way to trigger a state change.
*/
export type Dispatch<A extends Action = AnyAction> = (action: A) => A;
/**
* A function that is called when the state in the store changes.
*/
export type Unsubscribe = () => void;
/**
* The single source of truth for your application's state.
* @template S The type of state maintained by the store.
* @template A The type of action which can be dispatched.
*/
export interface Store<S = any, A extends Action = AnyAction> {
dispatch: Dispatch<A>;
getState(): S;
subscribe(listener: () => void): Unsubscribe;
replaceReducer(nextReducer: Reducer<S, A>): void;
}
/**
* A store creator is a function that creates a Redux store.
*/
export type StoreCreator = <S, A extends Action>(
reducer: Reducer<S, A>,
preloadedState?: S
) => Store<S, A>;
/**
* A store enhancer is a higher-order function that composes a store creator to return a new, enhanced store creator.
*/
export type StoreEnhancer<Ext = {}, StateExt = {}> = (
next: StoreCreator
) => StoreCreator;
/**
* A middleware is a higher-order function that composes a dispatch function to return a new dispatch function.
*/
export interface MiddlewareAPI<D extends Dispatch = Dispatch, S = any> {
dispatch: D;
getState(): S;
}
export interface Middleware<
DispatchExt = {},
S = any,
D extends Dispatch = Dispatch
> {
(api: MiddlewareAPI<D, S>): (
next: Dispatch<AnyAction>
) => (action: AnyAction) => any;
}
/**
* A utility function to check if an object is a plain object.
*/
function isPlainObject(obj: any): boolean {
if (typeof obj !== 'object' || obj === null) return false;
let proto = obj;
while (Object.getPrototypeOf(proto) !== null) {
proto = Object.getPrototypeOf(proto);
}
return Object.getPrototypeOf(obj) === proto;
}
/**
* Creates a Redux store that holds the complete state tree of your app.
* There should only be a single store in your app.
*
* @param reducer A function that returns the next state tree, given the current state tree and an action to handle.
* @param preloadedState The initial state.
* @param enhancer The store enhancer.
* @returns A Redux store that lets you read the state, dispatch actions and subscribe to changes.
*/
export function createStore<S, A extends Action, Ext, StateExt>(
reducer: Reducer<S, A>,
preloadedState?: S,
enhancer?: StoreEnhancer<Ext, StateExt>
): Store<S, A> & Ext {
if (typeof preloadedState === 'function' && typeof enhancer === 'undefined') {
enhancer = preloadedState as StoreEnhancer<Ext, StateExt>;
preloadedState = undefined;
}
if (typeof enhancer !== 'undefined') {
if (typeof enhancer !== 'function') {
throw new Error('Expected the enhancer to be a function.');
}
return enhancer(createStore)(reducer, preloadedState) as Store<S, A> & Ext;
}
if (typeof reducer !== 'function') {
throw new Error('Expected the reducer to be a function.');
}
let currentReducer = reducer;
let currentState = preloadedState as S;
let currentListeners: (() => void)[] | null = [];
let nextListeners = currentListeners;
let isDispatching = false;
function ensureCanMutateNextListeners() {
if (nextListeners === currentListeners) {
nextListeners = currentListeners.slice();
}
}
function getState(): S {
if (isDispatching) {
throw new Error(
'You may not call store.getState() while the reducer is executing.'
);
}
return currentState;
}
function subscribe(listener: () => void): Unsubscribe {
if (typeof listener !== 'function') {
throw new Error('Expected the listener to be a function.');
}
if (isDispatching) {
throw new Error(
'You may not call store.subscribe() while the reducer is executing.'
);
}
let isSubscribed = true;
ensureCanMutateNextListeners();
nextListeners.push(listener);
return function unsubscribe() {
if (!isSubscribed) {
return;
}
if (isDispatching) {
throw new Error(
'You may not unsubscribe from a store listener while the reducer is executing.'
);
}
isSubscribed = false;
ensureCanMutateNextListeners();
const index = nextListeners.indexOf(listener);
nextListeners.splice(index, 1);
currentListeners = null;
};
}
function dispatch(action: A): A {
if (!isPlainObject(action)) {
throw new Error('Actions must be plain objects.');
}
if (typeof action.type === 'undefined') {
throw new Error('Actions may not have an undefined "type" property.');
}
if (isDispatching) {
throw new Error('Reducers may not dispatch actions.');
}
try {
isDispatching = true;
currentState = currentReducer(currentState, action);
} finally {
isDispatching = false;
}
const listeners = (currentListeners = nextListeners);
for (let i = 0; i < listeners.length; i++) {
const listener = listeners[i];
listener();
}
return action;
}
function replaceReducer(nextReducer: Reducer<S, A>): void {
if (typeof nextReducer !== 'function') {
throw new Error('Expected the nextReducer to be a function.');
}
currentReducer = nextReducer;
dispatch({ type: '@@redux/REPLACE' } as A);
}
dispatch({ type: '@@redux/INIT' } as A);
return {
dispatch,
subscribe,
getState,
replaceReducer,
} as Store<S, A> & Ext;
}
/**
* Composes single-argument functions from right to left. The rightmost
* function can take multiple arguments; the remaining functions must be unary.
*/
export function compose(...funcs: Function[]): Function {
if (funcs.length === 0) {
return (arg: any) => arg;
}
if (funcs.length === 1) {
return funcs[0];
}
return funcs.reduce((a, b) => (...args: any[]) => a(b(...args)));
}
/**
* Creates a store enhancer that applies middleware to the dispatch method
* of the Redux store. This is handy for a variety of tasks, such as expressing
* asynchronous actions in a concise manner, or logging every action payload.
*/
export function applyMiddleware(
...middlewares: Middleware[]
): StoreEnhancer {
return (createStore: StoreCreator) => <S, A extends Action>(
reducer: Reducer<S, A>,
preloadedState?: S
) => {
const store = createStore(reducer, preloadedState);
let dispatch: Dispatch = () => {
throw new Error(
'Dispatching while constructing your middleware is not allowed.'
);
};
const middlewareAPI: MiddlewareAPI<Dispatch<A>, S> = {
getState: store.getState,
dispatch: (action: A) => dispatch(action),
};
const chain = middlewares.map(middleware => middleware(middlewareAPI));
dispatch = compose(...chain)(store.dispatch);
return {
...store,
dispatch,
};
};
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment