Skip to content

Imperative Shell Functional Core architecture applied to a React application

Notifications You must be signed in to change notification settings

adamborowski/react-arch-demo

Repository files navigation

Listing Repositories

Requirements

  • search GitHub repositories using GraphQL API
  • show loading and error states
  • pay attention to UX, ie. Search enabled only if the request would result in changes
  • be able to easily integrate with Fetch API
  • support multiple backend schemas (ie. legacy backend schema and modern schema)
  • easy testability in unit testing and Storybook environments, without a need of mocking mechanisms
  • easy exploration of various use cases exposed by easily accessible Storybook stories
  • full typescript and runtime type safety
  • demonstrate imperative shell, functional core architecture (details below)

For comparison, simpler version is available here

Verifications

Local development.

First, copy .env to .env.local file and provide your GitHub developer token.

Then you can start with

npm start

The repository also has configured commitlint and prettier and eslint git hooks.

Build & lint

In order to check typescript or eslint error, run the following command:

npm run build --ci

Tests

Unit tests can be executed with this command:

npm test

End-to-end tests are held by Chromatic, and are available through online console. You can access them from GitHub Actions and Pull Request check links or the Invite link.

See Online Chromatic Storybook.

GitHub Actions

This repository contains following github actions run on master`s push and every PR to master branch.

Component-Driven development

You can start storybook and see important component use cases and interactive scenarios.

npm run storybook

Please see Client In Memory Mock story for dynamic scenario (Play feature)

Architecture

My main goal in this task is demonstrate the specific implementation of the architectural pattern called functional core, imperative shell I found pretty simple and useful for medium-sized applications.

Disclaimer: for performance questions regarding simple top-down props passing, I can refer to react-fast-context for example.

In this demo application I separated view, interaction and IO and allow them to be easily substituted.

View layer consists of pure components that rely mainly on props and do not have any business level state. It can be composed of other components with separate responsibilities: pure page, layout, data display.

Then we have state layer which cares about async interactions and holds the state. This usually composes custom interaction hooks (separated from UI) and pure view component

Last layer is the client layer which provides the actual implementation for every IO operation.

In addition to that, we have component concepts:

  • Display component, i.e. RepositoriesEmptyMessage, RepositoriesList
  • Layout component, i.e. RepositoriesSearchPageLayout which cares about css layouts
  • Page pure component which usually represents one big UI feature / routing page and is pure (no state and side effects), i.e. RepositoriesSearchPagePure
  • Page connected component which integrates pure page with special interaction hook (state, side effects), i.e. RepositoriesSearchPageConnected

Connected Page component is likely to accept only I/O clients as props.

Client is a set of functions that start IO operations and return a promise.

The goal of the client concept is to easily change the IO implementation depending on the use case. We can have separate client for storybook or testing (in memory client), or fetch, graphql, local storage - for production. We also can have adapters that allow to use one client that accepts data in a specific shape and transforms into a local structure. This enables easy switching between backend versions that may differ in the schema. After all, we can use zod, the powerful typescript-first schema library that allows us for very easy runtime validation of I/O data structures.

In addition to that we can have higher order clients (withFailing, withDelay) that make it very simple to reproduce scenarios that are hard to reproduce in development environments.

Thanks to that we don't need to mock or hack our code at all! And in combination with storybook, we can have all popular use cases available to reproduce in one menu.

What's new to me

  • first time using Chakra UI, previous experience with:
    • Ant Design
    • Material Design
    • Cloudinary Design System (maintainer, will be open sourced)
    • Semantic UI
    • React-Bootstrap
  • first time using GraphQL
    • using raw GraphQL at the thinnest layer
    • not using react-query or Apollo
      • I wanted to present the clean and simple architecture,
      • it requires that we have an abstract "client" interface that is promise-based

What's not included

  • Pagination is not implemented, it loads first 50 records.
    • my existing virtual scroll with data fetching example (search for "spider" and scroll) with source
  • Not doing browser checking, prepared for newest Google Chrome
  • Routing - would use react-router but this time it would be small ROI for one page
  • also didn't integrate with popular solutions like depcheck or syncpack or snyk

Project generated using Create React App,

About

Imperative Shell Functional Core architecture applied to a React application

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published