# Frontend style guide

TerriaJS currently uses React as the user interface framework. Some rough
guidelines are outlined below for the various parts of UI.

## MobX UI refactor

In the v8 branch we are in a transitory period of moving from css modules+sass
to styled-components to improve the theme-ability of TerriaJS.

Any new components should _only_ be using styled-components.

Any work to existing components should include an effort to remove sass from it.

The prop api for `lib/Styled/*.(t|j)sx` will be extremely unstable while we
figure out conventions that are suitable, feel free to suggest improvements or
examples of better ways 🙂

## React UI

### Global state vs local (hook / react-class-component / mobx-observable) state

The majority of the "global UI state" is located in
`lib/ReactViewModels/ViewState.ts`, however throughout the development of
version 8 you may notice things like `setState()` being used on components if
you are converting them from class components. You may be wondering if this
state is more suitable to be stored on ViewState. The answer is usually yes, and
frame the question of "can I control the entirety of essential UI state with
`ViewState` actions alone?" to think about whether this state needs to be set or
used from other parts of the UI.

A really basic example would be an extremely local state item like a hover state
of a tooltip, where it doesn't necessarily need to be known to the rest of the
app (until it does). So don't feel everything needs to be in `ViewState.ts`,
however for these cases you may benefit from the simplicity & ease of the
`useState()` hook & pattern in your functional components, rather than setting
up a class component purely to use local-component-mobx-state.

### Always use actions

For ViewState changes, avoid inlining `runInAction` & ensure any state changes
are encapsulated in an action, e.g. rather than:

```ts
runInAction(() => {
  this.props.viewState.selectedHelpMenuItem = this.props.content.itemName;
  this.props.viewState.helpPanelExpanded = true;
});
```

An action on `ViewState.ts` and calling that action.

```ts
// ViewState.ts
@action
selectHelpMenuItem(key: string) {
  this.selectedHelpMenuItem = key;
  this.helpPanelExpanded = true;
}

// Component.tsx
this.props.viewState.selectHelpMenuItem(this.props.content.itemName);
```

This additional level of abstraction means we get to more freely refactor what
`selectHelpMenuItem` does, not having to trace down every use of it across the
app but also the ability to compose actions that call a group of actions
together.

### When to use a class or function component

The React ecosystem at large heavily utilises composition, but at our model
(read: catalog items) layer we quite heavily use inheritance. For the React
layer, we will favour the use of composition, and with the introduction of
hooks, having more functional components at the UI layer makes most sense. Write
your components as function components.

### HOCs & Hooks

#### HOCs

Higher order components (HOCs) are often used across the codebase. Usually you
do not need to think about the order these are applied in. However there is one
case across terriajs - when using `react-anything-sortable` `sortable` that you
should be aware of. The <Sortable /> component expects each child to be
something wrapped with `sortable`, when you add a HOC over that, you will break
the sorting functionality - to avoid breakage, simply ensure `sortable` is the
outermost HOC.

Finally, when writing a new HOC, consider whether it would be better suited as a
hook - one of the strength of HOCs lie in the ability to apply it to any
component.

#### Hooks

React hooks are often used across the codebase to reuse functionality, and gives
us many of the benefits of HOCs without further deepening the component tree.
The most frequent problem you will run into when utilising hooks, is that they
are incompatible with React class components. This will incentivise us to both
write components as functional components, as well as convert existing class
components to them when suitable. However if you really must use hooks with a
class component, wrap the class component with a functional component and add
the hook there. All hook names should be prefixed with the word `use` so that
React can perform checks on the use of them, e.g. `useWindowSize` or
`useRefForTerria`.

Finally, when writing new hooks, consider if they would be better suited as a
HOC. Hooks need to be consumed within a component rather than wrapping a
component, but is one of its own strengths with you being able to be more
declarative about the dependencies of a component.

### Error Boundaries

Some of our `TerriaMap`s utilise https://reactjs.org/docs/error-boundaries.html
as a way of better handling UI errors, we have not yet added this to TerriaJS.
If you think of good spots to insert these for `terriajs`, please submit a
contribution!

### Testing

Try to add tests for all components, even a basic "it renders" test as seen in
`test/ReactViews/Map/Navigation/Compass/CompassSpec.tsx` can help catch runtime
errors. Some slightly longer, but still in the spirit of "it renders", can be
seen in tests like `test/ReactViews/ShortReportSpec.tsx` &
`test/ReactViews/Search/SearchBoxAndResultsSpec.tsx`.
Some UI-related tests not involving rendering can be seen at
`test/Models/parseCustomMarkdownToReactTsSpec.ts`

### Internationalisation (Internationalization / i18n)

Any strings within the application should be through a translation file - a base
English one is used under `lib/Language/en/translation.json`.

For the most part, simple strings should go through fine by calling
`i18next.t()`. There are a few notable cases where this can get tricky:

1. When loading an object (array) from translation. For example, `"aliases": "helpContentTerm1.aliases"` mapping to an array in the translation file, to
   keep things in one place when loading strings straight from config.json or
   overridden in translation overrides

2. When loading really long strings, they can get truncated, e.g. `markdown`
   striaght into `i18next.t()` will result in losing some markdown inside the
   string

## TypeScript

TypeScript should be the default choice when writing components. Lean towards
`tsx` when writing React components, with the following caveats:

- The majority of the `lib/Styled` components are not yet `tsx`, these will need
  to be (tsx-ified!) or imported via CommonJS to consume them, e.g.

```ts
const Box: any = require("../../../Styled/Box").default;
```

or

```ts
const BoxSpan: any = require("../../../Styled/Box").BoxSpan;
```

- When rapididly prototyping a UI, you may find it quicker to do this in jsx
  then tsxifying when you know what the final look is. You may want to consider
  still having some typed benefits by using logic in TypeScript (either through
  a wrapper `tsx`, or some `ts` helpers) to aid in the process.

Components written in TypeScript will not need `PropTypes` defined on them, as
type errors on props will be caught at compilation rather than a runtime check.
