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.

ELab Team
ELab Team
ELab Team
Mastering Redux: Core Principles, Architecture, and Real-World Implementation

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

createStore

creates 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

applyMiddleware

is 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

compose

creates 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

useSelector

subscribes 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 = checkForUpdates

Perform 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.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

frontendJavaScriptState ManagementReactmiddleware
ELab Team
Written by

ELab Team

Sharing fresh technical insights

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.