Skip to content

New Coding Manual

alisman edited this page Feb 28, 2023 · 9 revisions

State Management

Mobx philosophy

Single source of truth. Everything that can be computed, should be computed, and in such a way that it will automatically recompute when its underlying inputs change.

Store state vs component state Keep data/state/derived computations that are shared across features of an application in a global page store. Put feature specific and ephemeral state in "local" component state.
API loaded data should almost always be kept in global stores.

We need to come up with a better way to break up massive stores into smaller stores of concern. This is not trivial.

runInAction, @action Render cycles are synchronous so you need to batch updates

@computed as a caching mechanism

MakeMobxView

Class components need to call makeObservable in constructor in order to make observable decorators kick in (this is due to a change in js decorator implementation). Mobx has reluctantly deprecated decorators but you can still use them if you add special configuration.

In functional components, we can achieve local observable properties and computed getters using the useLocalObservable hook.

React component style

Class components vs functional (w/ hooks)

New components should use functional style. Refactor when you can.

Almost all components should be made observers.. This allows Mobx to isolate renders in leaf components without needlessly re-rendering parents.

Bootstrap and React-Bootstrap

We are stuck on Bootstrap 3.4.1 (documentation). Upgrading to 4 would be a herculean task and I don't see any reason to do it. We use React-Bootstrap which gives us some interactive component (e.g. dialogs and tabs). You can either use these components or, when you want more control and customization, just use Bootstrap markup.

We adopted CSS modules a couple years after starting project so there is still a lot of legacy CSS (SASS actually). CSS modules allow us to avoid name collisions without having to adopt painful class name conventions. You know you are using CSS modules when you are editing a file [name].module.scss. Be aware that you have to import these into your components and then reference them in className attributes. Also, be aware that your classes will be munged on output. This is how uniqueness is enforced. But it means you won't see your nice human-readable class names in the DOM.

Functional programming style

The idea of functional programming is to isolate your business logic in easily-tested, pure functions which are then invoked by control code responsible for execution flow and state mutation.

As a rule of thumb, avoid for loops (do while, etc). Instead use operations like map/reduce/filter which can be chained. Use Lodash when necessary.

No:

const output = [];
for (i=0;i>arr.length;i++) {
    if (arr[i].type === SOMETYPE) {
        output.push({ name: arr[i].first + ' ' + arr[i].last });
    }
}

Yes:

const output = arr.filter(el=>el.type === SOMETYPE)
                  .map(el=>{ return { name: el.first + ' ' + el.last } })

Data loading and loading status

Data loading should be done in remoteData (a factory for MobxPromise). MobxPromise models the state of loading process (isPending, isComplete, isError, result).

We strive to never reference result property unless we've checked that isComplete is true. It would be nice if typescript could do this for us, in the way that it does e.g. for undefined, but it cannot.

You want to check loading status of your promises (this is what actually fires the loading process) outside of your feature components so that you can show either the loading spinner OR the feature. This is analogous to React's Suspense system. This allows your feature components to avoid knowledge of loading state. They can accept the data as properties.

Use MobxView to load multiple MobxPromises (remoteData) and handle their loading state in the aggregate.

Clone this wiki locally