These are my notes from the following video
Common Pitfalls
1 - Bloated Stores
Start from a reasonable standpoint that nothing should be in the store unless it proves itself to be in the store.
Rather than to put everything in the store and pull it out once it becomes problematic.
Use the SHARI Principle and the DUGSA principle
DUGSA ->
- D - Dependent - data that is needed for actions and effects
- U - Unique - data can not be derived from other data. If data can be derived it should be a selector
- G - Global - data needed everywhere
- S - Serializable
- A - Atomic
...Here is how Atomic relates
The idea is that sometimes multiple state variables are related, but no one of them is derivable from any other one. People expend a lot of effort trying to keep these variables in sync with each other, and often have bugs when they don't succeed.
A classic example is when working with forms, people might track whether the form is: dirty, valid, submitting, submitted, canceled, rejected, etc.
These states are not necessarily derivable from each other, but often change in concert with each other = they are not atomic.
In such circumstances, this might point to the presence of another variable that IS atomic, from which our data can be derived, and reduces the complexity of our store.
In this case, we can look to a state machine. More information on state machines here:
https://css-tricks.com/robust-react-user-interfaces-with-finite-state-machines/
So in our form example, we could store the state of our form state machine in our state and update that in our reducers.
Then, we use selectors to get back all the variables we needed before based on the single state machine state variable.
Liquid error: internal
2 - Too Few Selectors
- Prefer specific selectors. Make sure to compose selectors
- Use selectors to map data
- Use selectors to create view models
const getFileViewModel = (file: FileUploadModel): FileViewModel => ({
id: file.id,
fileName: file.fileName,
formattedFileSize: getFormattedFileSize(file.fileSize),
canRetry: file.status === FileUploadStatus.Failed,
canDelete: file.status !== FileUploadStatus.Completed,
statusIcon: getFileViewModelIcon(file.status),
statusColorClass: getFileViewModelColorClass(file.status),
showProgress:
file.status === FileUploadStatus.InProgress &&
file.progress &&
file.progress >= 0,
progress: file.progress,
errorMessage: file.status === FileUploadStatus.Failed && file.error
});
3 - Command-Based Actions
Actions are Events Not Commands*
Do not dispatch actions as if they were commands E.g. "Save Result" or "Open Modal". Instead, think about it as what buttons the user clicked, what text did they enter, what flow did they began.
DRY - Don't Repeat Yourself
When this is done in NgRx create a situation that acts counter to the intention of the architecture. Repeating and reusing to much code in NgRX can actually lead to code which has a lot of regression and is more difficult to maintain
AHA Programming - Avoid Hasty Abstraction
Actions should be unique, a user clicking a certain button vs a similar button in a different view are unique events. They are similar in what they will trigger but they are unique in the context where they occur.
It is cheap to make actions
If one event occurs it should be linked to one action
If somebody submits a form to put some to-do sent they might dispatch actions to:
1) Post the to-do
2) Open a toast
3) Go to the dashboard
function submitFormCommands({todo}){
this.store.dispatch(postTodo());
this.store.dispatch(openToast('Form Submitted));
this.store.dispatch(navigateTo('Dashboard));
}
This will require to have effects that post the to do, that open the toast and that navigate to the dashboard.
This is making some of your code and your actions much harder to understand and your flows much harder to understand because if you ever arrive at the open toast action it is harder to find how many different places is this used? where is it dispatched from? what if I want to change what happens when a toast is opened?
Eg. to have another step that has happened before, is it ok to have it every single place that I'm showing a toast to have that occur.
When you make your actions super super super specific (and using commands) you lose that functional programming/declarative style and the ability to change and adapt. This is changing it to an imperative mode since it is specified exactly what needs to happen and removes all that flexibility.
Again you go back to having things which are tightly coupled but they're tightly coupled through the indirection of NgRx which is the worst of both worlds.
Instead, this should dispatch a single action and all the different steps should be handle in the effect.
function submitFormCommands({todo}){
this.store.dispatch(todoSubmitted());
}
Struggling? Try Event Storming
Go through the UI and find what are all the things that can be treated as an action
4 - Beware Effect Dominoes
Effect dominoes are actions that are dispatch which triggers effects that dispatch action and so on.
Instead, Leverage Independent Effects and handle them independently
- Every effect does a specific task *. We have taken off the parts which are both independent and generic and split them off into their own effects.
What happens with multiple effects that interact concurrently with the same data?
It can be refactored into a single effect if they, in fact, rely on the same data
How to deal with dependency order for example when one effect need to happen after the other?
By adding more action that signifies that the effect has completed, and tied the completion of that effect to an action. If they are more interconnected, they can be modeled as a single effect.
5 - Beware of Effect Based Selector
The following example is subscribing to the store.
There is no easy way to trace what actions modified the state. This can also trigger that effect starts happening not in purpose when some state changes
Make the effect based on events
6 - Overly Smart Components - Make Fewer, Dumber Components
Subscribe to the store in few places as possible
It is tempting to use the selector to get data, but then this will create a phantom dependence on all the components.
Components can be purer if they are given the data and rendering accordingly, instead of depending on the store.
Links
wesleygrimes / managing-file-uploads-with-ngrx
Managing File Uploads w/ NgRx
RealWorldApp
Run the app: npm run serve-with-api
Download the presentation: ManagingFileUploadsWithNgRx.pdf
Further help
Visit the Nx Documentation to learn more.
https://github.com/ngrx/platform/tree/master/projects/example-app
Top comments (1)
Thanks for the article. The header of #5 is mixed:
5 - Beware of Effect Based Selector
should be
5 - Beware of Selector Based Effect