Building ****** Finder 2.0

My experience with React & Co in a large-scale application

 

src
├── screens
│   └── App
│       ├── components
│       ├── screens
│       │   ├── Admin
│       │   │   ├── components
│       │   │   ├── screens
│       │   │   │   ├── Reports
│       │   │   │   │   ├── components
│       │   │   │   │   └── Reports.screen.tsx
│       │   │   │   └── Users
│       │   │   │       ├── components
│       │   │   │       └── Users.screen.tsx
│       │   │   ├── shared
│       │   │   │   └── helpers
│       │   │   │       └── some.helper.ts
│       │   │   └── Admin.screen.tsx
│       │   └── Course
│       │       ├── components
│       │       ├── screens
│       │       │   └── AssignmentsList
│       │       │       ├── components
│       │       │       └── AssignmentsList.screen.tsx
│       │       └── Course.screen.tsx
│       ├── shared
│       │   └── components
│       │       ├── Avatar.component.tsx
│       │       └── Icon.component.tsx
│       └── App.tsx
├── shared
└── index.ts
  • mirrors route configuration
  • screens act as containers
  • multiple /shared folders for each section
  • easy maintenance and reusability
  • long readable filenames
  • avoid non-descriptive "index.js"
  • suffix helps a lot

File naming

VehicleList.screen.tsx
VehicleListFilter.container.tsx
VehicleListFilter.component.tsx
vehicleListFilter.scss
VehicleListFilterChips.component.tsx
preventTouchClickMouseEmulation.helper.ts

CSS Modules + SASS

$breakpoints: (
  xxs: 0px,
  xs: 480px,
  s: 768px,
  m: 992px,
  l: 1200px,
  xl: 1600px
);

$brand-blue: #15577e;

$weight-light: 300;

$font-size-display: (
        xxs: (
                font-size: 1.900rem,
                line-height: 2.375rem,
                font-weight: $weight-light
        ),
        xs: (
                font-size: 2.000rem,
                line-height: 2.500rem,
                font-weight: $weight-light
        )
);

Our own responsive grid library

typed interfaces instead of prop-types

interface PropsInterface {
    count: number;
    options: Array<string>;
    label?: string | JSX.Element;
    type?: 'default' | 'primary' | 'thin';
    entity: AsyncModel<InquiryDetails>;
    filterKey: keyof OfferFilter;
}

const SomeComponent: React.SFC<PropsInterface> = (props) => {
    return ...
};

swagger-codegen for models

export interface OfferDetails {
    "financingEnabled"?: boolean;
    "includedWarranty"?: string;
    "leaseAnnualMileage"?: string;
    "leaseDownPayment"?: PriceView;
    "leaseDuration"?: string;
    "leaseRate"?: PriceView;
    "leasingEnabled"?: boolean;
    "maintenanceBeforeDeliver"?: boolean;
    "marketPlace"?: OfferMarketPlaceView;
    "offerKey": string;
    "price"?: PriceView;
    "registrationBeforeDelivery"?: boolean;
    "totalPrice"?: PriceView;
    "vehicle": VehicleOfferDetails;
    "warrantyOffers"?: Array<WarrantyOfferView>;
}

swagger-codegen for API

create(params: { "create": VehicleCreate;  }, options?: any): (fetch?: FetchAPI, basePath?: string) 
    => Promise<VehicleDetails> {
        const fetchArgs = VehicleApiFetchParamCreator.create(params, options);
        return (fetch: FetchAPI = isomorphicFetch, basePath: string = BASE_PATH) => {
            return fetch(basePath + fetchArgs.url, fetchArgs.options).then((response) => {
                if (response.status >= 200 && response.status < 300) {
                    return response.json();
                } else {
                    throw response;
                }
            });
        };
    }

typings-for-css-modules-loader

.attribute-circle {
  ...
  &-icon {
    ...
  }
  &--inner {
    ...
  }
}

attributeCircle.scss

attributeCircle.scss.d.ts

export const attributeCircle: string;
export const attributeCircleInner: string;
export const attributeCircleIcon: string;

Autocomplete for classnames

Using nonexistent/renamed classnames results in typescript warning

"Dead" class detection

Webpack configuration

  • bootstrapped using create-react-app
  • typescript flavour
  • aliases for shorter import statements
  • happypack
import FlexItem from '../../../../../../../../shared/components/...'
import FlexItem from 'shared/components/...'

react-responsive for complex responsive functionality

react-intl for i18n

Infinite scrolling and Back button

