Introduce React Incrementally - egnomerator/misc GitHub Wiki

Introduce React Incrementally

What's this "incremental" stuff about?

We typically think "build a React App" rather than "use the JavaScript library React"

  • we typically think of SPAs
  • we typically think in terms of "A React App"--the whole app is a tree of React components

If using React, do we always have to write a "React App"? ... What if ...

  • Existing App:
    • what if you have an existing web app and want to start using React in it?
    • maybe re-write the app?
      • let's say in our scenario that isn't an option (sometime it is, sometimes it isn't)
  • No Experience:
    • what if everyone on your team has little to zero React knowledge/experience?
    • maybe learn React and then re-write the app?
      • let's say in our scenario that isn't an option (sometime it is, sometimes it isn't)
  • Convert and Keep Delivering Features:
    • what if you are on a team that needs to convert the app to a React App over time, while continuing to deliver new features to the existing app?
    • maybe let part of the team focus on new features, and part of the team focus on the re-write in parallel?
      • let's say in our scenario is continuing to deliver new features is a high enough priority that this approach would reduce new feature productivity a little too much for the business to accept this
      • let's say the business considers converting to React a lower priority that can take longer so that delivering new features can stay a little closer to current levels of productivity
        • note: "stay a little closer" meaning that the business does accept some reduced productivity of new features

Solution: Introduce React Incrementally

  • this enables the development team to have control
    • when to use React (in terms of sprint planning)
    • how much to use React (how large of a chunk of UI/functionality)
  • Existing App:
    • the team can begin using React by either adding new features as React components, converting existing features into React components, or both
  • No Experience:
    • the team can start really small
    • this can reduce risk of introducing bugs
    • this can enable the team to use React in "digestible" amounts
    • the team can build confidence over time as they increasingly use React at a safe pace
  • Convert and Keep Delivering Features:
    • by controlling when and how much to use React, the team can incorporate this into sprint planning
    • starting small can
      • enable new feature productivity to continue (reduced to an acceptable level)
      • while enabling increasing the use of React over time

Okay Cool! How?

A Specific Approach to Use React Incrementally in a .NET Core Web App

Prerequisites

  • NVM (optional) - i recommend using NVM to install/uninstall multiple Node.js versions and easily switch between versions
  • Node.js (recommend LTS version; recommend all team members on same version)
    • the version i used for the reference starter app (below) is v14.17.5 with NPM v6.14.14
  • NPM (this is installed as a tool that comes with Node.js)

Helpful Reference Solution

This reference solution is ready to be cloned and ran.

the solution: https://github.com/egnomerator/LibReactComponentsStarter

  • there's a readme to help get started with the solution
  • has the minimum NPM packages to support
    • ES6 modules, React, JSX, TypeScript, Jest unit testing, and watch mode (for unit testing and/or debugging)
    • a solid build process that is consistent across build scenarios
  • has the minimum NPM packages as prod dependencies (just React)
  • uses the folder and file structure that i've like most after looking at different common approaches
  • defines a React components API for the rest of the app to use (webpack exposes it globally)
  • demonstrates how to pass dependencies to React via this components API
  • demonstrates how to enable React to use an Ajax library without having to use another NPM package
    • it's common that React apps use an NPM package like axios for Ajax calls
    • i prefer to minimize the use of NPM dependencies
    • so i defined an object with a specific set of web API Ajax calls and passed this to React
      • this web API object uses jQuery Ajax--so React is using jQuery for its Ajax calls

Setting up the React components API

A key part of the above solution is global API that provides the ability to render an area of the page via React

source: https://github.com/egnomerator/misc/wiki/React-NonSPA-Strategies#how-to-use-react-in-a-non-spa-application---option-classic-scripts--a-global-libraryapi-exposing-react-module-scripts

  • this source is where i explain how i'm setting up the API for the rest of the app to use
  • the reference solution demonstrates this approach

Project Root

(the reference app is already setup with these steps)

  • add config files to Project Root folder (so at same folder level as the wwwroot folder)
    • package.json
    • package-lock.json
    • webpack.config.js
    • babel.config.js
    • tsconfig.json (for IDE intellisense support)
      • tsconfigbld.json (for build process type-checking)
      • tsconfigdts.json (for build producing declaration files--i don't have plans to use this though)
      • note that these 2 files for "bld" and "dts" use the "extends" property referencing the main tsconfig.sjon file
        • using this extends property means, use the referenced config but override with any differences specified in this file
  • install NPM packages
    • IF already have a package.lock file, use npm ci to install
    • otherwise i recommend
      • install all npm packages with the --save-exact flag
      • definitely install all devDependencies with the --save-dev flag
  • update .csproj (incorporate JavaScript build process into .NET build process)
  • add wwwroot folder to the Project Root folder if not already there
    • this step is probably not needed--the folder is probably already in the project
  • create a wwwroot/app folder
  • and the folder/file structure under wwwroot/app folder (see next sections for folder/file structures)

The folder/file structure

(the reference app is already setup with this folder/file structure)

Top level folder: wwwroot/app with 2 sub-folders

  • sub-folder: wwwroot/app/dist (JavaScript build output will be generated under this folder)
  • sub-folder: wwwroot/app/src (source code in here)

The following structure under wwwroot/app/src:

  • wwwroot/app/src/index.js (top-level API--ClientApp)
  • wwwroot/app/src/components (React components in here)
  • wwwroot/app/src/components/index.js (API to React components--ClientApp.Components)
  • wwwroot/app/src/components/FeatureName (organize by Feature--containing one or many components)
    • there will be a folder for each component in this feature
  • wwwroot/app/src/components/FeatureName/FeatureName
    • not a typo
    • this is the "root" component for this feature--name it the same as the feature name
  • ... remaining components in the feature
  • remaining features

React Feature folder/file structure

(the reference app is already setup with this folder/file structure)

A Feature

Feature: "TableCrudEditor"

  • let's imagine we have a feature in our app for a table that provides CRUD operations
  • the feature is called "TableCrudEditor"

Let's apply the folder structure for our feature--just the feature folder and the folder for each component in the feature:

  • wwwroot/app/src/components/tableCrudEditor (feature folder)
  • wwwroot/app/src/components/tableCrudEditor/TableCrudEditor (root component for this feature)
    • this component could define the structure (using child components) of this area in the web UI
    • this component could make Ajax calls and pass data to child components to display
    • this component could define the high-level functions of the table--the CRUD functionality; refresh, etc.
    • this component could maintain the state for this feature define how to operate on this state
  • wwwroot/app/src/components/tableCrudEditor/CreateItem
    • this could be a component that contains input elements and a button that triggers creation of a new item
  • wwwroot/app/src/components/tableCrudEditor/TableCrud
    • this could be the actual Table component that shows the items (rows)
    • this table could use child components for its editable rows
  • wwwroot/app/src/components/tableCrudEditor/EditableTr
    • this could be the row component (can be many of these in a table)
    • this row could define cells as normal HTML td tags
    • this row could additionally use a child component for a special cell that edits the row
  • wwwroot/app/src/components/tableCrudEditor/TrEditorCell
    • this could be the special cell that edits the row
    • it could contain buttons for editing, deleting, or viewing details of the item

React Component folder/file structure

(the reference app is already setup with this folder/file structure)

Capitalization: React component folders names are Pascal case--because React component names must be Pascal case

root component vs. the child-components

  • Root
    • the root component should be the orchestrator of the feature
    • it will define the structure of this area of the web UI
    • it will perform Ajax calls if needed, it will pass state, props, and functionality down to child components
    • it will maintain and operate on the state of the feature
  • child
    • the child components should have very limited if any state, props, and functionality

note: by "module" i mean JavaScript module (e.g. es6 modules)

Root React Component (complex)

  • wwwroot/app/src/components/tableCrudEditor/TableCrudEditor (component folder)
  • wwwroot/app/src/components/tableCrudEditor/TableCrudEditor/index.js
    • this is the actual component, it is a JavaScript class
    • this component will use an instance of workflows class and pass itself as a reference
  • wwwroot/app/src/components/tableCrudEditor/TableCrudEditor/props.js
    • separate props/state into their own modules to help keep file sizes smaller/readable
  • wwwroot/app/src/components/tableCrudEditor/TableCrudEditor/state.js
    • separate props/state into their own modules to help keep file sizes smaller/readable
  • wwwroot/app/src/components/tableCrudEditor/TableCrudEditor/webApi.js
    • if Ajax is a part of this feature, define a clean API for the web requests and separate this into its own module
  • wwwroot/app/src/components/tableCrudEditor/TableCrudEditor/workflows.js
    • this is a JavaScript class
    • this module needs a reference to the component it operates on
    • this module will define the high-level functionality of the component
    • this module will operate on the component's state
    • this module imports activities and tasks (next)
  • wwwroot/app/src/components/tableCrudEditor/TableCrudEditor/activities
    • example activity files
      • /activities/createItem.js
      • /activities/editItem.js
      • /activities/resetState.js
    • each activity module handles a step or set of steps in a workflow
    • activities have some knowledge of context--the context is the React component they operate on
    • an activity module could potentially import another activity module
      • example:
        • when creating an item, you might want to clear the fields automatically
        • similarly, you might want to do this when editing an item
        • that resetState.js could represent this
          • it might take a number of steps to check existing state and make specific needed changes
        • createItem.js and editItem.js could import the resetState.js module
    • an activity module will likely import one or more task modules (next)
  • wwwroot/app/src/components/tableCrudEditor/TableCrudEditor/tasks
    • example task files
      • /tasks/getCopyOfItems.js
      • /tasks/updateMatchingItem.js
    • a task has one function and requires what it needs as arguments
    • unlike activities, tasks don't know about the component they are working for
      • a task requires the minimum information to do its work
      • a task does not get a reference to the React component
    • a task does not import other tasks

NOTE (about the workflow/activity/task organization):

  • this is just a way to organize the functionality--there are plenty of other organization approaches
  • this is similar to an approach i learned from a co-worker that our team has found very helpful

Child React Component (simple)

  • wwwroot/app/src/components/tableCrudEditor/EditableTr (component folder)
  • wwwroot/app/src/components/tableCrudEditor/EditableTr/index.js
    • this is the actual component
  • wwwroot/app/src/components/tableCrudEditor/EditableTr/props.js
    • only if needed--very often, a component does not need to have props or state
  • wwwroot/app/src/components/tableCrudEditor/EditableTr/state.js
    • only if needed--very often, a component does not need to have props or state
⚠️ **GitHub.com Fallback** ⚠️