Skip to main content

Future Direction

This is currently the plan for where we are heading. A more flat structure, with modules that own their state and components.

src
├── constants
│   ├── general.js
│   └── routes.js
├── modules
│   ├── feature/*
│   └── api/*
├── state
│   ├── history.js
│   ├── middleware
│   ├── root.js
│   └── store.js
├── theme
│   ├── icons
│   ├── images
│   └── styles
├── utils
├── hooks
├── components
│   ├── atoms
│   ├── molecules
│   └── organisms
└── utils

The component library should contain on stateless (AKA dumb)

Refactors

A series of refactors are required to fix some of the problems we've introduced over the years. A cleaner code base with consistent naming contentions, obvious patterns and documentation for common tasks will help front-end devs move faster with less cognitive overload and mistakes.

Fetching Data

There are currently three patterns for fetching data.

  1. When a component is mounted (EG: fetch a list of leases when the lease listing component is mounted).
    This is fine for the time being but React 18 in strict mode during development will call useEffect twice which will result in double API requests. There are workarounds like debouncing, using switchMap in our API epics or caching, but a better approach would be to fetch data on the routing level. React Router v has this functionality and is a much better pattern.
  2. A component state changes (EG: Date range changes)
    There are places where useEffect is used to trigger subsequent API requests when state or props change. This should be done as a last resort if there is no appropriate event handler. The general rule of thumb is to use event handlers where possible.
  3. As a side-effect of another successful response to retrieve more data that is required to render the UI. (EG: fetch parties when a portfolio is opened because the portfolio object doesn't include the owner or tenant names and we need those to render the UI).

Accessing State

Currently we leverage the context API (providers) as a convenience method to add a bunch of state that every component in the providers' sub-tree has access to. While very convenient this is generally bad as described under Context Problems and Pain Points. This was done initially because it can be difficult to know where to find the required state in the redux store and helped us move a bit faster and share common state patters between components.

This can be refactored while maintaining a level of abstraction that alleviates the need to intemately understand the global state structure. We can do this by Colocating State and creating new feature specific selectors and composing selectors from other modules to derive whatever state we may need for our component(s). Composition also encourages smaller units which leads to greater code reuse and better test coverage.

Example of abusing providers to share state

A prime example of abusing the provider pattern is the InvoicesPageContainer. The container injects it's props into the InvoicesProvider so that all children components have access. This was a workaround to prevent refactoring at the time, but has resulted in confusing code that is not performant or easy to maintain.

Whenever a value in the InvoicesPageContainer changes the InvoicesProvider is re-rendered which means that all it's children are re-rendered.

How to refactor

I've uploaded a video of the process to Google Drive.

Fully Typing All API Modules

We are now running TypeScript but the code base is still littered with $TSFixMe and any.

$TSFixMe is an alias for any so we can find them easier and so it's more obvious that it needs to be typed.

type $TSFixMe = any

The first major push to fully type our code base should be in the API modules. We have automated the generation of Scala case class => TypeScript types in the phoenix build process. This means that we don't need to manually write all the types/interfaces but we do need to integrate them in the API modules.

How to refactor

We should only be accessing global state at the lowest possible level in the component tree. We can do this using selectors directly in components and abstracting common patterns into custom hooks for better reusability.

Potential problems with this refactor

  • Class based components can't use hooks, so they'll need to be wrapped with connect directly. I'd prefer to refactor all class based components to functional components, but that is another refactor for another time.

Class based components

For the sake of consistency and so we can leverage hooks, it would be cleaner to refactor all class based components to functional components. Class based components were created before function components were a thing. There are currently 30+ class based components with a handful of complex ones.

How to refactor

I've uploaded a video to Google Drive. Also read https://medium.com/@MilkMan/read-this-before-refactoring-your-big-react-class-components-to-hooks-515437e9d96f

Replacing Lodash with Ramda

...