• Stars
    star
    346
  • Rank 118,068 (Top 3 %)
  • Language
    JavaScript
  • License
    MIT License
  • Created over 7 years ago
  • Updated about 5 years ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

Walk a React (or Preact) element tree, executing a "visitor" function against each element.

Update

I haven't been focusing on this library as I tend to reach for Next.js for my SSR needs. If anyone is interested in maintaining the library further I am happy to add you as a collaborator to the project.

As an alternative I can highly recommend react-ssr-prepass, developed by @kitten of Formidable Labs, which provides the same functionality with built in support for suspense.

Disclaimer

This library does not operate in an idiomatic manner against React. It makes some assumptions about the internals of React and makes calls against Components directly. This is a risk as it likely to break with future releases of React, i.e. the upcoming Suspense release.

Personally, I've found this library helpful in providing me with a solution for my server side rendering data fetching needs. That being said I very much look forward to being able to move over to Suspense as soon as it is stable and avoid having to use hacks/workarounds such as this library.

Please consider carefully before adopting this library. If you are happy to take on the risk I would recommend you write an abstraction over it that will allow you to easily remove/replace it from your codebase with Suspense or another more idiomatic solution.


react-tree-walker 🌲

Walk a React (or Preact) element tree, executing a "visitor" function against each element.

npm MIT License Travis Codecov

TOCs

Introduction

Inspired/lifted from the awesome react-apollo project. 😗

This modified version expands upon the design, making it Promise based, allowing the visitor to return a Promise, which would subsequently delay the tree walking until the Promise is resolved. The tree is still walked in a depth-first fashion.

With this you could, for example, perform pre-rendering parses on your React element tree to do things like data prefetching. Which can be especially helpful when dealing with declarative APIs such as the one provided by React Router 4.

Illustrative Example

In the below example we will create a visitor that will walk a React application, looking for any "class" component that has a getData method on it. We will then execute the getData function, storing the results into an array.

import reactTreeWalker from 'react-tree-walker'

class DataFetcher extends React.Component {
  constructor(props) {
    super(props)
    this.getData = this.getData.bind(this)
  }

  getData() {
    // Supports promises! You could call an API for example to fetch some
    // data, or do whatever "bootstrapping" you desire.
    return Promise.resolve(this.props.id)
  }

  render() {
    return <div>{this.props.children}</div>
  }
}

const app = (
  <div>
    <h1>Hello World!</h1>
    <DataFetcher id={1} />
    <DataFetcher id={2}>
      <DataFetcher id={3}>
        <DataFetcher id={4} />
      </DataFetcher>
    </DataFetcher>
    <DataFetcher id={5} />
  </div>
)

const values = []

// You provide this! See the API docs below for full details.
function visitor(element, instance) {
  if (instance && typeof instance.getData) {
    return instance.getData().then(value => {
      values.push(value)
      // Return "false" to indicate that we do not want to visit "3"'s children,
      // therefore we do not expect "4" to make it into our values array.
      return value !== 3
    })
  }
}

reactTreeWalker(app, visitor)
  .then(() => {
    console.log(values) // [1, 2, 3, 5];
    // Now is a good time to call React's renderToString whilst exposing
    // whatever values you built up to your app.
  })
  // since v3.0.0 you need to do your own error handling!
  .catch(err => console.error(err))

Not a particularly useful piece of code, but hopefully it is illustrative enough as to indicate the posibilities. One could use this to warm a cache or a redux state, subsequently performing a renderToString execution with all the required data in place.

Order of Execution

react-tree-walker walks your React application in a depth-first fashion, i.e. from the top down, visiting each child until their are no more children available before moving on to the next element. We can illustrate this behaviour using the below example:

<div>
  <h1>Foo</h1>
  <section>
    <p>One</p>
    <p>Two</p>
  </section>
  <Footer />
</div>

In this example the order of elements being visited would be:

div -> h1 -> "Foo" -> section -> p -> "One" -> p -> "Two" -> Footer

Whilst your application is being walked its behaviour will be much the same as if it were being rendered on the server - i.e. the componentWillMount lifecycle will be executed for any "class" components, and context provided by any components will be passed down and become available to child components.

Despite emulating a server side render, the tree walking process is far cheaper as it doesn't actually perform any rendering of the element tree to a string. It simply interogates your app building up an object/element tree. The really expensive cycles will likely be the API calls that you make. 😀

That being said you do have a bail-out ability allowing you to suspend the traversal down a branch of the tree. To do so you simply need to return false from your visitor function, or if returning a Promise ensure that the Promise resolves a false for the same behaviour.

API

The API is very simple at the moment, only exposing a single function. We will describe the API of the reactTreeWalker function below as well as the API for the visitor function that reactTreeWalker expects as a parameter.


reactTreeWalker

The default export of the library. The function that performs the magic.

const reactTreeWalker = require('react-tree-walker')

or

import reactTreeWalker from 'react-tree-walker'

Parameters

  • tree (React/Preact element, required)

    The react application you wish to walk.

    e.g. <div>Hello world</div>

  • visitor (Function, required)

    The function you wish to execute against each element that is walked on the tree.

    See its API docs below.

  • context (Object, optional)

    Any root context you wish to provide to your application.

    e.g. { myContextItem: 'foo' }

  • options (Object, optional)

    Additional options/configuration. It currently supports the following values:

    • componentWillUnmount: Enable this to have the componentWillUnmount lifecycle event be executed whilst walking your tree. Defaults to false. This was added as an experimental additional flag to help with applications where they have critical disposal logic being executed within the componentWillUnmount lifecycle event.

Returns

A Promise that resolves when the tree walking is completed.


visitor

The function that you create and provide to reactTreeWalker.

It should encapsulates the logic you wish to execute against each element.

Parameters

  • element (React/Preact element, required)

    The current element being walked.

  • instance (Component Instance, optional)

    If the current element being walked is a "class" Component then this will contain the instance of the Component - allowing you to interface with its methods etc.

  • context (Object, required)

    The React context that is available to the current element. react-tree-walker emulates React in exposing context down the tree.

  • childContext (Object, optional)

    If the current element being walked is a "class" Component and it exposes additional "child" context (via the getChildContext method) then this will contain the context that is being provided by the component instance.

Returns

If you return false then the children of the current element will not be visited.

e.g.

function visitor(element) {
  if (element.type === 'menu') {
    // We will not traverse the children for any <menu /> nodes
    return 'false'
  }
}

You can also return a Promise which will cause the tree walking to wait for the Promise to be resolved before attempting to visit the children for the current element.

function visitor(element, instance) {
  // This will make every visit take 1 second to execution.
  return new Promise(resolve => setTimeout(resolve, 1000))
}

You can make the Promise resolve a false to indicate that you do not want the children of the current element to be visited.

function visitor(element, instance) {
  // Only the first element will be executed, and it will take 1 second to complete.
  return (
    new Promise(resolve => setTimeout(resolve, 1000))
      // This prevents any walking down the current elements children
      .then(() => false)
  )
}

More Repositories

1

easy-peasy

Vegetarian friendly state for React
JavaScript
5,022
star
2

react-sizeme

Make your React Components aware of their width and height!
JavaScript
1,932
star
3

react-universally

A starter kit for universal react applications.
JavaScript
1,699
star
4

react-async-component

Resolve components asynchronously, with support for code splitting and advanced server side rendering use cases.
JavaScript
1,447
star
5

react-component-queries

Provide props to your React Components based on their Width and/or Height.
JavaScript
183
star
6

react-jobs

Asynchronously resolve data for your components, with support for server side rendering.
JavaScript
167
star
7

react-async-bootstrapper

Execute a bootstrap method on your React/Preact components. Useful for data prefetching and other activities.
JavaScript
118
star
8

code-split-component

Declarative code splitting for your Wepback bundled React projects, with SSR support.
JavaScript
116
star
9

prisma-pg-jest

Example showcasing how to use Prisma + Postgres + Jest, where each test has its own unique DB context
TypeScript
106
star
10

react-virtual-container

Optimise your React apps by only rendering components when in proximity to the viewport.
JavaScript
53
star
11

cra-monorepo

Example of a now 2.0 monorepo containing Create React App and Node Lambdas
TypeScript
52
star
12

vercel-node-server

An unofficial package allowing you to create http.Server instances of your Vercel Node lambdas.
TypeScript
50
star
13

react-injectables

Explicitly inject Components into any part of your React render tree.
JavaScript
43
star
14

easy-peasy-typescript

An example of using Easy Peasy with Typescript
TypeScript
33
star
15

react-universally-skinny

A "when size matters" adaptation of the react-universally boilerplate.
JavaScript
30
star
16

lerna-cola

Superpowers for your Lerna monorepos.
JavaScript
23
star
17

gun-most

Extends gunDB with the ability to chain into most.js observables.
JavaScript
14
star
18

i-theme

A minimalist theme for vscode
12
star
19

preact-compat-hmr

Example HMR with preact, preact-compat and webpack 2.
JavaScript
12
star
20

prisma2-template

A scratchpad for Prisma2
JavaScript
12
star
21

vercel-node-dev

An unofficial development CLI for Vercel applications targeting the Node.js runtime.
TypeScript
10
star
22

npm-library-starter

A starter kit for npm libraries.
JavaScript
9
star
23

vue-zod-form

A composition based API forms helper for Vue 3 projects that utilise TypeScript.
Vue
9
star
24

vue3-typescript-strategy

An example of a sound TS experience in Vue 3 via .tsx files
TypeScript
8
star
25

cinderella

[WIP] A tiny transformation library.
JavaScript
8
star
26

demo-react-async-and-jobs

Demo on how to use react-async-component and react-jobs together in an SSR project.
JavaScript
7
star
27

react-universally-opinionated

[WIP] An opinionated version of React, Universally.
JavaScript
7
star
28

simee

Ultra simple symlinking of local npm packages.
JavaScript
7
star
29

remix-aws-edge

An adapter for running Remix on AWS CloudFront Lambda@Edge
TypeScript
6
star
30

easy-peasy-hot-reload

Easy Peasy hot reloading example
HTML
6
star
31

templatiser

Apply templates to a file tree.
JavaScript
4
star
32

react-hey-fela

Alternative React bindings for Fela
JavaScript
4
star
33

react-app-rewire-modernizr

Adds the modernizr-loader to your react-app-rewired config.
JavaScript
3
star
34

reason-advent-2017

https://adventofcode.com solved using ReasonML
OCaml
2
star
35

code-split-match

A Webpack 2 code splitting Match component for React Router v4.
2
star
36

comickult.holding

RxJS powered holding page for comickult.
JavaScript
2
star
37

flowcheatsheet

An interactive cheat sheet for Facebook's Flow type system.
JavaScript
2
star
38

lerna-cola-sample

A sample application showcasing Lerna Cola
JavaScript
2
star
39

react-app-rewire-css-blocks

Adds support for CSS Blocks to your react-app-rewired config.
JavaScript
2
star
40

react-universally-archive

JavaScript
2
star
41

now-dev-issue

JavaScript
1
star
42

reason-react-gof

[WIP] Conway's Game of Life implemented using ReasonML/Bucklescript/React.
OCaml
1
star
43

sst-cors-example

An example of CORS configuration in the context of a Serverless Stack application.
TypeScript
1
star
44

react-responsive-element

A responsive element component for React web applications.
1
star
45

monilla

A CLI to manage monorepos with predictability and stability.
TypeScript
1
star
46

approachable-fp-in-scala

An Approachable Guide to Functional Programming in Scala
1
star
47

react-router-bundlesize

Debugging react-router bundle size optimisation.
JavaScript
1
star
48

blank

Blank project for fiddling with JS whilst having all the tooling I like
JavaScript
1
star
49

splice

A malleable content management system.
1
star
50

react-fractal-grid

A fractal grid Component set for React.
1
star