React Redux

repository·master·Indexed 12 days ago

https://github.com/reduxjs/react-redux

Official React bindings for Redux, providing performant and flexible state management. Version 9.3.0 requires React 18 or later. Includes the Hooks API and the connect() Higher-Order Component for injecting state and dispatch logic into components, as well as the batch() function for grouping state updates.

Tokens
32.6K
Snippets
105
Records
131
Agent score
97%

What's inside React Redux

  1. What is React Redux and why should I use it?

    master

    Redux is a standalone state management library that can work with any UI framework. React Redux is the official UI binding library for React.

    Instead of manually interacting with the Redux store from your React components, React Redux acts as a bridge. It automates the repetitive and complex logic required to integrate a Redux store with a UI layer, specifically:

    1. Subscribing to store updates: Automatically listening for changes in the Redux state.
    2. Data Extraction: Extracting only the specific pieces of data needed by a component.
    3. UI Synchronization: Triggering re-renders only when the relevant data has actually changed.
    4. Action Dispatching: Providing mechanisms to respond to UI inputs by dispatching Redux actions.

    Using React Redux ensures your application follows React's declarative design principles and stays compatible with updates to both React and Redux.

  2. What is `batch()` and when should I use it?

    master

    The batch() API is a re-export of React's unstable_batchedUpdates(). Because React-Redux supports both ReactDOM and React Native, it handles importing the correct version of this API from the appropriate renderer at build time.

    Use Case: Use batch() when you need to ensure that multiple actions dispatched outside of a React event loop tick result in only one combined re-render.

    When to skip:

    • If you are on React 18+, automatic batching is enabled by default, making this API unnecessary.
    • If your dispatches are already happening inside a React event handler (like an onClick), React already batches them automatically.
  3. Understand the return value of `connect()`

    master

    The connect() function returns a Higher-Order Component (HOC) wrapper. This wrapper is a function that takes your component as an argument and returns a new component with injected props.

    You can use this to create a reusable HOC if multiple components need the same Redux state or dispatch logic, or you can call it immediately to wrap a component in a single step.

    import { login, logout } from './actionCreators'
    
    const mapState = (state) => state.user
    const mapDispatch = { login, logout }
    
    // Option 1: Create a reusable HOC
    const connectUser = connect(mapState, mapDispatch)
    const ConnectedUserLogin = connectUser(Login)
    const ConnectedUserProfile = connectUser(Profile)
    
    // Option 2: Immediate wrapping (most common)
    export default connect(mapState, mapDispatch)(Login)
  4. How `connect()` manages props and data flow

    master

    When you use connect(), it creates a new component that wraps your original component. It manages the flow of data from the Redux store to your component using several lifecycle steps:

    1. mapStateToProps: Receives the Redux state (and optionally ownProps) and returns an object of data (internally called stateProps).
    2. mapDispatchToProps: Receives the Redux dispatch (and optionally ownProps) and returns an object of action creators/functions (internally called dispatchProps).
    3. mergeProps: If defined, this function takes stateProps, dispatchProps, and ownProps as arguments and returns a single object (mergedProps) that is passed to your component as props.

    If mergeProps is not provided, React-Redux automatically merges stateProps and dispatchProps into a single object.

  5. How React Redux optimizes performance

    master

    By default, React re-renders components when their parent re-renders, which can lead to wasted effort if the underlying data hasn't changed.

    React Redux implements internal performance optimizations to prevent unnecessary re-renders. It achieves this by:

    • Selective Re-rendering: Ensuring a component only re-renders when the specific slice of state it is subscribed to actually changes.
    • Granular Data Extraction: Allowing multiple components to connect to the store and extract only the specific pieces of data they need. This minimizes the frequency of re-renders because most state changes in the store will not affect the specific data slices used by those individual components.
  6. How `mapStateToProps` triggers re-renders

    master

    The wrapper component created by connect subscribes to the Redux store. It optimizes performance by checking if the store state has changed by reference (lastState === currentState).

    If the state reference is identical, mapStateToProps will not run. This relies on reducers (like those created with combineReducers) returning a new state object only when data actually changes.

    Warning: If you mutate state in a reducer instead of returning a new object, combineReducers may return the old state object, causing the UI to fail to re-render because mapStateToProps is never triggered.

  7. Avoid calling `store.dispatch` directly in components

    master
    It is considered an anti-pattern to import the Redux store and call store.dispatch() directly inside a React component. Instead, use the dispatch function provided to your component's props via connect. This ensures your component remains decoupled from the specific store instance and follows React Redux best practices.
  8. How to use multiple Redux stores

    master

    While Redux is designed for a single store, you can implement multiple stores by providing unique custom contexts for each. This isolates the stores from one another.

    To use multiple stores in a single component tree, you can wrap parts of your application in nested <Provider> components, each with its own store and context. You can then use compose with multiple connect calls to allow a single component to receive props from multiple stores.

    const ContextA = React.createContext(null);
    const ContextB = React.createContext(null);
    
    const storeA = createStore(reducerA);
    const storeB = createStore(reducerB);
    
    function App() {
      return (
        <Provider store={storeA} context={ContextA}>
          <Provider store={storeB} context={ContextB}>
            <RootModule />
          </Provider>
        </Provider>
      );
    }
    
    // Chaining connect to merge props from both stores
    const MultiConnectedComponent = compose(
      connect(mapStateA, null, null, { context: ContextA }),
      connect(mapStateB, null, null, { context: ContextB })
    )(MyComponent);
  9. Avoid 'Stale Props' and 'Zombie Children' when using hooks

    master

    Because hooks do not create a nested hierarchy of subscriptions (unlike connect()), edge cases like "stale props" and "zombie children" can occur:

    • Stale Props: A selector relies on props that haven't updated yet because the component hasn't re-rendered, leading to incorrect data or errors.
    • Zombie Children: A child component subscribes to the store before its parent. If an action deletes the data the child relies on, the child might attempt to read non-existent data before the parent can unmount it.

    How to mitigate these issues:

    1. Defensive Selectors: Don't reach straight into nested state (e.g., state.todos[props.id].name). Instead, verify the object exists first: const todo = state.todos[props.id]; return todo ? todo.name : undefined;.
    2. Avoid Props in Selectors: Try not to rely on component props inside your useSelector function.
    3. Use connect() as a Buffer: Placing a connect()-wrapped component in the tree just above a hook-based component can prevent these issues by ensuring the connect() component re-renders and updates its children via the subscription hierarchy.
  10. Use factory functions in `connect()` for memoized selectors

    master

    If mapStateToProps or mapDispatchToProps returns a function, that returned function is treated as the actual mapping function. This is a powerful pattern for creating component-instance-specific selectors, which is essential when using memoized selectors (like those from Reselect) to ensure that each component instance has its own selector cache.

    const makeUniqueSelectorInstance = () =>
      createSelector([selectItems, selectItemId], (items, itemId) => items[itemId])
    
    const makeMapState = (state) => {
      // This selector instance is unique to this component instance
      const selectItemForThisComponent = makeUniqueSelectorInstance()
      
      return function realMapState(state, ownProps) {
        const item = selectItemForThisComponent(state, ownProps.itemId)
        return { item }
      }
    }
    
    export default connect(makeMapState)(SomeComponent)
  11. How the number of arguments in `mapStateToProps` affects behavior

    master

    The number of arguments you define in your mapStateToProps function changes when the function is executed:

    1. (state): The function runs only when the root store state object changes.
    2. (state, ownProps): The function runs when the store state changes AND whenever the component's own props change.

    Best Practice: Do not include ownProps in the function signature unless you actually need to use it. Including it causes the function to run more frequently, potentially impacting performance.

    Argument Injection Rules

    React Redux determines whether to inject ownProps based on the number of mandatory parameters in your function definition:

    • Will NOT receive ownProps: If the function has exactly one mandatory parameter.
    • WILL receive ownProps: If the function has zero or two mandatory parameters.
    // Does NOT receive ownProps (1 mandatory parameter)
    function mapStateToProps(state) {
      console.log(state) // state
      console.log(arguments[1]) // undefined
    }
    
    // DOES receive ownProps (2 mandatory parameters)
    function mapStateToProps(state, ownProps) {
      console.log(state) // state
      console.log(ownProps) // ownProps
    }
    
    // DOES receive ownProps (0 mandatory parameters)
    function mapStateToProps() {
      console.log(arguments[0]) // state
      console.log(arguments[1]) // ownProps
    }
    
    // DOES receive ownProps (using rest parameters)
    function mapStateToProps(...args) {
      console.log(args[0]) // state
      console.log(args[1]) // ownProps
    }
  12. Use React Redux hooks as the default API

    master
    React Redux provides a custom hooks API that allows function components to subscribe to the Redux store and dispatch actions. While the connect HOC API is still supported, the hooks API is the recommended approach because it is simpler and offers better TypeScript support. These hooks were introduced in version 7.1.0.