Building Custom Store Enhancers in React with Redux Toolkit
Store enhancers are a powerful mechanism in Redux for extending the functionality of your store. They allow you to add features like persistence, logging, or performance monitoring without directly modifying the core Redux logic. This challenge will guide you in creating your own store enhancers, specifically focusing on a simple logging enhancer and a performance monitoring enhancer using Redux Toolkit.
Problem Description
You are tasked with creating two custom store enhancers for a React application using Redux Toolkit. The first enhancer, loggerEnhancer, should log every dispatched action to the console, including the action type and payload. The second enhancer, performanceEnhancer, should measure and log the time taken for each dispatched action to complete. This will help you understand the performance characteristics of your application's actions.
Key Requirements:
loggerEnhancer: Takes the Redux store as an argument and returns a function that modifies thenextdispatch function. This modified dispatch function should log the action type and payload before calling the originalnextdispatch.performanceEnhancer: Takes the Redux store as an argument and returns a function that modifies thenextdispatch function. This modified dispatch function should record the time before and after dispatching the action, and log the duration.- Redux Toolkit: Utilize Redux Toolkit's
configureStorefunction to apply both enhancers to a store. - Typescript: The solution must be written in Typescript.
Expected Behavior:
- When an action is dispatched,
loggerEnhancershould print a message to the console in the format:Action: [action.type], Payload: [action.payload]. - When an action is dispatched,
performanceEnhancershould print a message to the console in the format:Action [action.type] took [duration]ms. - Both enhancers should work seamlessly together when applied to the store.
Edge Cases to Consider:
- Actions with no payload.
- Asynchronous actions (actions that return promises). The
performanceEnhancershould measure the time until the promise resolves or rejects. - Ensure the enhancers don't interfere with the normal dispatch process.
Examples
Example 1:
Input: Dispatch an action { type: 'INCREMENT', payload: 1 }
Output:
console.log("Action: INCREMENT, Payload: 1")
console.log("Action INCREMENT took 1ms") (duration will vary)
Explanation: The loggerEnhancer logs the action type and payload, and the performanceEnhancer logs the time taken to dispatch the action.
Example 2:
Input: Dispatch an action { type: 'FETCH_DATA' }
Output:
console.log("Action: FETCH_DATA, Payload: undefined")
console.log("Action FETCH_DATA took 50ms") (duration will vary)
Explanation: The loggerEnhancer logs the action type and undefined payload, and the performanceEnhancer logs the time taken to dispatch the action.
Example 3: (Asynchronous Action)
Input: Dispatch an action that returns a promise (e.g., a thunk that fetches data)
Output:
console.log("Action: FETCH_DATA, Payload: undefined")
console.log("Action FETCH_DATA took 200ms") (duration will vary, reflecting the fetch time)
Explanation: The performanceEnhancer should measure the time until the promise returned by the action resolves or rejects.
Constraints
- The enhancers should be pure functions, meaning they should not have any side effects other than logging.
- The performance measurement should be accurate enough to provide meaningful insights, but not so precise that it introduces significant overhead. Milliseconds are sufficient.
- The code should be well-documented and easy to understand.
- The solution must be compatible with Redux Toolkit version 2.x.
Notes
- Consider using
console.tablefor more structured logging if desired. - The
nextdispatch function is provided by Redux Toolkit'sconfigureStore. - For the
performanceEnhancer, you'll need to usePromise.resolveand.thento accurately measure the time taken for asynchronous actions.performance.now()is a good way to get high-resolution timestamps. - Focus on creating functional enhancers that are reusable and maintainable. Avoid modifying the store object directly.