• Stars
    star
    125
  • Rank 275,886 (Top 6 %)
  • Language
    Scala
  • License
    MIT License
  • Created about 7 years ago
  • Updated almost 4 years ago

Reviews

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

Repository Details

Production ready React wrapper for Scala.js - composable lifecycle - no memoization, no macros, no implicits.

React4s

React4s is a Scala library for frontend UI. It wraps Facebook's React library. It exposes an API that makes it easy to write plain and simple Scala code for your components. You get the indispensable shouldComponentUpdate() for free, no callback memoization required. It uses no macros, no implicits and no complicated types.

resolvers += Resolver.sonatypeRepo("snapshots")
libraryDependencies += "com.github.ahnfelt" %%% "react4s" % "0.10.0-SNAPSHOT"

Writing a component

case class OkCancel(label : P[String]) extends Component[Boolean] {
    override def render(get : Get) = E.div(
        E.div(Text(get(label)),
        E.div(
            E.button(
                Text("OK"),
                A.onClick(_ => emit(true))
            ),
            E.button(
                Text("Cancel"),
                A.onClick(_ => emit(false))
            )
        )
    )
}

This defines a component OkCancel that takes one String "prop" named label. You read props, state, etc. with the get object, which is only available where you can safely read from these. The Boolean in Component[Boolean] says that this component emits Boolean messages, which is done with the emit(...) method. The render() method is what renders your component, and the component is rerendered automatically when the props or state change. The E, A and S objects provide methods for building the Virtual DOM.

Emitting messages instead of taking in callbacks via props is a departure from the usual React API, and is how you get shouldComponentUpdate() for free. It also clearly separates input (props) from output (callbacks).

You can use a component like this: Component(OkCancel, "Would you like some icecream?"). The first argument is the components companion object. The remaining arguments are the props for the component.

Keeping state

case class Counter() extends Component[NoEmit] {
    
    val okClicks = State(0)
    val cancelClicks = State(0)
    
    def onClick(ok : Boolean) = {
        if(ok) {
            okClicks.modify(_ + 1)
        } else {
            cancelClicks.modify(_ + 1)
        }
    }
    
    override def render(get : Get) = E.div(
        Component(OkCancel, "Would you like some icecream?").withHandler(onClick),
        E.hr(),
        E.div(Text("You've clicked OK " + get(okClicks) + " times.")),
        E.div(Text("You've clicked Cancel " + get(cancelClicks) + " times."))
    )
    
}

The State type allows the library to detect when you update the state, so it can rerender your component. You can read it with eg. okClicks() and update it with eg. okClicks.set(42) or okClicks.modify(_ + 1).

Styles and CSS

case class OkCancel(label : P[String]) extends Component[Boolean] {
    override def render(get : Get) = E.div(
        E.div(Text(get(label)), S.color.rgb(0, 0, 255)),
        E.div(
            E.button(
                FancyButtonCss,
                Text("OK"),
                A.onClick(_ => emit(true))
            ),
            E.button(
                FancyButtonCss,
                Text("Cancel"),
                A.onClick(_ => emit(false))
            )
        )
    )
}

The above uses one inline style S.color.rgb(0, 0, 255) and one css class FancyButtonCss. The css class is defined as follows:

object FancyButtonCss extends CssClass(
    S.cursor.pointer(),
    S.border.px(2).solid().rgb(0, 0, 0),
    S.color.rgb(0, 0, 0),
    S.backgroundColor.rgb(255, 255, 255),
    Css.hover(
        S.color.rgb(255, 255, 255),
        S.backgroundColor.rgb(0, 0, 0)
    )
)

It styles a button to be white with a black border, and black with white text when the mouse is hovered over it. The resulting <style>...</style> will be added to the DOM the first time FancyButtonCss is used to render a component.

Binding it to the DOM

object Main extends js.JSApp {
    def main() : Unit = {
        val component = Component(Counter)
        ReactBridge.renderToDomById(component, "main")
    }
}

Just create the component and call renderToDomById. The "main" argument is the ID refering to an existing HTML element, eg. <div id="main"></div>.

Performance

In React, you implement shouldComponentUpdate() to avoid rerendering unrelated components when your model is updated. In React4s, this method is already implemented for you. It uses Scala's != operator to check if any props changed, and only updates the component if either the props changed or the state has been updated. That means that for everything that hasn't been reallocated, it just compares the references, and thus doesn't traverse deep into the props.

Beware that what you pass via props must be immutable and have structural equality. You can't pass mutable objects or functions as props, or you will get a stale view or a slow view respectively. However, it's completely safe to pass immutable collections and immutable case classes.

Lifecycle

image

This is the complete component lifecycle for React4s. It's simpler than plain React because the React4s model makes the assumption that your props are immutable and have structural equality.

  1. When your component is added to the Virtual DOM, the constructor is invoked.
  2. Before each render, the componentWillRender() method is called. Here you can update any state that depends on props that have changed.
  3. Then in render(), you'll return the Virual DOM that displays your component. State updates are not allowed during this call.
  4. When your component is removed from the Virtual DOM, componentWillUnmount() is called.

The component will only be rerendered when your props have changed, as defined by Scala's structural inequality !=, or your state has been updated. The state is considered updated when you've called update() explicitly or called .set(...) or .modify(...) on State objects with a value that's different from the previous according to the != operator.

You can attach Attachables that listen on these lifecycle events, and React4s comes with three of those: Timeout, Debounce and Loader. See how they're used in the Online Examples.

More Repositories

1

type-inference-by-example

A series of down-to-earth articles on implementing type inference
Scala
144
star
2

funk

PROTOTYPE: A minimal scripting language - OOP via lambda functions and pattern matching
JavaScript
53
star
3

firefly-boot

Bootstrap compiler for Firefly
JavaScript
48
star
4

parsercombinator

A parser combinator for Java 8. Proof of concept.
Java
13
star
5

AlgorithmWStepByStep

Type inference for ML-like languages. A port to F# of "Algorithm W Step by Step" by Martin Grabmüller.
F#
10
star
6

commandline

EXPERIMENTAL: A command line parser for Scala that follows POSIX and GNU conventions. No dependencies, no macros and no implicits.
Scala
8
star
7

rulebuilder

A rule builder for business logic in plain JavaScript. Boolean logic and custom operators. Styleable. Version 0.0.1.
6
star
8

react4s-todomvc

TodoMVC in 139 lines of Scala with React4s
CSS
6
star
9

mlcsp

(Not under active development). OCamlCSP is a library for Ocaml based on Tony Hoares Communicating Sequential Processes (CSP) .
OCaml
5
star
10

FQL

A functional query language.
Haskell
4
star
11

cms

Experimental: Goals: a minimal amount of "admin screens" and everything editable in place. Status: planning.
Haxe
3
star
12

intestines

Experimental: A game
Haskell
3
star
13

guts

Experimental: A game
Haskell
3
star
14

alua

DRAFT: Alua is a typed language whose syntax is Lua-like
Scala
3
star
15

Templ

A template language with statically typed bindings for Java.
Java
2
star
16

visualization

School project
Haskell
2
star
17

Piqo

Experimental: A new strain of that programming language I am always working on. NOTE: This is not being worked on actively, and the current state of the language is mostly idea only.
Haskell
2
star
18

keen

Experimental: An eager functional programming language
Haskell
2
star
19

elm-editable-tree

A very simple example that shows how to implement an editable tree structure in Elm. The trick is a recursive Model type, the recursive use of update and view, and the At i msg combinator.
Elm
2
star
20

jsonr

EXPERIMENTAL: JSONR is JSON, but with concise syntax, simpler schemas and efficient binary encoding.
2
star
21

chainquery

Experimental: A typed database query language for Java.
Java
1
star
22

ri

Recursive install for maven 2
Java
1
star
23

Abyss

Experimental: A game
Haskell
1
star
24

jtransaction

Experimental: Transactional memory for Java
Java
1
star
25

reactive

Experimental: Reactive GUI library for JavaScript
JavaScript
1
star
26

uld-hd

A port of Werks ULD library for OpenGL (WebGL + fragment shader)
JavaScript
1
star
27

guts2

Experimental: A game
1
star
28

astrobots

Experimental: A touch-screen programming language (lambda calculus + functional reactive programming) and a game
Java
1
star
29

elmchatgpt

A toy implementation of the ChatGPT client in Elm
Elm
1
star
30

dope

Experimental: A game of trust and betrayal
Haskell
1
star
31

boa

EXPERIMENTAL: A language with hints of Python, Smalltalk and Haskell
Scala
1
star
32

ziphon

Experimental: A programming language
Haxe
1
star
33

typed-format

Experimental: A simple and complete schema language and binary format for data interchange.
1
star
34

react4s-example

Please see react4s.org instead
Scala
1
star
35

Editor

Experimental: A texmode editor that gets out of your way. Standard keyboard shortcuts, yet you don't *need* to leave the home row. It's being written (in Haskell), but it's not useable yet, and don't hold your breath.
Haskell
1
star