Reducing screen complexity with composing wrappers
Tags: reactnative, functional, typescript
Published: 2022-12-28 14:08:32

Lately I've bem exploring new ways to organize screens in react native and this is a simple pattern to compose screens with wrappers.

The motivation for this pattern is to remove complexity from the working component and delegate the state management of components like toast, modal, loading overlay to something outside the working component.

The typical scenario where this pattern start making sense is when we have an ui component that is simply being added and managed in the working component and it's state has to be managed in every screen.

Simplified version of the problem:

```tsx
function ExampleScreen(){
  const [toastVisible, setToastVisible] = useState(false);
  return (
    <>
      <Toast title="This is a toast" visible={toastVisible} />

      <Button
       title="Show toast"
       onClick={() => setToastVisible(true)}
      />
    </>
  );
}
```

The problem with this approach IMO is that it doesn't scale well, every screen you have to repeat the state, binds and managing of that component's state. And as the number of components like this increases, it gets worse, now we have multiple of those thrown all over the screen definition.

```tsx
const [toastVisible, setToastVisible] = useState(false);
```

The solution I came up with is to replace all that with a hook that manages the state only once through a never changing context. Something like this:

```tsx
function ExampleView(){
  const { showToast } = useToast();
  return (
    <>
      <Button
       title="Show toast"
       onClick={() => showToast("This is a toast")}
      />
    </>
  );
}

const ExampleScreen = () => (
  <ToastProvider>
    <ExampleView/>
  <ToastProvider/>
);
```

I guess the philosophy of using such pattern is: it's not important how the toast is being handled, the screen concern is only to show the toast at a given moment. Usually a screen have a lot to manage on its own, so the idea is to limit the complexity to screen related things.

> Pros:
> - Reduce usage complexity
> - Easy to use apis
> - Easier to reuse code


> Cons:
> - Hide things from the working component
> - Performance might be a concern because of context api
> - Not all components can be used this way

## Collaterals: The boilerplate

As every decision in software engineering, it comes with a cost, in this case, it increases the boilerplate. Hopefully, there are ways to abstract that away from the screen.

The problem is when we have multiple of those components being used, in this case, this solution is not an improvement at all, it is as much ugly as the previous one.

```tsx
function ExampleView(){
  const { showToast } = useToast();
  const { showModal, hideModal } = useModal();
  const { showLoadingOverlay } = useLoadingOverlay();
  return (
    <>
      <Button
       title="Show toast"
       onClick={() => showToast("This is a toast")}
      />
      <Button
       title="Show modal"
       onClick={() => showToast({
        title: "Modal title",
        mainButton: {
          label: "Ok, got it!",
          onPress: hideModal,
        }
       })}
      />
      <Button
       title="Show loading"
       onClick={() => showLoadingOverlay()}
      />
    </>
  );
}

const ExampleScreen = () => (
  <LoadingOverlayProvider>
    <ModalProvider>
      <ToastProvider>
        <ExampleView/>
      <ToastProvider/>
    <ModalProvider/>
  <LoadingOverlayProvider/>
);
```

For that, we have a know solution, we can use an HOC (higher order component) with a `compose` utility to reduce some of that complexity. At first, let's create a HOC to wrap a component with the context provider.

```tsx
import React, { ComponentType } from 'react';

export function withWrapper<
  TComponentProps extends JSX.IntrinsicAttributes
>(Wrapper: ComponentType) {
  return (Component: ComponentType<TComponentProps>) =>
    (props: TComponentProps) =>
      (
        <Wrapper>
          <Component {...props} />
        </Wrapper>
      );
}
```

Now things can be done like this:

```tsx
const ExampleScreen = () => withWrapper(
  ToastProvider
)(ExampleView)
```

Or for multiple providers:

```tsx
const ExampleScreen = () => withWrapper(ToastProvider)(
  withWrapper(ModalProvider)(
    withWrapper(LoadingOverlayProvider)(ExampleView)
  )
)
```

As we can see, applying the HOC on multiple wrappers still is a bit messy. Fortunately, we can borrow the `compose` concept from functional programing to apply those function in a more elegant way.

```tsx
const ExampleScreen = () => compose(
  withWrapper(ToastProvider),
  withWrapper(ModalProvider),
  withWrapper(LoadingOverlayProvider),
)(ExampleView)
```

We can even make named HOCs for each component to make it even more readable, so the finale version would look like this:

```tsx
const ExampleScreen = () => compose(
  withToast(),
  withModal(),
  withLoadingOverlay(),
)(ExampleView);
```

In addition, the HOC can receive props to configure the inner component if necessary:

```tsx
withToast({ autoHide: true })
```

Full working example can be found a this gist: https://gist.github.com/lenilsondc/48d82c84d050e610bf94cd1c067cefa9

Finally, this is not an optimal solution for many cases and reasons, it's very experimental, and even uses the infamous HOCs, which has been losing popularity lately. Nevertheless, it's a pretty clean way of building react screens. In the end it's all  part of my journey of exploring functional programing applied to react seeking to make code look elegant.

I hope you've had a good read. Cheers!