From 37e57a516b75af390e7970075f317bb627392766 Mon Sep 17 00:00:00 2001 From: fjmorant Date: Sun, 27 Sep 2026 09:19:39 +0200 Subject: [PATCH] docs: cut the README back to what a user needs Drops the paragraph explaining why there is no bare React Native example. The absence does not need defending to someone who never knew there were two, and the archived repository points at the Expo one already. Drops the NPM_TOKEN setup from the Releasing section. It is maintainer configuration, done once, and it does not belong in a file people read to find out how to use the library. The rest is a tone pass. The sections added over the last few days argued their own design decisions at the reader: why two node types exist, why nesting lists is the arrangement React Native warns against, why levels start at 1 rather than 0, why the merge order for listProps is what it is. That reasoning belongs in the CHANGELOG, where it is already written down, and in the commits that made the decisions. A README should say what the thing does and how to use it. 48 lines in, 91 out. No documented behaviour changed. --- README.md | 139 +++++++++++++++++++----------------------------------- 1 file changed, 48 insertions(+), 91 deletions(-) diff --git a/README.md b/README.md index 217fa91..703e558 100644 --- a/README.md +++ b/README.md @@ -88,8 +88,7 @@ const data = [{title: 'Node 1', items: [{title: 'Node 1.1'}, {title: 'Node 1.2'} ### Reaching the list -`listProps` is forwarded to the underlying list, which is how you reach anything -on its surface: +`listProps` is forwarded to the underlying list: ```javascript ``` -Three groups of props reach the list, applied in this order: +Props reach the list in this order: -| Order | What | Notes | +| Order | What | | | --- | --- | --- | -| 1 | `listProps` | everything you pass, applied first | -| 2 | `extraData`, `initialNumToRender`, `style` | their own props, which win over `listProps` — but **only when you actually pass them**, so a value set through `listProps` is never erased by an absent prop | -| 3 | `data`, `renderItem`, `keyExtractor` | set by the component and not overridable. They are excluded from `IListProps`, so passing one is a compile error rather than a silent no-op | +| 1 | `listProps` | | +| 2 | `extraData`, `initialNumToRender`, `style` | win over `listProps`, and only when you pass them | +| 3 | `data`, `renderItem`, `keyExtractor` | set by the component. Excluded from `IListProps`, so passing one is a compile error | -`initialNumToRender` therefore has two spellings. Its own prop predates -`listProps` and stays supported; `listProps.initialNumToRender` is equivalent, -and the dedicated prop wins if you set both. +`initialNumToRender` has two spellings: its own prop and +`listProps.initialNumToRender`. They are equivalent, and the prop wins if you set +both. -`IListProps` is typed against `FlatList`, the default. Another `ListComponent` -with props of its own may need a cast. +`IListProps` is typed against `FlatList`. Another `ListComponent` with props of +its own may need a cast. ### NestedRow @@ -127,30 +126,19 @@ with props of its own may need a cast. | **`height`** | Fixed height for the row | number | Not required — the row sizes to its content | | **`style`** | Row container style | `StyleProp` | Not required | -#### Why levels start at 1 +#### Levels start at 1 -The nodes of `data` are at level **1**, not 0, so with the default increment a -top-level row is already indented by 10px. - -That is deliberate. `NestedRow` indents by `level * paddingLeftIncrement`, so a 0 -base would put top-level rows flush against the screen edge — changing the -appearance of every app built on the documented pattern, for no functional gain. -It is inherited from the synthetic root node the old recursive renderer wrapped -`data` in, and it is kept on purpose rather than by accident. - -If you want a flush left edge, pass `level={level - 1}` or set your own -`paddingLeftIncrement`. +The nodes of `data` are at level 1, so with the default increment a top-level row +is indented by 10px. For a flush left edge pass `level={level - 1}`, or set your +own `paddingLeftIncrement`. ### Types -Two node types are exported, because a node on the way in and a node on the way -out are not the same shape. - | Type | What it describes | | --- | --- | -| **`INode`** | a node as you write it in `data`. Every field is optional — add whatever your app needs | -| **`IRenderedNode`** | a node as `renderNode` and `onNodePressed` receive it: the `_internalId` the list assigned, and `opened` resolved to the node's current expanded state | -| **`IRow`** | one row of the flattened tree, as a custom `ListComponent` receives it in `renderItem` | +| **`INode`** | a node as you write it in `data`. Every field is optional | +| **`IRenderedNode`** | what `renderNode` and `onNodePressed` receive, where `_internalId` is a `string` and `opened` a `boolean` | +| **`IRow`** | one row of the flattened tree, as a custom `ListComponent` receives it | | **`IListProps`** | the shape of `listProps` | ```typescript @@ -165,31 +153,24 @@ const renderNode = (node: IRenderedNode, level: number, isLastLevel: boolean) => ) ``` -`getChildrenName` and `keyExtractor` are handed an `INode`, not an -`IRenderedNode`: both are called while the tree is being walked, before the list -has assigned a node anything. +`getChildrenName` and `keyExtractor` receive an `INode`. ## Performance -The tree is flattened into a single array of visible rows, and one list renders -it. Depth is a number carried on a row rather than a level of nesting in the -component tree, so: - -- there is one list, not one per node, whatever the shape of the data. Nesting - lists of the same orientation is the arrangement React Native warns against, - where windowing cannot work correctly -- the number of mounted rows is bounded by the window rather than by the size of - the tree. A 20,000-level-deep tree mounts as few rows as a flat one -- expanding and collapsing recomputes the row array instead of mounting and - unmounting lists. Rows that did not move keep their identity and are not - re-rendered -- nothing walks the tree on an ordinary render. Only a change to `data` or - `extraData` rebuilds the rows, so an inline `renderNode`, `onNodePressed` or - `getChildrenName` costs nothing - -`getChildrenName` and `keyExtractor` are read when the rows are built. If either -starts answering differently without `data` changing, change `extraData` to pick -it up. +The tree is flattened into a single array of visible rows, rendered by one list. +Depth is a number on a row rather than a level of nesting in the component tree. + +- one list, whatever the shape of the data +- the number of mounted rows is bounded by the window, not by the size of the + tree. A 20,000-level-deep tree mounts as few rows as a flat one +- expanding and collapsing recomputes the row array. Rows that did not move keep + their identity and are not re-rendered +- only a change to `data` or `extraData` rebuilds the rows, so inline + `renderNode` and `onNodePressed` callbacks cost nothing + +`getChildrenName` and `keyExtractor` are read when the rows are built. Change +`extraData` if either starts returning something different while `data` stays +the same. ### Using another list @@ -206,21 +187,17 @@ import {LegendList} from '@legendapp/list' /> ``` -Each `item` is an `IRow`, so a custom list must pass its `item` through to -`renderItem` unchanged. What else the component sets, and what `listProps` can -and cannot override, is in [Reaching the list](#reaching-the-list). +Each `item` is an `IRow`; pass it through to `renderItem` unchanged. See +[Reaching the list](#reaching-the-list) for what `listProps` can override. ### Node identity -Each node is given an `_internalId`: a path built from the node's own `id`, or -failing that its `key`, or failing that its position among its siblings. -`keyExtractor` overrides the choice. Identity is what expanded state is keyed by, -so giving nodes stable keys is what lets a node stay expanded while `data` -changes around it. +Each node gets an `_internalId`: a path built from its own `id`, or its `key`, or +its position among its siblings. `keyExtractor` overrides that. -By default a node's expanded state is forgotten once the node leaves the tree, -including while it sits inside a collapsed parent. `keepOpenedState` keeps that -state instead, so a subtree comes back expanded as it was. +Expanded state is keyed by this id, so stable keys keep a node expanded while +`data` changes around it. The state is dropped once a node leaves the tree, +including while it sits inside a collapsed parent. `keepOpenedState` keeps it. ## Examples @@ -239,13 +216,6 @@ cd -react-native-nested-listview-examples-expo npm install && npm start ``` -There is no separate bare React Native example, and there is nothing to lose by -that: this library is pure JavaScript with no native module, so it installs and -behaves identically in a bare app and in an Expo one. The -[bare examples repository](https://github.com/fjmorant/react-native-nested-listview-examples) -is archived — it targeted React Native 0.70 and no longer builds on current -toolchains, and keeping two example apps current is what let it rot. - ## Roadmap The roadmap is tracked on the [GitHub project board](https://github.com/users/fjmorant/projects/7), @@ -268,38 +238,25 @@ yarn check-package # entrypoints and types agree, across all resolution modes ### Releasing -Releasing is one button: **Actions → Publish → Run workflow**. +**Actions → Publish → Run workflow.** -It reads the version from `package.json`, refuses to go on if that version is -already tagged or already on npm, takes the release notes from that version's -CHANGELOG entry, runs the whole gate, publishes to npm with +It takes the version from `package.json` and the release notes from that +version's CHANGELOG entry, runs the checks, publishes to npm with [provenance](https://docs.npmjs.com/generating-provenance-statements), and creates the GitHub release. -The CHANGELOG entry is required. A version with no `## ` section stops -the run before anything is published, so the release and the CHANGELOG cannot -drift apart — and a rehearsal shows the notes it would publish. +**Rehearse only** is ticked by default: it runs every check and publishes +nothing. Untick it to release. -**Rehearse only** is ticked by default, so the default action of that button -publishes nothing — it runs every check and stops. Untick it to release for -real. Either way the run's summary says plainly what did or did not happen. - -Bumping the version stays a pull request, because the CHANGELOG has to be -written by a person anyway. Everything after that point is what this automates: +The run stops before publishing if the version is already tagged, already on +npm, or has no `## ` section in the CHANGELOG. ``` -# 1. a PR bumping package.json and adding the CHANGELOG entry for it +# 1. a PR bumping package.json and adding the CHANGELOG entry # 2. merge it # 3. Actions -> Publish -> Run workflow, with Rehearse only unticked ``` -Publishing needs an `NPM_TOKEN` secret on the **`production`** environment — an -npm **automation** token, since a classic token fails against an account that -requires 2FA for publishing. The job declares that environment, so its -protection rules apply: adding required reviewers there makes a real publish -something that has to be approved, and restricting *Deployment branches and -tags* limits where it can run from. - ### Trying a local build in an app Pack the library and install the tarball into the consuming app: