Reactjs+Redux+React Router应用:Redux更新致组件重复渲染咨询
Hey there, let’s work through this issue you’re facing—this is a super common pain point when you’re ramping up with Redux, so we’ll break down exactly what’s happening and how to fix it.
Why All Components Are Re-Rendering
First, let’s get to the root of why every component is re-rendering when your FlashMessage state updates:
- Overly Broad
useSelectorCalls: If you’re usinguseSelector(state => state)(grabbing the entire store) or selecting more state than your component actually needs, Redux will trigger a re-render every time any part of the store changes—even if your component doesn’t care about that change. - No Memoization for Selected State: By default,
useSelectoruses reference equality checks. If you return an object/array fromuseSelectorwithout memoization, every store update will create a new reference, tricking React into thinking the state changed even when it didn’t. - Parent Component Re-Renders Causing Child Re-Renders: Even if a child component doesn’t use the updated state, if its parent re-renders, the child will too—unless you explicitly prevent it.
Why API Calls Are Happening Twice
The duplicate API calls are likely tied to the unnecessary re-renders, but here are the most common culprits:
- Unoptimized
useEffectDependencies: If your API call is in auseEffectthat has a dependency that changes on every re-render (like an unmemoized object), the effect will run again each time. - React Strict Mode: In development, React’s Strict Mode intentionally mounts components twice to detect accidental side effects. This can cause API calls to run twice—this is normal in dev and won’t happen in production.
- Duplicate Action Dispatches: You might be dispatching the action that triggers the API call more than once (e.g., in multiple components or lifecycle methods).
Step-by-Step Fixes
1. Optimize Redux Selectors
- Select Only What You Need: Instead of grabbing the entire store, pick exactly the state slice your component uses. For example:
// Bad: Grabs entire store, causes re-renders on any state change const state = useSelector(state => state); // Good: Only selects the flashMessage slice const flashMessage = useSelector(state => state.flashMessage); - Use
shallowEqualfor Multiple Selections: If you need to pick multiple state slices, useshallowEqualto compare objects by value instead of reference:import { useSelector, shallowEqual } from 'react-redux'; const MyComponent = () => { const { flashMessage, user } = useSelector( state => ({ flashMessage: state.flashMessage, user: state.user }), shallowEqual // Compares object values instead of references ); // ... }; - Create Memoized Selectors with Reselect: For more complex state selections (like derived data), use Reselect to create memoized selectors that only recalculate when their inputs change:
import { createSelector } from '@reduxjs/toolkit'; // Base selectors const selectFlashMessage = state => state.flashMessage; const selectUser = state => state.user; // Memoized combined selector export const selectRelevantData = createSelector( [selectFlashMessage, selectUser], (flashMessage, user) => ({ flashMessage, user }) ); // In your component: const data = useSelector(selectRelevantData);
2. Prevent Unnecessary Component Re-Renders
- Wrap Components with
React.memo: This tells React to only re-render the component if its props have changed (shallow comparison):import React from 'react'; const FlashMessage = React.memo(({ message }) => { return <div className="flash-message">{message}</div>; }); export default FlashMessage; - Memoize Props with
useMemo/useCallback: If you’re passing objects, arrays, or functions as props to child components, useuseMemo(for values) oruseCallback(for functions) to keep their references stable:const ParentComponent = () => { const flashMessage = useSelector(state => state.flashMessage); // Memoize the object to prevent unnecessary child re-renders const messageProps = useMemo(() => ({ text: flashMessage.text }), [flashMessage.text]); return <FlashMessage {...messageProps} />; };
3. Fix Duplicate API Calls
- Audit
useEffectDependencies: Make sure your API call’suseEffectonly runs when necessary. For example, if it should run once on mount, use an empty dependency array:useEffect(() => { const fetchData = async () => { const response = await fetch('/api/data'); const data = await response.json(); dispatch(setData(data)); }; fetchData(); }, []); // Empty array = runs once on mount - Check for Strict Mode: If you’re seeing duplicate calls in development only, it’s likely React Strict Mode. You can temporarily remove
<React.StrictMode>from your root component to verify, but remember this is a dev-only check—your production build won’t have this behavior. - Verify Action Dispatches: Double-check that you’re not dispatching the API-triggering action in multiple places (e.g., in both a parent and child component, or in a lifecycle method that runs on every re-render).
Final Notes
Start with optimizing your selectors—this is usually the biggest win for reducing unnecessary re-renders. Then layer in React.memo and memoized props as needed. For the API calls, narrow down whether it’s a dependency issue, Strict Mode, or duplicate dispatches.
内容的提问来源于stack exchange,提问作者chifliiiii