admin interface

  • CRUD
  • > 40 business objects
  • consistent UI and data structure (API and DTOs)
  • consistent URL scheme

 

  • base screens and components with common interface -> TypeScript FTW!!!!1!!111!!111eleven
  • heavy use of TypeScript generics
  • no Redux (too much overhead, not much benefits)

list

filter

  • React form components
  • filter definitions from server (visible, possible options, validation, current value)

table

  • column definitions
  • paging
  • sorting (server side)
  • links to view, edit, delete
  • loader container
  • filter values, paging and sorting in URL -> deep links, browser navigation
  • data loading
  • flash messages for error and success

list component structure

 ModelGenerationListScreen

  •  AdminEntityListScreen
    • ModelGenerationFilter
      • AdminEntityFilter
        • form components
    • ModelGenerationList
      • PagedAdminEntityTable
        • AdminEntityTable
          • headers
          • rows
          • columns
          • cell renderers
        • Pager
        • LoaderContainer
    • ModelGenerationDeleteConfirmation
      • DialogConfirmation

 

ModelGenerationListScreen extends AdminEntityListScreen

  • ModelGenerationFilter extends AdminEntityFilter
    • form components
  • ModelGenerationList extends PagedAdminEntityTable
    • AdminEntityTable
      • cell renderers
      • columns
      • rows
      • headers
    • Pager
    • LoaderContainer
  • ModelGenerationDeleteConfirmation extends DialogConfirmation

list component structure

excursus

React components with Generics

generic React component

works but no warning in IDE / compiler

generic React component

won't work (JSX syntax error)

generic React component

real world example

list component structure

view

  • simple properties
  • linked references to other business objects
  • i18n properties
  • link to delete and edit
  • loader container
  • data loading    
  • flash messages for error and success

view component structure

 ModelGenerationViewScreen

  •  AdminEntityViewScreen
    • ModelGenerationView
      • Rows
      • Columns
      • Property Renderer
      • AdminL10NViewRow
      • LoaderContainer
    • ModelGenerationDeleteConfirmation
      • DialogConfirmation

 

ModelGenerationViewScreen extends AdminEntityViewScreen

  • ModelGenerationView
    • Rows
    • Columns
    • Property Renderer
    • AdminL10NViewRow
    • LoaderContainer
  • ModelGenerationDeleteConfirmation extends DialogConfirmation

no extends in React!

view component structure

create / edit

  • form components for simple properties, i18n properties or linked references
  • form validation
  • form field definition from server (visible, readonly, possible options, validation, current value)
  • data loading and saving
  • flash messages for error and success

 

create component structure

 ModelGenerationCreateScreen

  •  AdminEntityCreateScreen
    • ModelGenerationForm
      • Rows
      • Columns
      • Form components
      • AdminL10NFormRow
      • LoaderContainer

ModelGenerationCreateScreen extends AdminEntityCreateScreen

  • ModelGenerationForm
    • Rows
    • Columns
    • Form components
    • AdminL10NFormRow
    • LoaderContainer

no extends in React!

edit component structure

 ModelGenerationEditScreen

  •  AdminEntityEditScreen
    • ModelGenerationForm
      • Rows
      • Columns
      • Form components
      • AdminL10NFormRow
      • LoaderContainer

ModelGenerationEditScreen extends AdminEntityEditScreen

  • ModelGenerationForm
    • Rows
    • Columns
    • Form components
    • AdminL10NFormRow
    • LoaderContainer

no extends in React!

create component structure

edit component structure

wiring it together

  • CRUD screen for every business object with subscreens for list, view, edit, create
  • constistens URL scheme
  • component structure
    • ModelGenerationsScreen
      • AdminEntityCRUD
        • ModelGenerationListScreen
        • ModelGenerationViewScreen
        • ModelGenerationEditScreen
        • ModelGenerationCreateScreen

 

directory structure

directory structure

Then God blessed them and said, "Be fruitful and multiply. Fill the earth and govern it. Reign over the fish in the sea, the birds in the sky, and all the animals that scurry along the ground."

Book of Genesis 1:28

benefits

  • ca. 1h per business object
    (copy & paste & search & replace & modify)
  • very short ramp up time for new developers
  • refactoring very easy
    (and done very often)
  • backend modifications can be detected
  • consistent UI and URL scheme

codeStyleSettings.xml

Problems

nonexistent typescript typings

  • Webpack incremental build performance
  • Large action and reducer files
  • shouldComponentUpdate
  • Redux + TypeScript = even more boilerplate
  • dirty hack for React components with TypeScript generics
  • no tests

Would we use this stack again?

Geschafft!

Building ****** Finder

By Sergey Ryzhov

Building ****** Finder

  • 1,159