How to Build Large Scale React Apps

Adam L Barrett

Scale

Team Scale

Some of the challenges that come along with working across multiple teams on one product or shared components

  • Communication
  • Interpersonal Relationships
  • Process
  • Consistency
  • Complexity

​

Team Scale

Some of the challenges that come along with working across multiple teams on one product or shared components

  • Communication
  • Interpersonal Relationships
  • Process
  • Consistency
  • Complexity

​​

To build Large Scale Web Apps

Create a common language to aid communication

Set out some patterns and rules which will

  • aid with consitency
  • optimize for change
  • fight complexity

Use tools to help us stick to those patterns and prevent human mistakes

The Agenda

πŸ‘©β€πŸ”¬ High Level Concepts

πŸ‘·πŸ½β€β™€οΈ Details and implementations

πŸ‘¨πŸ»β€πŸ’» Adam's Personal Preferences

πŸ‘©β€πŸ”¬

High Level

The Secret to Building Large Scale Apps…

Complexity

How do you fight complexity?

Many strategies simply move the complexity around, one of the only true ways to reduce complexity is abstraction, or black-boxing

Black Box Abstraction

Front-End Architecture

Layers and Adapters

Persistence

Logic

Interface

Data

Application

Presentation

Model

Controller

View

Storage

UI

Domain

Storage

Domain Logic/State

UI

Storage

UI

Domain Logic/State

πŸ‘©β€πŸ”¬ High Level Concepts

Let's Review what we said so far...

  • Don't Build Big Apps
  • Fight Complexity
  • Abstraction / Black Box
  • Architecture
  • Storage / Domain / UI

​

πŸ‘·πŸ½β€β™€οΈ

Details and Implementations

Storage

Domain

UI

APIs and Platform

Stores

React Components

Domain
Components

APIs and Platform

Stores

UI Components

Services

Domain
Components

VS

Domain
Components

UI
Components

Domain Components

  • are awareΒ of your domain entities
    • your Products, Carts, Orders, Accounts, etc
  • do NOT render HTML elements
  • connect the store state to the UI
  • convert UI Actions/Events into DOMAIN actions/Events

UI
Components

  • are unawareΒ of domain entities
  • may be stateful
  • render HTML and styles
  • emit UI actions/events
    • clicks, selects, input
  • render based on the data provided to them
const Documents = ({ userId }) => {
  const [
    { documents },
    { create, update, destroy }
  ] = useDocuments({ userId });

  return (
    <ItemList
      items={documents}
      onCreate={() => create({ documentData: {} })}
      onUpdate={data => update({ documentData: data })}
      onDelete={document => destroy(document.id)}
    />
  );
};

Domain Component

const ItemList = ({ items, onCreate, onUpdate, onDelete }) => {
  return (
    <>
      {items.map(item => {
        <div key={item.id}>
          <Item
            name={item.name}
            onChange={data => onUpdate(data)}
          />
          <button onClick={() => onDelete(item)}>X</button>
        </div>;
      })}
      <AddButton onClick={() => onCreate()} />
    </>
  );
};

UI Component

const PaymentsLayout = ({ children }) => {
  const nodes = React.Children.toArray(children);
  return (
    <Grid padded>
      <Grid.Row columns="2">
        <Grid.Column>{nodes[0]}</Grid.Column>
        <Grid.Column>{nodes[1]}</Grid.Column>
      </Grid.Row>
      <Grid.Row>
        <Grid.Column>{children.slice(3)}</Grid.Column>
      </Grid.Row>
    </Grid>
  );
};

Layout Components

const Payments = ({ loan }) => (
  <PaymentsLayout>
    <NextPaymentsTable loan={loan} />
    <>
      <NumberOfPaymentsTable loan={loan} />
      <PartialPaymentTable loan={loan} />
    </>
    <PastDueTable loan={loan} />
  </PaymentsLayout>
);

Domain Using Layout Component

Domain Components

  • are awareΒ of your domain entities
    • your Products, Carts, Orders, Accounts, etc
  • do NOT render HTML elements
  • connect the store state to the UI
  • convert UI Actions/Events into DOMAIN actions/Events

UI
Components

  • are unawareΒ of domain entities
  • may be stateful
  • render HTML and styles
  • emit UI actions/events
    • clicks, selects, input
  • render based on the data provided to them

APIs and Platform

Stores

React Components

Services

Domain
Components

The Pieces

UI: UI Components (React Components)

Adapter: Domain components (also React Components)

Domain Logic/State: Stores

Adapter: Services (Class instances, functions orΒ  simple objects)

Storage: APIs and PlatformΒ (REST + GraphQL endpoints, etc)

Personal Preferences

UI ComponentsΒ - Function Components, Hooks, Styled Components

Domain componentsΒ - Custom Hooks / Providers

StoresΒ - RxJS BehaviorSubjects (or Redux + Redux-Observable)

ServicesΒ - Simple classes that return promises or RxJS observables

APIs and PlatformΒ - GraphQL and localForage

UI Component

