diff --git a/docz/docs/03-demos/14-grid.md b/docz/docs/03-demos/14-grid.md
index aa1a543..e6d45b9 100644
--- a/docz/docs/03-demos/14-grid.md
+++ b/docz/docs/03-demos/14-grid.md
@@ -151,3 +151,218 @@ a download. By setting the `filename` and `sheetname` options in the `ui-grid`
options, the output can be controlled.
+
+## Framework Lifecycle
+
+For modern frameworks like React, data grids tend to follow the framework state
+and idioms. The same `sheet_to_json` and `json_to_sheet` / `aoa_to_sheet`
+methods are used, but they pull from a shared state object that can be mutated
+with other buttons and components on the page.
+
+### React Data Grid
+
+:::note
+
+This demo was tested against `react-data-grid 7.0.0-beta.15`, React 18.2.0,
+and `create-react-app` 5.0.1 on 2022 August 16.
+
+:::
+
+[`react-data-grid`](https://github.com/adazzle/react-data-grid) is a data grid
+built for React. `react-data-grid` powers
+
+[A complete example is included below.](#rdg-demo)
+
+#### Rows and Columns state
+
+`react-data-grid` state consists of an Array of column metadata and an Array of
+row objects. Typically both are defined in state:
+
+```jsx
+import DataGrid, { Column } from "react-data-grid";
+
+export default function App() {
+ const [rows, setRows] = useState([]);
+ const [columns, setColumns] = useState([]);
+
+ return ( );
+}
+```
+
+The most generic data representation is an array of arrays. To sate the grid,
+the columns must be objects whose `key` property is the stringified number:
+
+```ts
+import { WorkSheet, utils } from 'xlsx';
+import { textEditor, Column } from "react-data-grid";
+
+type Row = any[];
+type AOAColumn = Column;
+type RowCol = { rows: Row[]; columns: AOAColumn[]; };
+
+function ws_to_rdg(ws: WorkSheet): RowCol {
+ /* create an array of arrays */
+ const rows = utils.sheet_to_json(ws, { header: 1 });
+
+ /* create column array */
+ const range = utils.decode_range(ws["!ref"]||"A1");
+ const columns = Array.from({ length: range.e.c + 1 }, (_, i) => ({
+ key: String(i), // RDG will access row["0"], row["1"], etc
+ name: utils.encode_col(i), // the column labels will be A, B, etc
+ editor: textEditor // enable cell editing
+ }));
+
+ return { rows, columns }; // these can be fed to setRows / setColumns
+}
+```
+
+In the other direction, a worksheet can be generated with `aoa_to_sheet`:
+
+```ts
+import { WorkSheet, utils } from 'xlsx';
+
+type Row = any[];
+
+function ws_to_rdg(rows: Row[]): WorkSheet {
+ return utils.aoa_to_sheet(rows);
+}
+```
+
+#### RDG Demo
+
+Complete Example (click to show)
+
+1) Create a new TypeScript CRA app:
+
+```bash
+npx create-react-app sheetjs-cra --template typescript
+cd sheetjs-cra
+```
+
+2) Install dependencies:
+
+```bash
+npm i -S https://cdn.sheetjs.com/xlsx-latest/xlsx-latest.tgz react-data-grid
+```
+
+3) Replace the contents of `src/App.tsx` with the following code. Note: a copy
+to clipboard button will show up if you move your mouse over the code. The
+notable SheetJS-specific code is highlighted below:
+
+```tsx title="src/App.tsx"
+import React, { useEffect, useState, ChangeEvent } from "react";
+import DataGrid, { textEditor, Column } from "react-data-grid";
+import { read, utils, WorkSheet, writeFile } from "xlsx";
+
+import './App.css';
+
+type DataSet = { [index: string]: WorkSheet; };
+type Row = any[];
+type AOAColumn = Column;
+type RowCol = { rows: Row[]; columns: AOAColumn[]; };
+
+/* this method returns `rows` and `columns` data for sheet change */
+const getRowsCols = ( data: DataSet, sheetName: string ): RowCol => ({
+ rows: utils.sheet_to_json(data[sheetName], {header:1}),
+ columns: Array.from({
+ length: utils.decode_range(data[sheetName]["!ref"]||"A1").e.c + 1
+ }, (_, i) => ({ key: String(i), name: utils.encode_col(i), editor: textEditor }))
+});
+
+export default function App() {
+ const [rows, setRows] = useState([]); // data rows
+ const [columns, setColumns] = useState([]); // columns
+ const [workBook, setWorkBook] = useState({} as DataSet); // workbook
+ const [sheets, setSheets] = useState([]); // list of sheet names
+ const [current, setCurrent] = useState(""); // selected sheet
+
+ /* called when sheet dropdown is changed */
+ function selectSheet(name: string) {
+ // highlight-start
+ /* update workbook cache in case the current worksheet was changed */
+ workBook[current] = utils.aoa_to_sheet(rows);
+ // highlight-end
+
+ /* get data for desired sheet and update state */
+ const { rows: new_rows, columns: new_columns } = getRowsCols(workBook, name);
+ setRows(new_rows);
+ setColumns(new_columns);
+ setCurrent(name);
+ }
+
+ /* this method handles refreshing the state with new workbook data */
+ async function handleAB(file: ArrayBuffer): Promise {
+ // highlight-start
+ /* read file data */
+ const data = read(file);
+ // highlight-end
+
+ /* update workbook state */
+ setWorkBook(data.Sheets);
+ setSheets(data.SheetNames);
+
+ /* select the first worksheet */
+ const name = data.SheetNames[0];
+ const { rows: new_rows, columns: new_columns } = getRowsCols(data.Sheets, name);
+ setRows(new_rows);
+ setColumns(new_columns);
+ setCurrent(name);
+ }
+
+ /* called when file input element is used to select a new file */
+ async function handleFile(ev: ChangeEvent): Promise {
+ const file = await ev.target.files?.[0]?.arrayBuffer();
+ if(file) await handleAB(file);
+ }
+
+ /* when page is loaded, fetch and processs worksheet */
+ useEffect(() => { (async () => {
+ const f = await fetch("https://sheetjs.com/pres.numbers");
+ await handleAB(await f.arrayBuffer());
+ })(); }, []);
+
+ /* method is called when one of the save buttons is clicked */
+ function saveFile(ext: string): void {
+ /* update current worksheet in case changes were made */
+ workBook[current] = utils.aoa_to_sheet(rows);
+
+ // highlight-start
+ /* construct workbook and loop through worksheets */
+ const wb = utils.book_new();
+ sheets.forEach((n) => { utils.book_append_sheet(wb, workBook[n], n); });
+ // highlight-end
+
+ /* generate a file and download it */
+ writeFile(wb, "sheet." + ext);
+ }
+
+ return (
+ <>
+
SheetJS × React-Data-Grid Demo
+
+ {sheets.length > 0 && ( <>
+
Use the dropdown to switch to a worksheet:
+
+
+
Current Sheet: {current}
+
+
Click one of the buttons to create a new file with the modified data
+
{["xlsx", "xlsb", "xls"].map((ext) => (
+
+ ))}
+ >)}
+ >
+ );
+}
+```
+
+4) run `npm start`. When you load the dev page in the browser, it will attempt
+to fetch and load the data.
+
+
+
+The following screenshot was taken from the demo:
+
+![react-data-grid screenshot](pathname:///react/rdg1.png)
diff --git a/docz/docs/03-demos/21-react.md b/docz/docs/03-demos/21-react.md
new file mode 100644
index 0000000..c016e1e
--- /dev/null
+++ b/docz/docs/03-demos/21-react.md
@@ -0,0 +1,179 @@
+---
+sidebar_position: 20
+title: ReactJS
+---
+
+[ReactJS](https://reactjs.org/) is a JS library for building user interfaces.
+
+This demo tries to cover common React data flow ideas and strategies. React
+familiarity is assumed.
+
+Other demos cover general React deployments, including:
+
+- [Static Site Generation powered by NextJS](./content#nextjs)
+- [iOS and Android applications powered by React Native](./mobile#react-native)
+- [React Data Grid UI component](./grid#react-data-grid)
+
+
+## Installation
+
+[The "Frameworks" section](../getting-started/installation/frameworks) covers
+installation with Yarn and other package managers.
+
+The library can be imported directly from JS or JSX code with:
+
+```js
+import { read, utils, writeFile } from 'xlsx';
+```
+
+
+## Internal State
+
+The various SheetJS APIs work with various data shapes. The preferred state
+depends on the application.
+
+### Array of Objects
+
+Typically, some users will create a spreadsheet with source data that should be
+loaded into the site. This sheet will have known columns. For example, our
+[presidents sheet](https://sheetjs.com/pres.xlsx) has "Name" / "Index" columns:
+
+![`pres.xlsx` data](pathname:///react/pres.png)
+
+This naturally maps to an array of typed objects, as in the TS example below:
+
+```ts
+import { read, utils } from 'xlsx';
+
+interface President {
+ Name: string;
+ Index: number;
+}
+
+const f = await (await fetch("https://sheetjs.com/pres.xlsx")).arrayBuffer();
+const wb = read(f);
+const data = utils.sheet_to_json(wb.Sheets[wb.SheetNames[0]]);
+console.log(data);
+```
+
+`data` will be an array of objects:
+
+```js
+[
+ { Name: "Bill Clinton", Index: 42 },
+ { Name: "GeorgeW Bush", Index: 43 },
+ { Name: "Barack Obama", Index: 44 },
+ { Name: "Donald Trump", Index: 45 },
+ { Name: "Joseph Biden", Index: 46 }
+]
+```
+
+A component will typically map over the data. The following example generates
+a TABLE with a row for each President:
+
+```tsx title="src/SheetJSReactAoO.tsx"
+import React, { useEffect, useState } from "react";
+import { read, utils } from 'xlsx';
+
+interface President { Name: string; Index: number; }
+
+export default function SheetJSReactAoO() {
+ /* the component state is an array of presidents */
+ const [pres, setPres] = useState([]);
+
+ /* Fetch and update the state once */
+ useEffect(() => { (async() => {
+ const f = await (await fetch("https://sheetjs.com/pres.xlsx")).arrayBuffer();
+ // highlight-start
+ const wb = read(f); // parse the array buffer
+ const ws = wb.Sheets[wb.SheetNames[0]]; // get the first worksheet
+ const data = utils.sheet_to_json(ws); // generate objects
+ setPres(data); // update state
+ // highlight-end
+ })(); }, []);
+
+ return (
Name
Index
+ { /* generate row for each president */
+// highlight-start
+ pres.map(pres => (
+
{pres.Name}
+
{pres.Index}
+
))
+// highlight-end
+ }
+
);
+}
+```
+
+### HTML
+
+The main disadvantage of the Array of Objects approach is the specific nature
+of the columns. For more general use, passing around an Array of Arrays works.
+However, this does not handle merge cells well!
+
+The `sheet_to_html` function generates HTML that is aware of merges and other
+worksheet features. React `dangerouslySetInnerHTML` attribute allows code to
+set the `innerHTML` attribute, effectively inserting the code into the page:
+
+```tsx title="src/SheetJSReactHTML.tsx"
+import React, { useEffect, useState } from "react";
+import { read, utils } from 'xlsx';
+
+export default function SheetJSReactHTML() {
+ /* the component state is an HTML string */
+ const [html, setHtml] = useState("");
+
+ /* Fetch and update the state once */
+ useEffect(() => { (async() => {
+ const f = await (await fetch("https://sheetjs.com/pres.xlsx")).arrayBuffer();
+ const wb = read(f); // parse the array buffer
+ const ws = wb.Sheets[wb.SheetNames[0]]; // get the first worksheet
+ // highlight-start
+ const data = utils.sheet_to_html(ws); // generate HTML
+ setHtml(data); // update state
+ // highlight-end
+ })(); }, []);
+
+ // highlight-next-line
+ return ( );
+}
+```
+
+### Rows and Columns
+
+Some data grids and UI components split worksheet state in two parts: an array
+of column attribute objects and an array of row objects. The former is used to
+generate column headings and for indexing into the row objects.
+
+The safest approach is to use an array of arrays for state and to generate
+column objects that map to A1-style column headers.
+
+The [React Data Grid demo](./grid#rows-and-columns-state) uses this approach
+with the following column and row structure:
+
+```js
+/* rows are generated with a simple array of arrays */
+const rows = utils.sheet_to_json(worksheet, { header: 1 });
+
+/* column objects are generated based on the worksheet range */
+const range = utils.decode_range(ws["!ref"]||"A1");
+const columns = Array.from({ length: range.e.c + 1 }, (_, i) => ({
+ /* for an array of arrays, the keys are "0", "1", "2", ... */
+ key: String(i),
+ /* column labels: encode_col translates 0 -> "A", 1 -> "B", 2 -> "C", ... */
+ name: XLSX.utils.encode_col(i)
+}));
+
+```
+
+![Column labels for headers](pathname:///react/cols.png)
+
+
+
+## Legacy Deployments
+
+[The Standalone Scripts](../getting-started/installation/standalone) play nice
+with legacy deployments that do not use a bundler.
+
+[The legacy demo](pathname:///react/index.html) shows a simple React component
+transpiled in the browser using Babel standalone library.
\ No newline at end of file
diff --git a/docz/docs/03-demos/index.md b/docz/docs/03-demos/index.md
index fd9cf3e..74dc5e7 100644
--- a/docz/docs/03-demos/index.md
+++ b/docz/docs/03-demos/index.md
@@ -21,7 +21,7 @@ The demo projects include small runnable examples and short explainers.
- [`Angular.JS`](./legacy#angularjs)
- [`Angular 2+ and Ionic`](https://github.com/SheetJS/SheetJS/tree/master/demos/angular2/)
- [`Knockout`](./legacy#knockout)
-- [`React and NextJS`](https://github.com/SheetJS/SheetJS/tree/master/demos/react/)
+- [`React`](./react)
- [`VueJS`](https://github.com/SheetJS/SheetJS/tree/master/demos/vue/)
### Front-End UI Components
@@ -68,3 +68,9 @@ The demo projects include small runnable examples and short explainers.
- [`vite`](./bundler#vite)
- [`webpack`](./bundler#webpack)
- [`wmr`](./bundler#wmr)
+
+:::note
+
+If a demo for a library or framework is not included here, please leave a note.
+
+:::
diff --git a/docz/static/react/cols.png b/docz/static/react/cols.png
new file mode 100644
index 0000000..e5c4204
Binary files /dev/null and b/docz/static/react/cols.png differ
diff --git a/docz/static/react/index.html b/docz/static/react/index.html
new file mode 100644
index 0000000..d75e8d4
--- /dev/null
+++ b/docz/static/react/index.html
@@ -0,0 +1,158 @@
+
+
+
+
+
+
+ SheetJS React Demo
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/docz/static/react/pres.png b/docz/static/react/pres.png
new file mode 100644
index 0000000..f2d4311
Binary files /dev/null and b/docz/static/react/pres.png differ
diff --git a/docz/static/react/rdg1.png b/docz/static/react/rdg1.png
new file mode 100644
index 0000000..bd4ded0
Binary files /dev/null and b/docz/static/react/rdg1.png differ