🌲 Collect and compute React render data seamlessly across the server and client.

Related tags

React Apps reforest
Overview

🌲 reforest

Collect and compute React render data seamlessly across the server and client.

Note While digital trees are cool, climate change is affecting real trees at a rapid rate. Please consider planting a tree, starting a garden, or donating to an organization.

Install

npm install reforest
yarn add reforest

Why?

When building low-level components in React for accessibility, styling, and animation purposes, the orchestration for everything can become painful for both the library author and consumer. In general, the problem boils down to a component needing to use render data from another component[s]. This library aims to solve this problem by managing a tree of data built from other component renders in an easy API that works on the server and client.

Related

Usage

Warning The API is still in development and may change without notice. Docs coming soon..

Please note the following example is for demo purposes only and you should use a more robust solution that is fully accessible.

(null) function Select({ children }: { children: React.ReactNode }) { const highlightedIndexState = React.useState(null) const [highlightedIndex, setHighlightedIndex] = highlightedIndexState const [selectedValue, setSelectedValue] = React.useState(null) const tree = useTree(children) const moveHighlightedIndex = (amountToMove: number) => { setHighlightedIndex((currentIndex) => { if (currentIndex === null) { return 0 } else { const nextIndex = currentIndex + amountToMove if (nextIndex >= tree.maxIndex) { return 0 } else if (nextIndex < 0) { return maxIndex - 1 } return currentIndex + amountToMove } }) } return (
{ if (event.key === "ArrowUp") { moveHighlightedIndex(-1) } else if (event.key === "ArrowDown") { moveHighlightedIndex(1) } }} > {selectedValue ? <>Selected: {selectedValue} : `Select an option below`} {indexedChildren}
) } function Option({ children, value }: { children: React.ReactNode; value: any }) { const { indexPath, index } = useTreeData() const selectContext = React.useContext(SelectContext) const [highlightedIndex, setHighlightedIndex] = selectContext.highlightedIndexState const isHighlighted = index === highlightedIndex const isSelected = selectContext.selectedValue ? selectContext.selectedValue === value : false return (
setHighlightedIndex(index)} onMouseOut={() => setHighlightedIndex(null)} onClick={() => selectContext.selectIndex(indexPath)} style={{ backgroundColor: isHighlighted ? "yellow" : "white" }} > {children} {isSelected && "✅"}
) } const fruits = ["Apple", "Orange", "Pear", "Kiwi", "Banana", "Mango"] export default function App() { return ( ) }">
import * as React from "react"
import { useTree, useTreeData } from "reforest"

const SelectContext = React.createContext<any>(null)

function Select({ children }: { children: React.ReactNode }) {
  const highlightedIndexState = React.useState<number | null>(null)
  const [highlightedIndex, setHighlightedIndex] = highlightedIndexState
  const [selectedValue, setSelectedValue] = React.useState<React.ReactElement | null>(null)
  const tree = useTree(children)
  const moveHighlightedIndex = (amountToMove: number) => {
    setHighlightedIndex((currentIndex) => {
      if (currentIndex === null) {
        return 0
      } else {
        const nextIndex = currentIndex + amountToMove

        if (nextIndex >= tree.maxIndex) {
          return 0
        } else if (nextIndex < 0) {
          return maxIndex - 1
        }

        return currentIndex + amountToMove
      }
    })
  }

  return (
    <div
      tabIndex={0}
      onKeyDown={(event) => {
        if (event.key === "ArrowUp") {
          moveHighlightedIndex(-1)
        } else if (event.key === "ArrowDown") {
          moveHighlightedIndex(1)
        }
      }}
    >
      <strong>{selectedValue ? <>Selected: {selectedValue}</> : `Select an option below`}</strong>
      <SelectContext.Provider value={{ highlightedIndexState, selectedValue }}>
        {indexedChildren}
      </SelectContext.Provider>
    </div>
  )
}

function Option({ children, value }: { children: React.ReactNode; value: any }) {
  const { indexPath, index } = useTreeData()
  const selectContext = React.useContext(SelectContext)
  const [highlightedIndex, setHighlightedIndex] = selectContext.highlightedIndexState
  const isHighlighted = index === highlightedIndex
  const isSelected = selectContext.selectedValue ? selectContext.selectedValue === value : false

  return (
    <div
      onMouseOver={() => setHighlightedIndex(index)}
      onMouseOut={() => setHighlightedIndex(null)}
      onClick={() => selectContext.selectIndex(indexPath)}
      style={{ backgroundColor: isHighlighted ? "yellow" : "white" }}
    >
      {children} {isSelected && "✅"}
    </div>
  )
}

const fruits = ["Apple", "Orange", "Pear", "Kiwi", "Banana", "Mango"]

export default function App() {
  return (
    <Select>
      {fruits.map((fruit) => (
        <Option key={fruit} value={fruit}>
          {fruit}
        </Option>
      ))}
    </Select>
  )
}
Comments
  • refactor state to zustand

    refactor state to zustand

    This PR moves the source code under a src directory with files split out into their respective parts since this project has grown in complexity. It also moves state management from valtio to zustand for easier control of when updates occur. This was due to proxy state changes constantly throwing Maximum Update Exceeded errors.

    opened by souporserious 1
  • simplify gathered data

    simplify gathered data

    Right now a Map is used to store all data since it is more performant and this simplifies updating data. However, this isn't feasible for storing server data right now as it is rather large as seen in the screenshot below:

    large amount of json data from a reforest tree render

    This should instead be stored as nested arrays since they will be ordered properly and don't need indexPathString which is rather large to tack on to the data. In general, this probably warrants proper serialize/deserialize utilities.

    opened by souporserious 1
  • pre-rendering

    pre-rendering

    This is yet another rewrite of the useTree logic, but after testing this seems like the right API and balance of constraints 🥇. Shoutout to @nihgwu for their approach in create-slots which inspired the following technique 🙏

    Pre-rendering

    A new concept called pre-rendering is introduced that allows control of splitting the initial render similar to post-processing in graphics where we use data from one render to compute the next. This is done by simply calling the root children twice on initial mount which simplifies server computed data greatly. The tradeoff is minimal in that the control is up to the user to make sure there isn't DOM injected twice and gets rid of needing to inject server data or hack Suspense.

    See the following example for a high-level look at the new API until docs are available:

    import { useTree, useTreeData } from 'reforest'
    
    function Parent({ children }: { children: React.ReactNode }) {
      const tree = useTree(children)
    
      // pass through children and no DOM for the initial render
      if (tree.isPreRender) {
        return tree.children
      }
    
      return (
        <div style={{ display: "flex", gap: "1rem" }}>
          {tree.children}
        </div>
      )
    }
    
    function Child({ color, duration }: { color: string; duration: number }) {
      const useTreeStore = useTreeState()
      const treeMap = useTreeStore((state) => state.treeMap)
      const { isPreRender } = useTreeData(React.useMemo(() => ({ color, duration }), [color, duration]))
    
      // again, we stop early when doing the initial render since we're only interested in data in the pre-render pass
      if (isPreRender) {
        return null
      }
    
      const computed = treeMap.size + duration
    
      return (
        <>
          <div>Duration: {duration}</div>
          <div>Computed: {computed}</div>
        </>
      )
    }
    
    opened by souporserious 0
  • rewrite state again

    rewrite state again

    This rewrites the state layer to use Valtio again 🫠. Jotai was causing flickering when computing tree state no matter what method was used. Thankfully, Valtio does not suffer from this problem. Using the learnings from the Jotai rewrite everything has been merged back into one solution with two new useTreeEffect and useTreeSnapshot hooks.

    opened by souporserious 0
  • jotai rewrite

    jotai rewrite

    This PR rewrites the tree state management to use jotai to fix maximum update exceeded errors. It was originally a rewrite to zustand and then back to valtio but jotai seems to be the best approach for handling computed data since it is a first-class API. The file structure was also cleaned up and moved into src since the file was getting large. Finally, a new useTreeState hook which allows controlling tree state outside of a component as well as a flattenChildren utility were added.

    opened by souporserious 0
  • fix initial computed data

    fix initial computed data

    This fixes the initial computed data not getting hydrated properly because prior this was storing the information used to compute. Now this simply stores a map of computed values which is much smaller and accurate when hydrating from the server 🎉

    closes #2

    opened by souporserious 0
  • fix tree computed data

    fix tree computed data

    Trying to use overall computed data from the proxyMap with another proxy was not synchronizing properly. This PR forks Valtio's proxyMap and uses proxyWithComputed which fixes things. It might be worth seeing if this can be supported upstream somehow in Valtio or maybe there is a better way to compute data specifically for maps.

    opened by souporserious 0
  • rewrite

    rewrite

    This is a full rewrite and rename from use-indexed-children to reforest. This normalizes the API into three useTree, useTreeData, and useTreeEffect hooks for subscribing and computing data based on other components. Better docs and site coming soon.

    opened by souporserious 0
  • reparenting

    reparenting

    The architecture of reforest lends well for performance wins like reparenting. This library is a great example we should be able to learn from to support this.

    opened by souporserious 0
  • presence

    presence

    Looking to the next render is a powerful mechanism that libraries like Framer use for mounting and exiting transitions. This should be a first-class API in reforest. It might be as simple as holding the render while giving the opportunity to use the next one since we have all of the information about the tree.

    opened by souporserious 0
Owner
Travis Arnold
Designer 🎨 Engineer 📐 Systems 🎛 React ⚛️ He/Him
Travis Arnold
Render and position native windows as simply as you render and position tooltips in your React app.

Portal Windows Render and position windows as simply as you render and position tooltips in your React app. Getting started To use Portal Windows, you

Tandem 61 Jul 16, 2022
A collection tracking app for anything you might want to collect

Hoarders Helper User Story AS AN avid Magic The Gathering card collector I WANT to search for new cards and add to my collection SO THAT I can keep a

Brandon Sorrell 3 Jul 18, 2022
Cache & de-deduplicate queries and mutations across a React application's components with a convenient API mirroring React.useState.

Cache & de-deduplicate queries and mutations across a React application's components with a convenient API mirroring React.useState. Immediately update the local cache with setState and let React Remote State call your mutation in the background with debounce or throttle options.

Eric Rabinowitz 2 May 1, 2022
This accelerator provides a no code Studio for users to quickly build complex, multi-stage AI pipelines across multiple Azure AI and ML Services

Business Process Accelerator Overview This accelerator provides a no code Studio for users to quickly build complex, multi-stage AI pipelines across m

Microsoft Azure 16 Sep 7, 2022
An open source project to catalogue the diverse set of Indian cuisines available across the subcontinent for thousands of years

An open source project to catalogue the diverse set of Indian cuisines available across the subcontinent for thousands of years. We aspire to become t

Smaranjit Ghose 7 Apr 12, 2022
Cuckoo - Anonymous video calls across the world for free with screensharing

?? Cuckoo - A free anonymous video-calling web application built with WebRTC and React that provides peer-to-peer video and audio communication in a web browser with no plugins or extensions required.

Somik Datta 388 Sep 5, 2022
Use patches to keep the UI in sync between client and server, multiple clients, or multiple windows

The core idea is to use patches to keep the UI in sync between client and server, multiple clients, or multiple windows. It uses Immer as an interface

Webstudio 27 Sep 15, 2022
flipkart-clone using react js express js mongo db razorpay complete e-commerce website using MERN stack client server MVC architecture redux redux-thunk ecomerce project live example

Flipkar Clone MERN stack Sijeesh Miziha's Flipkart Clone is done with top-notch features for the entrepreneur startups like Flipkart it has RazorPay I

Sijeesh Miziha 40 Sep 25, 2022
Webapp of Mattermost server: https://github.com/mattermost/mattermost-server

Mattermost Mattermost is an open source, self-hosted Slack-alternative from https://mattermost.org. It's written in Golang and React and runs as a sin

Mattermost 1.9k Sep 24, 2022
Render URL links for Web & Twitter previews

expo-link-preview Render URL links for Web & Twitter previews Built with react-native using expo.

null 14 Aug 2, 2022
Shopping and E-commerce: A client-side utility project for a Shop product page. Calculate the order price, and check availability with supporting N* filters.

Shop Price Checker Shopping and E-commerce: A client-side utility project for a Shop product page. Calculate the order price, and check availability w

Max Base 4 Jul 31, 2022
A simplified Jira clone built with React/Babel (Client), and Node/TypeScript (API). Auto formatted with Prettier, tested with Cypress.

A simplified Jira clone built with React and Node Auto formatted with Prettier, tested with Cypress ?? Visit the live app | View client | View API Wha

Ivor Reic 9.1k Sep 27, 2022
A small reddit client for browsing the top posts of r/all - built with typescript and react.

snoof A small reddit client for browsing the top posts of r/all - built with typescript and react. Desktop Mobile Features responsive infinite loading

Phoebe Gao 1 Nov 14, 2021
VBAN-Messenger: A VBAN-Chat client in Electron and React

VBAN-Messenger: A VBAN-Chat client in Electron and React

null 3 Apr 6, 2022
Hacker News - This project was created using Vite, React, GraphQL and Apollo Client.

Hacker News A application to get your favorites links made using Apollo Client In Progress ?? Table of contents Table of contents ?? About ?? Techs ??

Brenda Profiro 5 Jun 29, 2022
A chat application built with React, Next.js, and the xmtp-js client library

React Chat Example This example chat application demonstrates the core concepts and capabilities of the XMTP Client SDK. It is built with React, Next.

XMTP 55 Sep 19, 2022
Client for video streaming CRUD app with React and Redux

CRUD Streaming App with React and Redux About the Project CRUD Streaming App with React and Redux. The app allows a user to view a list of available s

Miguel Carvalho 3 Jun 2, 2022
Project flat is the Web, Windows and macOS client of Agora Flat open source classroom.

Project flat is the Web, Windows and macOS client of Agora Flat open source classroom.

netless 4.1k Sep 23, 2022
Log, test, intercept and modify Apollo Client's operations

Test, mock, intercept and modify Apollo Client's operations — in both browser and unit tests! Features mock responses in either unit tests or browser

Zendesk 100 Sep 19, 2022