loading...
Cover image for Notes from Advanced NgRx

Notes from Advanced NgRx

alfredoperez profile image Alfredo Perez ・5 min read

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.

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.

Image with the example code of effect domino

Instead, Leverage Independent Effects and handle them independently

Image with the example code for independent effetcs

  • 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.

Example of an effect that subscribes to a 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

https://twitter.com/robocell

https://github.com/ngrx/platform/tree/master/projects/example-app

Discussion

markdown guide