Skip to content

bothrs/global-state-management-rnd

Repository files navigation

[R&D] Global State Management @bothrs

[Chapter R&D] Notion Docs

Table of contents

What problem are we solving and why?

we don't know which one brings the best developer experience, with the must pragmatic approach (giving us flexibility in different code bases)

What happens if we don't solve this problem

We don't have a default when starting projects, leaving it up to the moment, and risking the stack diverging

Global state with React Context

Official Docs -- React Context
[PR #1] React Context experiment -- Vercel Preview

  • βœ…Β Official React API, not going anywhere in the future
  • βœ…Β No extra concepts to learn if already familiar with React
  • βœ…Β Actively maintained as part of React by Meta
  • βœ…Β Often used under the hood by other state management libraries (optimised by them though)
  • βœ…Β Automatically picked up out of the box by React Devtools
  • πŸ€”Β Still needs to be paired with other official React hooks to turn it into a state manager
  • πŸ€”Β Bare bones / β€œbuild your own solution” still leaves room for different interpretations
  • πŸ€”Β Can cause issues during SSR where server & browser state get out of sync
  • πŸ€”Β Context API can be slow at times, often requires memoization in large apps [Docs]
  • πŸ‘Β Performance issues might get solved with React's new useSelectedContext hook

Global state with Recoil

Official Docs -- Recoiljs.org
[PR #2] React Context experiment -- Vercel Preview

  • βœ… Straightforward DX, feels like using regular React state
  • βœ… Separating read-only & read-write APIs
  • βœ…Β Minimal overhead on top of normal hooks
  • βœ…Β Isolates global state into atoms to avoid unnecessary / unrelated re-renders
  • βœ…Β Promoted / maintained by (devs working for) Meta
  • βœ…Β 269,471 weekly downloads [NPM]
  • βœ…Β Actively maintained πŸ‘‰ (bi-)weekly updates & releases [GitHub]
  • πŸ€”Β Not an official API though, might fall out of flavour / tank in popularity eventually


Supports Next.js Supports Vercel Deployments Supports Netlify Deployments Test in Expo GO Supports Expo iOS Supports Expo Android Docs with Storybook

This project was bootstrapped with Aetherspace, the Evergreen repo setup for all your full-stack cross platform app development needs {...πŸ’š} Enabling the project to be built for Web, iOS, Android, PWA, Static, SSR, REST and GraphQL all at once πŸ‘‡

Getting Started ⚑️

Install packages: yarn install

Run with docs: yarn dev

Run on web & mobile: yarn dev-mobile


βœ… Aetherspace, GREEN stack & Template Benefits? πŸš€

Aetherspace - GREEN stack starter template for cross platform React app development

Table of contents

πŸ’š - What is the GREEN stack?
πŸš€ - What is Aetherspace?
πŸ€– - Why start with a turbo/monorepo?
πŸ“ - File structure and installing new packages.
πŸ‘Ύ - Benefits and next steps.
πŸ€·β€β™‚οΈ - When not to use the GREEN stack.
πŸ“š - Relevant Docs.

What the hell is the GREEN stack? πŸ’š

In short GREEN stands for these 5 core technologies:

  • GraphQL for typed and self documenting APIs
  • React-Native and React-Native-Web for write-once UI
  • Evergreen components that run anywhere (as well as Electron)
  • Expo for easy web + mobile dev, deploys and testing
  • Next.js for SEO, Static Exports, API, SSR & Web-Vitals

The core idea this tech stack enables you to achieve boils down to writing your app code or features once with Javascript and React, yet make it available on any platform or device without double implementations or the need for different development teams.

It allows you to move fast, save time and deliver more πŸŽ‰

Think of it as Unity for React Apps. Just like Unity aims to make cross console game development a lot easier for (indie) game devs, the GREEN stack aims to do the same for cross-platform app development.

How does 'Aetherspace' help, exactly? πŸš€

Aetherspace is an opinionated framework I've made that fills in the gaps of working and building with the GREEN stack:

  • How should I handle responsive design?
  • How do I avoid SSR layout shift when react-native styling does not support media queries or classnames?
  • How can I expose / read public env vars across multiple platforms?
  • Wait, how do I take advantage of next/image on the web when that's not available in React-Native?
  • What's the best way to style and animate my UI elements for both web and mobile?

Just to name a few.

While the stack itself is very powerful, figuring out how to get set up and do certain things in a write-once way can be frustrating and time consuming. To save you time figuring it all out on your own, Aetherspace contains a bunch of packages, utils and best-practices to set you up for a free and easy ride to cross-platform success.

Aetherspace is also fully optional. Usage of the UI primitives, React hooks and JS utils provided by packages/aetherspace is recommended but not required.

Provided you throw out the examples and edit some helper scripts in the package.json files, you could even delete the package entirely and still be left with a great GREEN stack starter.

More on Aetherspace in the πŸ‘Ύ Benefits and Next steps section or AETHERSPACE.md and CODEGEN.md.

But why start with a turbo/monorepo? πŸ€–

One very annoying thing about figuring stuff out on your own is when packages you're using require custom configuration for webpack, babel or otherwise. It often happens that updating e.g. a single babel.config.js used for both React-Native and Next.js will fix usage on either, but then break the other.

Using a monorepo with different entry points for Next.js and Expo allows us to keep configs more seperate, and therefore allow more confident updating of packages and configs without accidentally breaking other platforms.

In this starter template, we've opted to use turborepo with yarn workspaces. We'll list some basics in the next section, but for a deeper understanding please refer to their documentation for more info.

πŸ“ File structure and package management πŸ“¦

This starter monorepo has two types of workspaces:

  • /apps/* for all expo & next.js versions of your apps
  • /packages/* for all shared dependencies / library code used in multiple apps
β”œβ”€β”€ apps/
β”‚   └── {app-name}/ πŸ‘‰ Where all cross-platform code for {app-name} lives
β”‚       └── components/ ➑️ Molecules / Atoms / Common UI used in 'screens/'
β”‚       └── graphql/ ➑️ Shared code for the GraphQL API client (optional)
β”‚       └── resolvers/ ➑️ Shared resolvers used in both REST or GraphQL API
β”‚       └── screens/ ➑️ Page templates used in App.tsx and next.js's 'pages/' directory
β”‚       └── package.json ➑️ config required by yarn-workspaces, no dependencies
β”‚
β”‚   └── {app-name}-expo/ πŸ‘‰ Where all Expo & mobile specific config for {app-name} lives
β”‚       └── app.json ➑️ Expo app config (e.g. landscape / tablet support)
β”‚       └── App.tsx ➑️ Mobile Entrypoint & Navigation Setup (using '{app-name}/screens/')
β”‚       └── babel.config.js ➑️ Babel transpilation config for Expo
β”‚       └── index.js ➑️ Mobile entrypoint loader for App.tsx
β”‚       └── metro.config.js ➑️ Metro bundler config for react-native
β”‚       └── package.json ➑️ yarn-workspace config, lists expo & non-next.js dependencies
β”‚       └── tsconfig.json ➑️ Typescript config for Expo
β”‚       └── webpack.config.js ➑️ Enables PWA browser testing with Expo (no SSR)
β”‚
β”‚   └── {app-name}-next/ πŸ‘‰ Where all Next.js, Server & API config for {app-name} lives
β”‚       └── public/ ➑️ favicon, app icons & other static assets (e.g. images & fonts)
β”‚       └── src/
β”‚           └── pages/ ➑️ directory based routes (using '{app-name}/screens/')
β”‚               └── api/ ➑️ directory based api routes (using '{app-name}/resolvers/')
β”‚                   └── graphql.ts ➑️ GraphQL client from '{app-name}/graphql/'
β”‚               └── _app.tsx ➑️ App Layout Wrapper (e.g. headers / footers / navigation)
β”‚               └── _document.tsx ➑️ HTML wrapper for head, body & meta tags (+ SSR styles)
β”‚               └── index.tsx ➑️ Homepage (e.g. using '{app-name}/screens/HomeScreen.tsx')
β”‚       └── babel.config.js ➑️ Babel transpilation config for Next.js
β”‚       └── next.config.js ➑️ Next.js config, modules to transpile & plugins to support
β”‚       └── package.json ➑️ yarn-workspaces config, lists ONLY next.js dependencies
β”‚       └── tsconfig.json ➑️ Typescript config for Next.js
β”‚
β”œβ”€β”€ packages/
β”‚   └── @aetherspace/ ➑️ Primitives, utils & helpers for working with the GREEN stack
β”‚   └── @config/ ➑️ list of ts & other configs to use / extend from in next or expo apps
β”‚   └── @scripts/ ➑️ scripts that help streamline things like codegen & managing assets
β”‚   └── {comp-lib}/ πŸ‘‰ Code shared across apps, ideally same structure as 'apps/{app-name}'
β”‚       └── package.json ➑️ yarn-workspace config, doesn't need deps unless published
β”‚
β”œβ”€β”€ node_modules/ ➑️ Contains all modules for this monorepo
└── package.json  ➑️ Root yarn-workspaces configuration + helper scripts, no deps
πŸ’‘ `{app-name}` & `{comp-lib}` are just placeholders and you **can** have multiple of these

πŸ“¦ Keep your apps seperate with /apps/* workspaces:

For every app you're building in this monorepo, you'll need a few folders:

  • /apps/app - Where most of your app's UI, logic and Screens will live. Shouldn't have any dependencies.
  • /apps/app-next - Entry for web where only next.js related config/setup for an app should live. Should list only next.js related dependencies & polyfills.
  • /apps/app-expo - Entry for mobile where only expo related config/setup for an app should live. Should list all react(-native) and non next.js related dependencies.

In each of these folders own package.json file, a name property should be specified to identify that workspace. This name can then be referenced during installs via e.g.

yarn workspace app-next add next-images
yarn workspace app-expo add moti

It's also advised to see app workspaces as fully seperate from other apps:

For example, /apps/app should not import or reference anything from /apps/some-other-app. If you do need to embed a certain screen or component from one app in another, it's best to extract it to its own shared library workspace instead (toggle below for info πŸ‘‡)

πŸ’‘ `/packages/*` workspaces for e.g. component libraries

Write shared library code in /packages/* workspaces:

Packages aim to provide common building blocks or logic for both apps and other packages. They do not need to differentiate between entry points with /packages/...-next and /packages/...-expo.

Like /apps/ workspaces, they do also require their own package.json and name, and installing dependencies can work exactly the same:

yarn workspace component-library add -D @types/react

However, unless you will be publishing the package to NPM, it may be best to just install any dependencies in the consuming apps' /apps/{app-name}-next or /apps/{app-name}-expo workspace instead.

A good example of a library package usable by multiple app workspaces in this monorepo is the /packages/aetherspace workspace. It contains UI primitives like AetherView, AetherImage & AetherLink that are small wrappers for & recommended over react-native's own View, Text & Image components.

πŸ‘Ύ Stack and Template benefits + Next steps πŸ‘Ύ

If you've read the sections above, It's likely the ease of use, time saving capabilities and scalability of this stack & template are clear.

The starter repo comes with some opinionated extra packages and abilities.
Here's a list of what you can start doing out of the box:

  • Link pages and screens cross platform with expo-next-react-navigation or <AetherLink>
  • Use tailwind to style UI responsively on web / mobile with <AetherView tw="sm:px-2"> / tailwind-rn
  • Animate UI elements with <AetherView.Animated> / react-native-reanimated / moti
  • Add illustrations or icons with react-native-svg
  • Bring the power of GraphQL to JSON or REST apis with aetherResolver() and Schemas.
  • Add auth with AuthSession (Expo Examples)
  • Document your components and APIs with Storybook.
  • Deploy to vercel with yarn deploy or vercel --prod --no-clipboard (view live)
  • Deploy to netlify via this guide (view live)

If you'd like to continue learning about Aetherspace and the GREEN stack, there are more detailed guides, tips and best-practices in:

  • AETHERSPACE.md, CODEGEN.md, NAVIGATION.md & API.md (Aetherspace & Codegen)
  • STYLING.md, ANIMATING.md & DOCUMENTING.md (GREEN stack How-tos)

πŸ’Ό Why this makes sense from a business perspective πŸ’Έ

Whether you're a startup or established company, having both web and mobile apps is a great competitive advantage. There are many stories of market leaders suddenly being overtaken because the competition were able to move faster or had more devices their solution was available on for their customers.

This stack makes it near effortless to enable extra platforms. It helps keep teams small and enables them to move fast when building new pages or features for phones, tablets and/or the web.

More deliverables for less time invested in turn means flexibility in one or more of these areas:

  • ... negotiation room about budget or deadlines (in case of client work)
  • ... πŸ’° to be distributed among the entire team
  • ... πŸ•— available for experimentation
  • ... budget available to market the product
Show full πŸ•—πŸ•— to πŸ’°πŸ’°πŸ’° Comparison

Let's talk Return on Investment:

πŸ•— = time required = devs / teams / resources invested
πŸ’° = deliverable sale value = costs to build + profit margin
ROI = πŸ•— -> sold for -> πŸ’°

Web only project ROI = πŸ•—πŸ•— -> πŸ’°πŸ’°

  • πŸ•— Web Front-End πŸ’°
  • πŸ•— General Back-End (REST / GraphQL + Templates / SSR) πŸ’°

Native iOS + Android project ROI = πŸ•—πŸ•—πŸ•— -> πŸ’°πŸ’°πŸ’°

  • πŸ•— iOS App with Swift πŸ’°
  • πŸ•— Android app with Java πŸ’°
  • πŸ•— API Back-End (REST / GraphQL) πŸ’°

React-Native Mobile App ROI = πŸ•—πŸ•— -> πŸ’°πŸ’° to πŸ’°πŸ’°πŸ’°

  • πŸ•— iOS + Android App with RN πŸ’°(πŸ’°)
  • πŸ•— API Back-End (REST / GraphQL) πŸ’°

Expo Mobile + PWA ROI = πŸ•—πŸ•— ->πŸ’°πŸ’° to πŸ’°πŸ’°πŸ’°πŸ’°

  • πŸ•— iOS + Android + PWA with Expo & RN (Web without SSR) πŸ’°(πŸ’°πŸ’°)
  • πŸ•— API Back-End (REST / GraphQL) πŸ’°

Now, things get really interesting when you try to compare full cross-platform apps

Full Cross Platform with Separate Dev Teams ROI = πŸ•—πŸ•—πŸ•—πŸ•—πŸ•—πŸ•—πŸ•—Β ->Β πŸ’°πŸ’°πŸ’°πŸ’°πŸ’°πŸ’°πŸ’°

  • πŸ•— Web Front-End πŸ’°
  • πŸ•— iOS App with Swift πŸ’°
  • πŸ•— Android app with Java πŸ’°
  • πŸ•— Windows App Dev Team πŸ’°
  • πŸ•— MacOS App Dev Team πŸ’°
  • πŸ•— Linux App Dev Team πŸ’°
  • πŸ•— API Back-End (REST / GraphQL) πŸ’°

Full Cross Platform with GREEN stack ROI = πŸ•—πŸ•—Β -> πŸ’°πŸ’° to πŸ’°πŸ’°πŸ’°πŸ’°πŸ’°πŸ’°πŸ’°

  • πŸ•— Web (PWA & SSR & Web Vitals) + iOS + Android + Windows + MacOS + Linux πŸ’°(πŸ’°πŸ’°πŸ’°πŸ’°πŸ’°)
  • πŸ•— Back-End (REST + GraphQL + SSR + Static Exports + ISSG + universal JS utils thanks to Next.js) πŸ’°

Key takeaway: Always upsell more plaforms / devices the app could run on


When not to use the GREEN stack? πŸ€·β€β™‚οΈ

The GREEN stack is unlikely to be the best fit when your project...

  • ... will always be web only πŸ‘‰ Use next.js
  • ... will always be mobile only πŸ‘‰ Use Expo
  • ... will always be desktop only πŸ‘‰ Use Electron + React / Vue / Svelte
  • ... is very Bluetooth / AR / VR / XR heavy πŸ‘‰ Go native with Swift / Java
  • ... is a console game πŸ‘‰ Use Unity instead
  • ... is not using React πŸ‘‰ Use Svelte / Vue + Ionic
  • ... has no real need for Server Rendering, SEO or Web-Vitals πŸ‘‰ Use Expo (+ Web Support)
  • ... is using React, but the project is too far along and has no budget, time or people to refactor πŸ€·β€β™‚οΈ

If your project has required dependencies / SDKs / libraries that are either not available in JS, are not extractable to API calls or cannot function cross-platform, this may also not be a good solution for your use-case*.

πŸ›  * However, for JS libs, you could always try adding cross platform support yourself with `patch-package`

πŸ“š Relevant docs: