• Stars
    star
    145
  • Rank 248,949 (Top 5 %)
  • Language
    JavaScript
  • Created about 7 years ago
  • Updated almost 7 years ago

Reviews

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

Repository Details

Components & state

Note: Do not use, or even plan to use, this. I'm just explaining some ideas I have and giving code to back it up. I don't plan on marketing or fleshing out this repo at all.

Demo page

This is an experiment to improve two things in React:

  1. The component interface - avoid classes and embrace a more functional way of defining components with state, lifecycles, and refs
  2. State exposure - provide deeper tools to exploring state at runtime

This is born out of a lot of experience with React, and while working on a new product I desired to avoid many minor frustrations that I've experienced before. I have been using this in an existing product and while it's still early, I've been loving it.

The biggest thing I want to avoid is classes. They are verbose, error-prone, and hard to compose. Let me talk through that a little bit:

  • It's annoying to have to choose between functions and classes. I'll often start with a functional component to design it and then "upgrade" to a class to have state. Rewriting it into a class is annoying, but not a huge deal. It's still mental burden and a single interface would be nice.

  • Classes have the classic problem of having to bind functions to this. In React this happens most of the time with event callbacks. Do you do this.onChange.bind(this), or use the static method pattern of onChange = e => { ... }? The former creates a new function each render (could be bad if wanting pure props), but the latter is creating a new function instance for every single component instance (could be bad for memory). For the latter pattern, I now have another choice to make: when writing functions which style do I use?

  • I think the worst thing though is they aren't very composable. For example, let's say I have a utility function on my class:

  getVideo(id, fromUser) {
    const video = this.state.videos[id];
    return {
      video,
      fromUser,
      type: 'mp4'
    }
  }

This is fine, but then in componentWillReceiveProps I need to use this utility function because I need to process something. I can't! It's bound to the current state, but I need it to work with nextState. I could refactor it to take state in, but in a large scale I run into this all the time and end up wanting to pull out most of the code in my classes into functions (but then I have another thing to thing about: does this function belong on the class or not?)

This is what kickstarted this project; it's a new component interface that is all functions. I started with my own thing, and then drew inspiration from reason-react. What I had at first wasn't too different; the main thing I stole was the updater idea.

Component API

The entire implementation is in lively.js and you can see examples in the examples folder.

Let's start with a functional component:

function Input({ type }) {
  return <input type={type} />
}

Now let's add some state. That's where lively comes in. To "upgrade" this all we need to do wrap it with lively and tweak the function signature:

function onChange(bag, e) {
  return { value: e.target.value };
}

function getInitialState({ props }) {
  return { value: props.value || "" };
}
  
function Input({ props: { type }, state: { value }, updater }) {
  return <input type={type} value={value} onChange={updater(onChange)} />
}

export default lively(Input, { getInitialState });

To add lifecycle methods, you pass them in as the second arg to lively. Here we added a getInitialState method. In the component, we pull off the props and state separately, and add an onChange handler.

updater is a function that will take a function and turn it into a callback for that component (it is memoized so given the same args, it'll always return the same function instance which is great for prop diffing). The new function will take a "component bag" (see the onChange function) as the first arg, and any passed arguments after it. The bag consist of these props:

  • inst - The component instance (so you can have instance vars if needed)
  • props - The props
  • state - The state
  • refs - Any refs

All lifecycle methods and callbacks are given this bag. In fact, the render function itself is given this bag (so you could pull off refs or inst too). updater is only available in the render function though.

All updater methods and the componentWillReceiveProps hook update the state by simply returning new state. You never manually call setState. This is nice for several reasons: the confusing about what is the current state goes away, and we can restrict when it can be updated (can't ever call setState in componentDidUpdate).

See Input.js and Form.js in the examples folder for more examples, and see them running on the demo page.

State

My main goal is the component API, but since I'm making that I figured I would explore some ideas for exposing component state to the user.

I really like component local state for transient state. But the problem is it tends to be hidden away from the developer, and difficult to build tooling around. I believe that's why initially there was a push to everything in redux because it is nice to see what's going on.

After thinking about my use cases a lot, I implemented the following:

  • The ability to override the initial state from the outside. You would never want to do this in production, but it's super nice for documentation and other tooling. I currently have a large design system with an example page that renders all of my components in all of their states at once. Many of these components manage their own state (am I open? selected? etc) but I can override it to make a really powerful design system that I can hot reload and see changes across all states.

  • The ability to record state as it changes. Originally I want to actually host the state outside of React. But after looking into it, it would be fighting against React hard, especially Fiber, and I don't see how it could play well.

For the second feature, it turned out OK that I don't host my own state because I can't think of any real-world use case where I need it. The ability to record it is good enough.

Note that all of this should be able to work in production mode, these are not just debugging tools. Even if some of this functionality isn't on by default in prod, I'd like to tell the user that they could turn on a "verbose" mode and send me a recording if possible.

Check out the demos. Here is the override state demo. I'm able to force the autocomplete dropdown to be open.

The rest of them involve recording. The next one (Log state) simply logs the state in the devtools as it changes over time. This is achieved by simply wrapping the subtree that I'm interested in with the Recorder component:

Recorder.js

<Recorder>
  <Input value={5} />
</Recorder>

See Recorder.js for the implementation. It uses context to provide a recordState function which lively component call when state changes. The definition of that function is recordState(inst, prevState) (you get the current state with inst.state).

Note that we are working with subtrees. You could argue that the React devtools could do all of this. Usually I'm only really interested in a small subtree though, and it's really nice to be able to scope it.

The next demo (Undo state) shows how you can implement undo functionality with this. Without the form knowing, you can enhance it with undo functionality this this:

Undo.js

function undo({ refs }) {
  refs.undo.undo();
}

// ... snip ...

<Undo ref={el => (refs.undo = el)}>
  <Form />
</Undo>
<button onClick={updater(undo)}>Undo</button>

See Undo.js for the details. I am well aware that this is probably not practical in the real-world though, too many complications. (Since we're not tracking the reconciled tree, this wouldn't actually work if the tree changes).

The last demo (View state) shows how you could display an inline object inspector to show the state and props of a subtree. See the StateViewer.js component for the implementation, and just like before, it's as easy as wrapping a subtree:

StateViewer.js

<StateViewer>
  <Form style={{ marginTop: 20 }} />
  Outside of the form: <Input name="outside" />
</StateViewer>

Last note: if you look at the components that implement state recording, that have an instance and the state. What would be really cool is if we could retroactively construct the tree that React has reconciled, and be able to generated IDs for each node that encode where they are in the tree. Then, we need a way to have this same reconciled information when setting the initial state.

The result would be that we could actually snapshot React's state, serialize it for later, and load it up. Give the same props & state, we should be able to reconstruct the exact same UI.

I have no idea if that's possible, or even if there's a strong enough use case for it. The main use case for all of the state stuff is allowing a user to send me a detailed dump of what happened when they hit an error. Anything to help with that is huge, and a simple after-the-fact recording may be good enough.

More Repositories

1

absurd-sql

sqlite3 in ur indexeddb (hopefully a better backend soon)
JavaScript
4,062
star
2

transducers.js

A small library for generalized transformation of data (inspired by Clojure's transducers)
JavaScript
1,726
star
3

blog

All the sources for my (not powered by React anymore) blog
CSS
1,223
star
4

electron-with-server-example

An example Electron app with a backend server all wired up via IPC
JavaScript
1,002
star
5

crdt-example-app

A full implementation of CRDTs using hybrid logical clocks and a demo app that uses it
JavaScript
576
star
6

css-animations.js

A library to work with CSS3 keyframe animations from javascript
JavaScript
479
star
7

backend-with-webpack

A simple server using webpack as a build tool
JavaScript
459
star
8

unwinder

An implementation of continuations in JavaScript
JavaScript
304
star
9

canvas-game-bootstrap

A starting point & tutorial for basic 2d canvas games
JavaScript
237
star
10

es6-macros

A collection of sweet.js macros that implement ES6 features for ES5
JavaScript
237
star
11

monkey-hot-loader

A webpack loader to hot reload JavaScript modules
JavaScript
171
star
12

outlet

Outlet is a simple Lisp-like programming language
JavaScript
161
star
13

jsx-reader

A JSX reader for JavaScript, powered by sweet.js.
JavaScript
149
star
14

emojiscript

EmojiScript: emotion literals, emotional algebra, and more for JavaScript
JavaScript
138
star
15

dom3d

Rendering 3d with CSS3
JavaScript
111
star
16

gambit-iphone-example

An example iphone app which uses Gambit Scheme.
Scheme
103
star
17

farmageddon

The raw, unedited source for the iOS game I wrote in Gambit Scheme back in 2010.
C
102
star
18

dcpu-lisp

A Lisp-like language that compiles to DCPU-16 assembly code
JavaScript
77
star
19

redux-experiments

A few customizations for my own projects
JavaScript
64
star
20

lljs-cloth

Cloth simulation as a large-scale test for LLJS development
JavaScript
47
star
21

sweetjs-loader

A webpack loader for sweetjs
JavaScript
47
star
22

swank-gambit

Gambit swank backend for SLIME
Scheme
40
star
23

learning-clojurescript

All the apps I'm making to learn ClojureScript (and also have fun)
JavaScript
40
star
24

absurd-example-project

A project to show everything needed to use absurd-sql
JavaScript
38
star
25

grunt-nunjucks

A grunt plugin for nunjucks.
JavaScript
38
star
26

steps

A little hack that allows you to step through JavaScript code (only in Firefox for now)
JavaScript
30
star
27

jlongster-rebuild

A rebuild of my blog w/react, CSP, and more
JavaScript
29
star
28

gulp-sweetjs

A gulp loader for sweet.js
JavaScript
26
star
29

sweet.js-tutorials

All the sweet.js tutorials from my blog and a working sweet.js environment
JavaScript
26
star
30

play-react-native-opengl

An example of using React Native to control OpenGL (terrible hacky code)
JavaScript
25
star
31

webfighter

An r-type inspired 2d game for the web
JavaScript
24
star
32

live-css-layout

A live editor for css-layout
JavaScript
20
star
33

configure-iphone

A utility shell script to help build libraries for iPhone devices.
19
star
34

objective-c-csv-parser

Objective-C CSV Parser
18
star
35

shade

A simple but optimal 3d engine
JavaScript
16
star
36

construct.js

Simple sugar for establishing prototype-based inheritance
JavaScript
15
star
37

mozlando-react-workshop

Lessons for learning React.
JavaScript
15
star
38

genetic-canvas

Implements a genetic algorithm for drawing 2d pictures
Scheme
14
star
39

tcomb-immutable-experiment

An experiment with immutable.js-backed tcomb types
JavaScript
14
star
40

outlet-machine

A register-based virtual machine for a basic Lisp
JavaScript
13
star
41

autoffi

FFI generator for Gambit Scheme
Scheme
13
star
42

ftgles

FTGL, a font rendering library, ported to OpenGLES.
C++
12
star
43

struct.js

Struct datatypes for JavaScript with a few sweet.js macros
JavaScript
12
star
44

gambit-ffi-generator

Generating Gambit-C Scheme FFI's from C header files
12
star
45

schemeray

A simple raytacer written in Gambit Scheme, and ported to other implementations
Scheme
11
star
46

fp-workshop

A workshop to introduce functional-style programming in JavaScript
10
star
47

mozilla.com

Battlegrounds for mozilla.com work (mirror of http://svn.mozilla.org/projects/mozilla.com/)
PHP
10
star
48

reason-http-server-example

A simple HTTP server in Reason using cohttp and built with Rebel.
10
star
49

devrepl

A REPL for the Firefox debugger client/server
JavaScript
9
star
50

weatherme

A simple weather web app
JavaScript
8
star
51

macro-editor

An embeddable sweet.js macro editor
JavaScript
8
star
52

flame

An experimental tile-based game
JavaScript
7
star
53

mozilla.org

A copy of the mozilla.org PHP site.
JavaScript
7
star
54

grime3d

A proof of concept for something special.
Scheme
6
star
55

perf-deets

Get those perf deets
JavaScript
6
star
56

OMGBUGS

Experimental nodejs-powered bugzilla interface. See the demo site at this repo's url.
JavaScript
6
star
57

game-engine-studies

Implementing various game engines in javascript
JavaScript
6
star
58

lljs-minecraft

Port of notch's minecraft demo to LLJS
JavaScript
5
star
59

ahoy

This is nothing.
JavaScript
5
star
60

tilt-this

A web-based builder for 3d Tilt objects (viewable by Firefox's Tilt devtool)
JavaScript
5
star
61

actual-automoto

JavaScript
5
star
62

ftgles-gambit

Gambit Scheme bindings to the FTGLES library
Scheme
4
star
63

webui

Using csuwldcat's WebComponent project to build some UI tools for webapps
JavaScript
4
star
64

relnotes

Release notes generator for Firefox releases
Python
4
star
65

lljs-editor

An experiment with live-editing LLJS code in the browser.
JavaScript
4
star
66

ff-devtools-libs

A quick experiment to help migrate to HTML
JavaScript
4
star
67

demo-cloth

A floating cloth demo with JavaScript/WebGL
JavaScript
4
star
68

channel-debugger

a firefox devtool addon for debugging channels
JavaScript
4
star
69

YPS

Yield Passing Style
JavaScript
4
star
70

entity.js

A little game experiment
JavaScript
3
star
71

spidermonkey-graphics

spidermonkey for 3d graphics and user interfaces
C
3
star
72

strangeloop2015

Slides for my talk "Cleaning the Tar: Using React in Firefox DevTools"
JavaScript
3
star
73

tweetness

A twitter Open Web App
JavaScript
3
star
74

js-lljs-c-benchmarks

Benchmarks comparing js, lljs, and c compiled with emscripten
JavaScript
3
star
75

octave-utility

my utility code for using octave
Objective-C
3
star
76

jlongster-django

My personal site: jlongster.com
Python
3
star
77

writing-webapps

My presentation at the Mozillas Apps Summit
JavaScript
3
star
78

django-basic-site

Python
3
star
79

fiber-modal-error

JavaScript
2
star
80

mozilla-product-details

git svn clone http://svn.mozilla.org/libs/product-details/json/
JavaScript
2
star
81

gollumdoc

Patches the gollum wiki to host documentation better
Ruby
2
star
82

servable-branches

Manifest git branches on the filesystem to be served on the web
JavaScript
2
star
83

persona.org

NodeJS project
JavaScript
2
star
84

my-react-native-babel-preset

JavaScript
2
star
85

voloweb

A site for volo
JavaScript
2
star
86

libgrowth

A tiny web site that estimates the growth of your node libraries over time
JavaScript
2
star
87

build-docker-mochitest

Making mochitests OK.
Makefile
2
star
88

xpcshell-unit-test

A basic xpcshell test runner that can be used outside of Firefox
JavaScript
2
star
89

onGameStart-2013

My presentation for onGameStart 2013, using websockets to connect everybody
JavaScript
2
star
90

devtool-prototype

An experiment with making a new devtool
CSS
2
star
91

actual-docs

markdown content
JavaScript
2
star
92

cordova-firefoxos-plugins

A place for Mozilla to track our Cordova work
Java
2
star
93

rn-android-input-pan

Repro for Android's panning behavior when focusing an input
Objective-C
2
star
94

jlongster.com

TypeScript
2
star
95

bugzillersearch

A quick experiment in hating iOS for nefarious research purposes.
Objective-C
2
star
96

php-product-details

Mozilla's PHP product-details library (mirror of https://svn.mozilla.org/libs/product-details)
PHP
2
star
97

services

services that I run on my computer & servers
Ruby
1
star
98

tmp-node-inspector

JavaScript
1
star
99

jlongster.github.io

HTML
1
star
100

noodleconfware

The conference software for noodleconf
JavaScript
1
star