-
Notifications
You must be signed in to change notification settings - Fork 3k
Redux Guidelines FAQs
This page involves some tidbits and guidelines when implementing or working with Redux in our application. If you are looking where to get started, please visit our other Redux wiki pages first:
Now that you have the gist of it from the other wikis, proceed!
We are adopting Redux on the team to utilize the benefits of structuring information in a single directional flow and maintaining state. To help establish best practices and maintain readable code, here are some guidelines.
- Views get its data through the state and states should be fairly simple. The reducer should not contain complex logic and simply should return the state for the view
- A new state is returned by dispatching action to a store, which is calculated by the reducer
- In the reducer, we should not have any
return state
and instead return a new state configured with the proper default values
- New actions should be created as classes and in it's own separate class file. For each action, there are associated action types.
- Payload for actions are created as properties on the action classes. They should be properties and not an associated value on the enum case
- Middlewares are optional and are only created if intentional.
- Some use cases for middlewares is to handle side effects of actions or include complex logic such as making network requests or reading data from storage
- Dependencies in the flow should live here
- A new action may be sent as a byproduct of an action that gets sent to the middleware, but does not have to be if we don’t need to change the view’s state
Naming Convention
- When creating action types, lean towards using user and API action names over state change names. We want our actions to read clearly on what action was taken and not the consequence of the action. The view should not know the consequences.
No view models should exist
- Redux and MVVM are two different systems that don’t really work together
- There are some cases in which we want to take the model and add some logic so that it's presentable for the view. Although this is essentially a view model, we prefer to name these classes as states.
- Business logic should be handled by the middleware
- Presentation logic should be handled by the reducer by returning the proper state
Switch Statements
- The reducer and the middleware always switch over action types
- There is a 2 lines maximum rule for the switch statement case so that they can maintain readability
No need to explicitly call main thread
- The store will automatically ensure actions are executed on the main thread so we don’t need to add another check for main thread
Call reducers explicitly
- We want to explicitly call to reducers instead of passing in the previous state
i.e
MicrosurveyPromptState.reducer(state.microsurveyState, action)
instead of state.microsurveyState
- At a minimum, state should have tests. See example here:
- Middlewares should also be tested. See example here:
How do I know which action to dispatch and where? Do I always need to dispatch an action from the middleware?
In Redux, it should simplify how we think without caring about the consequences of it. Therefore, when it comes to naming convention, we dispatch general action names from the view and the middleware dispatches middleware actions.
i.e. GeneralActionType.show
lives in the view and GeneralMiddlewareActionType.show
lives in the middleware
A dispatched action can update state in multiple places. However, we should only fire an event if needed. Therefore, we don't need to always fire an event from the middleware unless we are waiting for a response in the middleware to update the state. For example, if we're capturing telemetry, then we only need to dispatch an action to the middleware and not to be handled in the reducer. Here is a place where we were dispatching unnecessary actions and removed it in the PR.
In the middleware that's fetching the information, I try to unwrap the optional info object? Which option should I choose?
-
Option 1: in the middleware that's fetching the information, unwrap the optional info object and: a) if it exists, then dispatch the appropriate action, or b) if it doesn't exist, log a warning and dispatch an error type action for my view to handle.
-
Option 2: pass the object around through actions till the place it would need to be used, and try to unwrap it where I need it, and, if it's nil, log an error there, and try to gracefully recover by handling the data appropriately?
Choose Option 1.
What's the preferred pattern when a middleware starts to get too big?
Let's try to split the middleware up based on responsibilities.
Context: One example is our Tabs middleware is already at 1k lines, which means its too broad of a concept for one middleware to be responsible for.
Is there such thing as dispatching too many redux actions? Should views optimize for reloading content?
Don't optimize unless you run into performance issues. If we need to solve this problem, bring it up to the team and can discuss whether we want to add to our system a way to only dispatch newState to observers where the state has changed.
newState
method is being triggered for state changes relating to actions outside of the ones I care about, so we’re getting a bunch of newState
calls with identical input states, which leads to repetitiveness (eg. screen keeps appearing, animation keeps triggering)
Check whether you are resetting the state properly in cases that are not associated with the specific action you care about. Ensure that in the reducer we should always clear transient data in the default states. Therefore, we should not be using return state
and instead declare a new state with the default values, then anytime the reducer fires for any other reason the state of the field will be reset.