const AlarmClock = ({ title, state, onSnooze, onDismiss }) => (
  <>
    <h3>{title}</h3>
    <div className="display">{state}</div>
    <button className="snooze" onClick={onSnooze}>
      Snooze
    </button>
    <button className="dismiss" onClick={onDismiss}>
      Dismiss
    </button>
  </>
);

Domain Component

export const CreditCardForm = () => {
  const [{ values, errors, status } = {}, dispatch] = useStore(CreditCardStore);

  return (
    <FormComponent
      values={values}
      errors={errors}
      status={status}
      onSubmit={e => {
        e.preventDefault();
        dispatch(PAY);
      }}
      onChange={e => dispatch(UPDATE, { [e.target.name]: e.target.value })}
      onBlur={e => e.target.name && dispatch(TOUCH, e.target.name)}
    />
  );
};

Store

const createStore = (action$, creditCardService) => {
  const values$ = action$.pipe(ofType(UPDATE), map(creditCardService.validate));

  const status$ = action$.pipe(
    ofType(actions.PAY),
    exhaustMap(({ values }) => {
      from(creditCardService.pay(values)).pipe(
        mapTo({ status: 'success' }),
        catchError(error => of({ message: error.message }))
      );
    })
  );

  return combineLatest(values$, errors$, status$, (values, errors, status) => ({
    values,
    errors,
    status
  }));
};

Services

class creditCardService {
  construtor({ apiUrl }) {
    this.apiUrl = apiUrl;
  }

  async pay(data) {
    const response = await fetch(this.apiUrl, { body: JSON.stringify(data) });
    return response.data;
  }
  
  async validate(ccData) {
    await checkValidNumber(ccData);
    checkIsExpired(ccData);
  }
};

APIs and Platform

Stores

React Components

Services

Domain
Components

Benefits

Design Systems

Design Systems

Designer and Developers create a single source of truth and common languageΒ for UI components across products and applications

Design Systems

Pro-Tips:

  • This is where your
    UI ComponentsΒ live​
  • Devote a team to it
  • Have a process for change
  • Split visual and behaviour
  • Consider Stencil

πŸ’‘

Monorepos & Modern Tools

Monorepos &
Modern Tools

The 4 main areas that can have the biggest impact on your organizations ability to deliver great software are:

  • Source code management
  • Dependency management
  • Promoting best practices
  • Automation​

Β 

Β 

npx create-nx-workspace myorg
npx create-nx-workspace myorg

npx: installed 180 in 7.764s
? What to create in the new workspace 
  angular           [a workspace with a single Angular application] 
  angular-nest      [a workspace with a full stack application (Angular + Nest)]
 
❯ react             [a workspace with a single React application] 
  react-express     [a workspace with a full stack application (React + Express)
] 
  next.js           [a workspace with a single Next.js application] 
(Move up and down to reveal more choices)

...

cd myorg

Nrwl's Nx

apps directory

  • holds all your applications
  • also has e2e test apps (Cypress)
  • this is where your Domain Components live

libs directory

  • for creating shared libraries like services and stores
  • your design system and UI Components

tools directory

  • for automating across teams
npx nx g @nrwl/workspace:lib my-shared-lib
npx nx dep-graph
nx affected:test
nx affected:build
npx nx g @nrwl/react:storybook-configuration my-shared-react-lib
npx nx e2e my-app-e2e

Linting / Prettier

  • developer consistency (and bugs)

Β 

Unit Tests

  • developer code design and correctness

Β 

Integration Tests

  • developer refactor with confidence

Β 

E2E/Functional Tests

  • prevent bugs/regressions ensure correctness

JavaScript Testing Trophy

Nx allows you to

  • Easily create applications and work with them using a consistent set of commands
  • Verify that a code change is safe
  • Extract shared libraries
  • Create code generators to save time and effort
  • Enforce best practices

Details and implementations

Let's Review what we said so far...

  • React Application Layers
    • UI Components
    • Domain Components
    • Store(s)
    • Services
  • Design System
  • Awesome Monorepo with Nx​

πŸ‘·πŸ½β€β™€οΈ

Details and implementations

Let's Review what we said so far...

  • Nx Workspace
    • Apps
      • Domain Components
      • E2E Tests
    • Libs
      • UI Components
      • Store(s)
      • Services
    • Tools

πŸ‘·πŸ½β€β™€οΈ

πŸ‘¨πŸ»β€πŸ’»

Personal Preferences

No exceptions!

TypeScript

RxJS

Whenever you code...

  • Make it work

  • Make it right

  • Make it fast

Your Iteration Schedule

  • Week 1: Prototype Sprint

  • Week 2: Integration / Optimization

  • Week 3: Cool Down and Planning

Recap

APIs and Platform

Stores

React Components

Services

Domain
Components

How to Build Large Scale React AppsΒ 

Questions?

How to Build Large Scale React Apps

By Adam L Barrett

How to Build Large Scale React Apps

This talk is about the fundamental principles for managing the complexity of large front-end applications at scale. It is not about scaling your deployments (i.e. operations), but rather the trouble of scaling front-end development across multiple teams, with strategies for front-end architecture, code organization, predictability and fighting complexity.

  • 8,837