Mastering Redux: Core Principles, Architecture, and Real-World Implementation
This article provides a comprehensive overview of Redux, covering its design philosophy, core components, API implementation, React bindings, and practical code examples to help developers understand and apply state management effectively in modern front‑end applications.
Redux Design Philosophy
Single source of truth
There must be only one unique global data source; the state and the view have a one‑to‑one mapping.
Data - View Mapping
State is read‑only
The state cannot be mutated directly; when a change is needed, a new state replaces the old one.
Changes are made with pure functions
State updates are performed by a pure function (Reducer) that receives an Action describing how the state should change and returns a brand‑new state.
Pure functions produce the same output for the same input, making state updates predictable. The current state is determined by two factors: the initial state and the sequence of dispatched actions, enabling time‑travel debugging with Redux DevTools.
Single State + Pure Function
Redux Architecture
Redux Components
state : the global immutable state object.
store : the object created by createStore, encapsulating methods that operate on the global state.
action : an object describing how the state should change; it always has a type property and is dispatched via store.dispatch.
reducer : a pure function supplied by the user that receives the current state and an action, returning the next state.
storeEnhancer : a higher‑order function that wraps createStore to extend store capabilities; applyMiddleware is the official example.
middleware : a higher‑order function that wraps dispatch, enabling a chain of processing steps.
Redux Composition
Redux API Implementation
Redux Core
createStore
createStorecreates a closure that defines the store and its API. It checks for a storeEnhancer and applies it if present, then dispatches an internal INIT action to produce the initial state.
if (typeof enhancer !== 'undefined') {
// type checking
if (typeof enhancer !== 'function') {
...
}
// enhancer receives a storeCreator and returns a new storeCreator
return enhancer(createStore)(reducer, preloadedState)
}When no initial state is supplied, the reducer must provide a default value.
// When a store is created, an "INIT" action is dispatched so that every
// reducer returns their initial state. This effectively populates
// the initial state tree.
dispatch({ type: ActionTypes.INIT } as A)The store object is assembled and returned:
const store = {
dispatch,
subscribe,
getState,
replaceReducer, // rarely used, omitted here
[$$observable]: observable // rarely used, omitted here
}
return store;getState
Calling getState inside a reducer is prohibited; otherwise it returns the current state.
function getState() {
if (isDispatching) {
...
}
return currentState
}dispatch
The built‑in dispatch supports plain object actions; async actions are handled by middleware. It performs two steps: invoke the reducer to produce a new state, then call all subscribed listeners.
function dispatch(action: A) {
// type checking
if (!isPlainObject(action)) {
...
}
// action must have a type
if (typeof action.type === 'undefined') {
...
}
if (isDispatching) {
...
}
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
}subscribe
Subscribes a listener to state updates and returns an unsubscribe function. Listeners are called on every dispatch, even if the state does not change, so UI bindings typically perform a diff before re‑rendering.
function ensureCanMutateNextListeners() {
if (nextListeners === currentListeners) {
nextListeners = currentListeners.slice()
}
}
function subscribe(listener: () => void) {
if (typeof listener !== 'function') {
...
}
if (isDispatching) {
...
}
let isSubscribed = true
ensureCanMutateNextListeners()
nextListeners.push(listener)
return function unsubscribe() {
if (!isSubscribed) return
if (isDispatching) {
...
}
isSubscribed = false
ensureCanMutateNextListeners()
const index = nextListeners.indexOf(listener)
nextListeners.splice(index, 1)
currentListeners = null
}
}applyMiddleware
applyMiddlewareis a storeEnhancer that enables plugin capabilities. It builds a chain of middleware functions that wrap dispatch.
export default function applyMiddleware(...middlewares) {
return (createStore) => {
const store = createStore(reducer, preloadedState)
let dispatch = () => {
throw new Error('Dispatching while constructing your middleware is not allowed.')
}
const middlewareAPI = {
getState: store.getState,
dispatch: (action, ...args) => dispatch(action, ...args)
}
const chain = middlewares.map(middleware => middleware(middlewareAPI))
dispatch = compose(...chain)(store.dispatch)
return { ...store, dispatch }
}
}Example: redux‑thunk creates a middleware that intercepts function actions.
function createThunkMiddleware(extraArgument) {
return ({ dispatch, getState }) => next => action => {
if (typeof action === 'function') {
return action(dispatch, getState, extraArgument)
}
return next(action)
}
}Redux Utils
compose
composecreates a right‑to‑left function pipeline, a common pattern in functional programming.
export default function compose(...funcs) {
if (funcs.length === 0) {
return arg => arg
}
if (funcs.length === 1) {
return funcs[0]
}
return funcs.reduce((a, b) => (...args) => a(b(...args)))
}combineReducers
Combines multiple reducers into a tree‑shaped reducer.
function combineReducers(reducers) {
const reducerKeys = Object.keys(reducers)
const finalReducers = {}
for (let i = 0; i < reducerKeys.length; i++) {
const key = reducerKeys[i]
finalReducers[key] = reducers[key]
}
const finalReducerKeys = Object.keys(finalReducers)
return function combination(state, action) {
let hasChanged = false
const nextState = {}
for (let i = 0; i < finalReducerKeys.length; i++) {
const key = finalReducerKeys[i]
const reducer = finalReducers[key]
const previousStateForKey = state[key]
const nextStateForKey = reducer(previousStateForKey, action)
nextState[key] = nextStateForKey
hasChanged = hasChanged || nextStateForKey !== previousStateForKey
}
hasChanged = hasChanged || finalReducerKeys.length !== Object.keys(state).length
return hasChanged ? nextState : state
}
}bindActionCreators
Wraps an action creator with dispatch so that calling the result directly dispatches the action.
function bindActionCreator(actionCreator, dispatch) {
return function () {
return dispatch(actionCreator.apply(this, arguments))
}
}Redux UI Bindings
React‑Redux
React‑Redux provides two ways to use Redux in React: higher‑order components (HOC) and Hooks. The Hooks API is highlighted.
How UI Gets Global State
Store the global state in a React Context.
export const ReactReduxContext = React.createContext(null)Wrap the app with a Provider component that supplies the store via the context.
function Provider({ store, context, children }) {
const Context = context || ReactReduxContext
return <Context.Provider value={contextValue}>{children}</Context.Provider>
}Expose a hook useStore to retrieve the store.
function useStore() {
const { store } = useReduxContext()
return store
}How State Changes Trigger UI Updates
useSelectorsubscribes to the store and forces a re‑render when the selected slice of state changes.
When an action is dispatched, useSelector() will do a reference comparison of the previous selector result value and the current result value. If they are different, the component will be forced to re‑render. If they are the same, the component will not re‑render.
Subscribe to store updates.
const subscription = useMemo(() => createSubscription(store), [store, contextSub])
subscription.onStateChange = checkForUpdatesPerform a shallow diff of the selected state.
function checkForUpdates() {
try {
const newStoreState = store.getState()
const newSelectedState = latestSelector.current!(newStoreState)
if (equalityFn(newSelectedState, latestSelectedState.current)) {
return
}
latestSelectedState.current = newSelectedState
latestStoreState.current = newStoreState
} catch (err) {
latestSubscriptionCallbackError.current = err as Error
}
forceRender()
}Force a component re‑render.
const [, forceRender] = useReducer(s => s + 1, 0)
forceRender()Using Redux Without UI Bindings
The three steps (create store, dispatch actions, subscribe to updates) are sufficient. Example:
const App = () => {
const state = store.getState()
const [, forceRender] = useReducer(c => c + 1, 0)
useEffect(() => {
return store.subscribe(() => {
forceRender()
})
}, [])
const onIncrement = () => {
store.dispatch({ type: 'increment' })
}
const onDecrement = () => {
store.dispatch({ type: 'decrement' })
}
return (
<div style={{ textAlign: 'center', marginTop: '35%' }}>
<h1 style={{ color: 'green', fontSize: '500%' }}>{state.count}</h1>
<button onClick={onDecrement} style={{ marginRight: '10%' }}>decrement</button>
<button onClick={onIncrement}>increment</button>
</div>
)
}Conclusion
Redux’s core implements the concepts of a single immutable state, pure reducers, and a minimal API that exposes store enhancers and middleware for extensibility. Its design enables powerful plugin ecosystems while keeping the core small and predictable.
The use of higher‑order functions for enhancers and middleware demonstrates a flexible plugin architecture that can be composed via compose, reducing cognitive load for developers extending Redux.
Redux solves the complex mapping between state and view by providing a predictable state container; UI frameworks handle rendering, diffing, and subscription, allowing Redux to remain focused on state management.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
