React TodoMVC with Fable

Unveil the power of React and functional programming!

This is a port of React TodoMVC to show how easy is to take advantage of the full power of React in Fable apps. You can also compare the F# source code with the original JS implementation to see the advantages of Fable programming. There's also a port of the React tutorial, including an express server and hot reloading. And remember Fable is also compatible with React Native for mobile development!

JavaScript bindings and helpers

Fable includes React bindings and helpers to make interaction with the tool more idiomatic in F#. We will also load a couple more of JS libraries: classnames with require.js and director directly with a <script> tag. We will make them accessible to our program with Import and Global attributes respectively.

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
12: 
13: 
14: 
15: 
16: 
#r "node_modules/fable-core/Fable.Core.dll"
#load "node_modules/fable-import-react/Fable.Import.React.fs"
#load "node_modules/fable-import-react/Fable.Helpers.React.fs"

open System
open Fable.Core
open Fable.Core.JsInterop
open Fable.Import

// JS utility for conditionally joining classNames together
let [<Import("default","classnames")>] classNames(o: obj): string =
    failwith "JS only"

// Director is a router. Routing is the process of determining what code to run when a URL is requested.
let [<Global>] Router(o: obj): obj =
    failwith "JS only"

Utility module

This module is the equivalent of utils.js in the original implementation. Note we only need a couple of functions to load and save data from the browser local storage as things like Guid generation (System.Guid.NewGuid()) or record immutable updates are built-in in F#/Fable.

Because our Todo type is really simple (see below), JSON.parse will meet our needs. For more complicated structures see JSON serialization with Fable.

1: 
2: 
3: 
4: 
5: 
6: 
7: 
module Util =
    let load<'T> key =
        Browser.localStorage.getItem(key) |> unbox
        |> Option.map (JS.JSON.parse >> unbox<'T>)

    let save key (data: 'T) =
        Browser.localStorage.setItem(key, JS.JSON.stringify data)

Model definiton

This is an almost direct port of todoModel.js which separates de logic of the app from the views. The biggest difference is with a line of code we can define our Todo type and let the F# compiler statically check we're always manipulating the structure correctly.

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
12: 
13: 
14: 
15: 
16: 
17: 
18: 
19: 
20: 
21: 
22: 
23: 
24: 
25: 
26: 
27: 
28: 
29: 
30: 
31: 
32: 
33: 
34: 
35: 
36: 
37: 
38: 
39: 
40: 
41: 
42: 
43: 
44: 
45: 
46: 
47: 
48: 
type Todo = { id: Guid; title: string; completed: bool }

type TodoModel(key) =
    member val key = key
    member val todos: Todo[] = defaultArg (Util.load key) [||] with get, set
    member val onChanges: (unit->unit)[] = [||] with get, set

    member this.subscribe(onChange) =
        this.onChanges <- [|onChange|]

    member this.inform() =
        Util.save this.key this.todos
        this.onChanges |> Seq.iter (fun cb -> cb())

    member this.addTodo(title) =
        this.todos <-
            [|{ id=Guid.NewGuid(); title=title; completed=false }|]
            |> Array.append this.todos
        this.inform()

    member this.toggleAll(checked') =
        this.todos <- this.todos |> Array.map (fun todo ->
            { todo with completed = checked' })
        this.inform()

    member this.toggle(todoToToggle) =
        this.todos <- this.todos |> Array.map (fun todo ->
            if todo.id <> todoToToggle.id
            then todo
            else { todo with completed = (not todo.completed) })
        this.inform()

    member this.destroy(todoToDestroy) =
        this.todos <- this.todos |> Array.filter (fun todo ->
            todo.id <> todoToDestroy.id)
        this.inform()

    member this.save(todoToSave, text) =
        this.todos <- this.todos |> Array.map (fun todo ->
            if todo.id <> todoToSave.id
            then todo
            else { todo with title = text })
        this.inform()

    member this.clearCompleted() =
        this.todos <- this.todos |> Array.filter (fun todo ->
            not todo.completed)
        this.inform()

React views

We enter now in React's realm to define three views: TodoItem, TodoFooter and TodoApp. We can use classes to define the views as explained in React docs, inheriting from React.Component and defining a render method, where we can use the DSL defined in Fable's React helper to build HTML elements in a similar fashion as we would do with JSX.

For convenience, we use a module alias (R) to shorten references to the React helper.

A big difference from JS is we can define simple models for the state and props of custom views, either by using records or interfaces, to allow for autocompletion and static checking, making our app much more robust than by using plain JS objects.

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
12: 
13: 
14: 
15: 
16: 
17: 
18: 
19: 
20: 
21: 
22: 
23: 
24: 
25: 
26: 
27: 
28: 
29: 
30: 
31: 
32: 
33: 
34: 
35: 
36: 
37: 
38: 
39: 
40: 
41: 
42: 
43: 
44: 
45: 
46: 
47: 
48: 
49: 
50: 
51: 
52: 
53: 
54: 
55: 
56: 
57: 
58: 
59: 
60: 
61: 
62: 
63: 
64: 
65: 
66: 
67: 
68: 
69: 
70: 
71: 
72: 
73: 
74: 
75: 
76: 
77: 
78: 
79: 
80: 
81: 
82: 
83: 
84: 
85: 
86: 
87: 
88: 
89: 
90: 
91: 
92: 
93: 
94: 
95: 
96: 
97: 
module R = Fable.Helpers.React
open R.Props

type TodoItemState = { editText: string }
type TodoItemProps =
    abstract key: Guid 
    abstract todo: Todo 
    abstract editing: bool
    abstract onSave: obj->unit
    abstract onEdit: obj->unit
    abstract onDestroy: obj->unit
    abstract onCancel: obj->unit
    abstract onToggle: obj->unit

let [<Literal>] ESCAPE_KEY = 27.
let [<Literal>] ENTER_KEY = 13.
let [<Literal>] ALL_TODOS = "all"
let [<Literal>] ACTIVE_TODOS = "active"
let [<Literal>] COMPLETED_TODOS = "completed"

type TodoItem(props, ctx) as this =
    inherit React.Component<TodoItemProps, TodoItemState>(props, ctx)
    do this.state <- { editText = props.todo.title }

    let mutable editField: obj option = None

    member this.handleSubmit (e: React.SyntheticEvent) =
        match this.state.editText.Trim() with
        | value when value.Length > 0 ->
            this.props.onSave(value)
            this.setState { editText = value }
        | _ ->
            this.props.onDestroy(e)

    member this.handleEdit (ev: React.MouseEvent) =
        this.props.onEdit(ev)
        this.setState { editText = this.props.todo.title }

    member this.handleKeyDown (e: React.KeyboardEvent) =
        match e.which with
        | ESCAPE_KEY ->
            this.setState { editText = this.props.todo.title }
            this.props.onCancel(e)
        | ENTER_KEY ->
            this.handleSubmit(e)
        | _ -> ()

    member this.handleChange (e: React.SyntheticEvent) =
        if this.props.editing then
            this.setState { editText = string e.target?value }

    member this.shouldComponentUpdate (nextProps: TodoItemProps) (nextState: TodoItemState) =
        not(obj.ReferenceEquals(nextProps.todo, this.props.todo))
        || nextProps.editing <> this.props.editing
        || nextState.editText <> this.state.editText

    member this.componentDidUpdate (prevProps: TodoItemProps) =
        if not prevProps.editing && this.props.editing then
            let node =
                ReactDom.findDOMNode(unbox editField.Value)
                :?> Browser.HTMLInputElement
            node.focus()
            node.setSelectionRange(float node.value.Length, float node.value.Length)

    member this.render () =
        let className =
            classNames(
                createObj [
                    "completed" ==> this.props.todo.completed
                    "editing" ==> this.props.editing
                ])
        // The React helper defines a simple DSL to build HTML elements.
        // For more info about transforming F# unions to JS option objects:
        // https://fable-compiler.github.io/docs/interacting.html#KeyValueList-attribute
        R.li [ ClassName className ] [
            R.div [ ClassName "view" ] [
                R.input [
                    ClassName "toggle"
                    Type "checkbox"
                    Checked this.props.todo.completed
                    OnChange this.props.onToggle  
                ] []
                R.label [ OnDoubleClick this.handleEdit ]
                        [ unbox this.props.todo.title ]
                R.button [
                    ClassName "destroy"
                    OnClick this.props.onDestroy ] [ ]
            ]
            R.input [
                ClassName "edit"
                Ref (fun x -> editField <- Some x)
                Value (U2.Case1 this.state.editText)
                OnBlur this.handleSubmit
                OnChange this.handleChange
                OnKeyDown this.handleKeyDown
            ] []
        ]

The next view is TodoFooter. This component just presents some buttons below the Todo list to filter by or change the completed property of the Todos.

Same as TodoItem, notice the component subscribes to some events (like OnClick) but instead of containing the logic to react to the event it just runs a callback received from its parent through the props object. Remember the state of React components cannot be directly updated, so this is a way to transmit the event to the parent and let it re-render the subtree if necessary.

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
12: 
13: 
14: 
15: 
16: 
17: 
18: 
19: 
20: 
21: 
22: 
23: 
24: 
25: 
26: 
27: 
28: 
29: 
30: 
31: 
32: 
33: 
34: 
35: 
36: 
37: 
38: 
39: 
40: 
41: 
42: 
43: 
44: 
45: 
46: 
47: 
type TodoFooterProps =
    abstract count: int
    abstract completedCount: int
    abstract onClearCompleted: obj->unit
    abstract nowShowing: string

type TodoFooter(props, ctx) =
    inherit React.Component<TodoFooterProps,obj>(props, ctx)
    member this.render () =
        let activeTodoWord =
            "item" + (if this.props.count = 1 then "" else "s")
        let clearButton =
            if this.props.completedCount > 0 then
                R.button [
                    ClassName "clear-completed"
                    OnClick this.props.onClearCompleted
                ] [ unbox "Clear completed" ] |> Some
            else None
        let className category =
            classNames(
                createObj ["selected" ==> (this.props.nowShowing = category)])
        R.footer [ ClassName "footer" ] [
            R.span [ ClassName "todo-count" ] [
                R.strong [] [ unbox this.props.count ]
                unbox (" " + activeTodoWord + " left")
            ]
            R.ul [ ClassName "filters" ] [
                R.li [] [
                    R.a [
                        Href "#/"
                        ClassName (className ALL_TODOS)
                    ] [ unbox "All" ] ]
                unbox " "
                R.li [] [
                    R.a [
                        Href "#/active"
                        ClassName (className ACTIVE_TODOS)
                    ] [ unbox "Active" ] ]
                unbox " "
                R.li [] [
                    R.a [
                        Href "#/completed"
                        ClassName (className COMPLETED_TODOS)
                    ] [ unbox "Completed" ] ]
                clearButton.Value
            ]
        ]

We finish with the TodoApp view. This component is the parent of the two previously defined components, which are invoked in render by calling the R.com helper. Notice that, among the arguments of R.com, we use F# object expressions to build the props.

In the original JS implementation of componentDidMount method, we need to take care to preserve the meaning of this when passing a lambda to another object by using bind. Luckily, that's not something we need to worry about in Fable :)

Note also we haven't defined an interface for the object returned by Router, so we just access its init method with the dynamic ? operator.

  1: 
  2: 
  3: 
  4: 
  5: 
  6: 
  7: 
  8: 
  9: 
 10: 
 11: 
 12: 
 13: 
 14: 
 15: 
 16: 
 17: 
 18: 
 19: 
 20: 
 21: 
 22: 
 23: 
 24: 
 25: 
 26: 
 27: 
 28: 
 29: 
 30: 
 31: 
 32: 
 33: 
 34: 
 35: 
 36: 
 37: 
 38: 
 39: 
 40: 
 41: 
 42: 
 43: 
 44: 
 45: 
 46: 
 47: 
 48: 
 49: 
 50: 
 51: 
 52: 
 53: 
 54: 
 55: 
 56: 
 57: 
 58: 
 59: 
 60: 
 61: 
 62: 
 63: 
 64: 
 65: 
 66: 
 67: 
 68: 
 69: 
 70: 
 71: 
 72: 
 73: 
 74: 
 75: 
 76: 
 77: 
 78: 
 79: 
 80: 
 81: 
 82: 
 83: 
 84: 
 85: 
 86: 
 87: 
 88: 
 89: 
 90: 
 91: 
 92: 
 93: 
 94: 
 95: 
 96: 
 97: 
 98: 
 99: 
100: 
101: 
102: 
103: 
104: 
105: 
106: 
107: 
108: 
109: 
110: 
111: 
112: 
113: 
114: 
115: 
116: 
117: 
118: 
119: 
type TodoAppProps = { model: TodoModel }
type TodoAppState = { nowShowing: string; editing: Guid option; newTodo: string }

type TodoApp(props, ctx) as this =
    inherit React.Component<TodoAppProps, TodoAppState>(props, ctx)
    do this.state <- { nowShowing=ALL_TODOS; editing=None; newTodo="" }

    member this.componentDidMount () =
        let nowShowing category =
            fun () -> this.setState({this.state with nowShowing = category})
        let router =
            Router(createObj [
                    "/" ==> nowShowing ALL_TODOS
                    "/active" ==> nowShowing ACTIVE_TODOS
                    "/completed" ==> nowShowing COMPLETED_TODOS
            ])
        router?init("/")

    member this.handleChange (ev: React.SyntheticEvent) =
        this.setState({ this.state with newTodo = unbox ev.target?value })

    member this.handleNewTodoKeyDown (ev: React.KeyboardEvent) =
        if ev.keyCode = ENTER_KEY then
            ev.preventDefault()
            let v = this.state.newTodo.Trim()
            if v.Length > 0 then
                this.props.model.addTodo(v)
                this.setState({ this.state with newTodo = "" })

    member this.toggleAll (ev: React.SyntheticEvent) =
        this.props.model.toggleAll(unbox ev.target?``checked``)

    member this.toggle (todoToToggle) =
        this.props.model.toggle(todoToToggle)

    member this.destroy (todo) =
        this.props.model.destroy(todo)

    member this.edit (todo: Todo) =
        this.setState({ this.state with editing = Some todo.id })

    member this.save (todoToSave, text) =
        this.props.model.save(todoToSave, text)
        this.setState({ this.state with editing = None })

    member this.cancel () =
        this.setState({ this.state with editing = None })

    member this.clearCompleted () =
        this.props.model.clearCompleted()

    member this.render () =
        let todos = this.props.model.todos
        let todoItems = 
            todos
            |> Seq.filter (fun todo ->
                match this.state.nowShowing with
                | ACTIVE_TODOS -> not todo.completed
                | COMPLETED_TODOS -> todo.completed
                | _ -> true)
            |> Seq.map (fun todo ->
                R.com<TodoItem,_,_>(
                    { new TodoItemProps with
                        member __.key = todo.id
                        member __.todo = todo
                        member __.onToggle _ = this.toggle(todo)
                        member __.onDestroy _ = this.destroy(todo)
                        member __.onEdit _ = this.edit(todo)
                        member __.editing =
                            match this.state.editing with
                            | Some editing -> editing = todo.id
                            | None -> false
                        member __.onSave text = this.save(todo, string text)
                        member __.onCancel _ = this.cancel()
                    }) [])
                |> Seq.toList
        let activeTodoCount =
            todos |> Array.fold (fun accum todo ->
                if todo.completed then accum else accum + 1
            ) 0
        let completedCount =
            todos.Length - activeTodoCount
        let footer =
            if activeTodoCount > 0 || completedCount > 0 then
                R.com<TodoFooter,_,_>(
                    { new TodoFooterProps with
                        member __.count = activeTodoCount
                        member __.completedCount = completedCount
                        member __.nowShowing = this.state.nowShowing
                        member __.onClearCompleted _ = this.clearCompleted()
                }) [] |> Some
            else None
        let main =
            if todos.Length > 0 then
                R.section [ ClassName "main" ] [
                    R.input [
                        ClassName "toggle-all"
                        Type "checkbox"
                        OnChange this.toggleAll
                        Checked (activeTodoCount = 0)
                    ] []
                    R.ul [ ClassName "todo-list" ] todoItems
                ] |> Some
            else None
        R.div [] [
            R.header [ ClassName "header" ] [
                R.h1 [] [ unbox "todos" ]
                R.input [
                    ClassName "new-todo"
                    Placeholder "What needs to be done?"
                    Value (U2.Case1 this.state.newTodo)
                    OnKeyDown this.handleNewTodoKeyDown
                    OnChange this.handleChange
                    AutoFocus true
                ] []
            ]
            main.Value
            footer.Value
        ]

Firing up the app

There's nothing left to do but building our model, mount our TodoApp view in the DOM by using ReactDom.render and subscribe to the events. Happy coding!

1: 
2: 
3: 
4: 
5: 
6: 
7: 
8: 
let model = TodoModel("react-todos")
let render() =
    ReactDom.render(
        R.com<TodoApp,_,_> { model = model } [],
        Browser.document.getElementsByClassName("todoapp").[0]
    ) |> ignore
model.subscribe(render)
render()
namespace System
namespace Fable
namespace Fable.Core
module JsInterop

from Fable.Core
namespace Fable.Import
Multiple items
type ImportAttribute =
  inherit Attribute
  new : get:string * from:string -> ImportAttribute

Full name: Fable.Core.ImportAttribute

--------------------
new : get:string * from:string -> ImportAttribute
val classNames : o:obj -> string

Full name: React-todomvc.classNames
val o : obj
type obj = Object

Full name: Microsoft.FSharp.Core.obj
Multiple items
val string : value:'T -> string

Full name: Microsoft.FSharp.Core.Operators.string

--------------------
type string = String

Full name: Microsoft.FSharp.Core.string
val failwith : message:string -> 'T

Full name: Microsoft.FSharp.Core.Operators.failwith
Multiple items
type GlobalAttribute =
  inherit Attribute
  new : unit -> GlobalAttribute

Full name: Fable.Core.GlobalAttribute

--------------------
new : unit -> GlobalAttribute
val Router : o:obj -> obj

Full name: React-todomvc.Router
val load : key:string -> 'T option

Full name: React-todomvc.Util.load
val key : string
module Browser

from Fable.Import
val localStorage : Browser.Storage

Full name: Fable.Import.Browser.localStorage
val unbox : value:obj -> 'T

Full name: Microsoft.FSharp.Core.Operators.unbox
module Option

from Microsoft.FSharp.Core
val map : mapping:('T -> 'U) -> option:'T option -> 'U option

Full name: Microsoft.FSharp.Core.Option.map
module JS

from Fable.Import
Multiple items
val JSON : JS.JSON

Full name: Fable.Import.JS.JSON

--------------------
type JSON =
  interface
    abstract member ( [Symbol.toStringTag] ) : obj
    abstract member parse : text:string * ?reviver:Func<obj,obj,obj> -> obj
    abstract member ( [Symbol.toStringTag] ) : obj with set
    abstract member stringify : value:obj * replacer:ResizeArray<obj> * space:U2<string,float> -> string
    abstract member stringify : value:obj * replacer:Func<string,obj,obj> * space:U2<string,float> -> string
    abstract member stringify : value:obj * replacer:ResizeArray<obj> -> string
    abstract member stringify : value:obj * replacer:Func<string,obj,obj> -> string
    abstract member stringify : value:obj -> string
  end

Full name: Fable.Import.JS.JSON
val save : key:string -> data:'T -> unit

Full name: React-todomvc.Util.save
val data : 'T
type Todo =
  {id: Guid;
   title: string;
   completed: bool;}

Full name: React-todomvc.Todo
Todo.id: Guid
Multiple items
type Guid =
  struct
    new : b:byte[] -> Guid + 4 overloads
    member CompareTo : value:obj -> int + 1 overload
    member Equals : o:obj -> bool + 1 overload
    member GetHashCode : unit -> int
    member ToByteArray : unit -> byte[]
    member ToString : unit -> string + 2 overloads
    static val Empty : Guid
    static member NewGuid : unit -> Guid
    static member Parse : input:string -> Guid
    static member ParseExact : input:string * format:string -> Guid
    ...
  end

Full name: System.Guid

--------------------
Guid()
Guid(b: byte []) : unit
Guid(g: string) : unit
Guid(a: int, b: int16, c: int16, d: byte []) : unit
Guid(a: uint32, b: uint16, c: uint16, d: byte, e: byte, f: byte, g: byte, h: byte, i: byte, j: byte, k: byte) : unit
Guid(a: int, b: int16, c: int16, d: byte, e: byte, f: byte, g: byte, h: byte, i: byte, j: byte, k: byte) : unit
Todo.title: string
Todo.completed: bool
type bool = Boolean

Full name: Microsoft.FSharp.Core.bool
Multiple items
type TodoModel =
  new : key:string -> TodoModel
  member addTodo : title:string -> unit
  member clearCompleted : unit -> unit
  member destroy : todoToDestroy:Todo -> unit
  member key : string
  member onChanges : (unit -> unit) []
  member todos : Todo []
  member inform : unit -> unit
  member save : todoToSave:Todo * text:string -> unit
  member onChanges : (unit -> unit) [] with set
  ...

Full name: React-todomvc.TodoModel

--------------------
new : key:string -> TodoModel
val defaultArg : arg:'T option -> defaultValue:'T -> 'T

Full name: Microsoft.FSharp.Core.Operators.defaultArg
module Util

from React-todomvc
val set : elements:seq<'T> -> Set<'T> (requires comparison)

Full name: Microsoft.FSharp.Core.ExtraTopLevelOperators.set
type unit = Unit

Full name: Microsoft.FSharp.Core.unit
val this : TodoModel
member TodoModel.subscribe : onChange:(unit -> unit) -> unit

Full name: React-todomvc.TodoModel.subscribe
val onChange : (unit -> unit)
property TodoModel.onChanges: (unit -> unit) []
member TodoModel.inform : unit -> unit

Full name: React-todomvc.TodoModel.inform
property TodoModel.key: string
property TodoModel.todos: Todo []
module Seq

from Microsoft.FSharp.Collections
val iter : action:('T -> unit) -> source:seq<'T> -> unit

Full name: Microsoft.FSharp.Collections.Seq.iter
val cb : (unit -> unit)
member TodoModel.addTodo : title:string -> unit

Full name: React-todomvc.TodoModel.addTodo
val title : string
val id : x:'T -> 'T

Full name: Microsoft.FSharp.Core.Operators.id
Guid.NewGuid() : Guid
type Array =
  member Clone : unit -> obj
  member CopyTo : array:Array * index:int -> unit + 1 overload
  member GetEnumerator : unit -> IEnumerator
  member GetLength : dimension:int -> int
  member GetLongLength : dimension:int -> int64
  member GetLowerBound : dimension:int -> int
  member GetUpperBound : dimension:int -> int
  member GetValue : [<ParamArray>] indices:int[] -> obj + 7 overloads
  member Initialize : unit -> unit
  member IsFixedSize : bool
  ...

Full name: System.Array
val append : array1:'T [] -> array2:'T [] -> 'T []

Full name: Microsoft.FSharp.Collections.Array.append
member TodoModel.inform : unit -> unit
member TodoModel.toggleAll : checked':bool -> unit

Full name: React-todomvc.TodoModel.toggleAll
val checked' : bool
val map : mapping:('T -> 'U) -> array:'T [] -> 'U []

Full name: Microsoft.FSharp.Collections.Array.map
val todo : Todo
member TodoModel.toggle : todoToToggle:Todo -> unit

Full name: React-todomvc.TodoModel.toggle
val todoToToggle : Todo
val not : value:bool -> bool

Full name: Microsoft.FSharp.Core.Operators.not
member TodoModel.destroy : todoToDestroy:Todo -> unit

Full name: React-todomvc.TodoModel.destroy
val todoToDestroy : Todo
val filter : predicate:('T -> bool) -> array:'T [] -> 'T []

Full name: Microsoft.FSharp.Collections.Array.filter
member TodoModel.save : todoToSave:Todo * text:string -> unit

Full name: React-todomvc.TodoModel.save
val todoToSave : Todo
val text : string
member TodoModel.clearCompleted : unit -> unit

Full name: React-todomvc.TodoModel.clearCompleted
module React

from Fable.Helpers
namespace Fable.Helpers
module Props

from Fable.Helpers.React
type TodoItemState =
  {editText: string;}

Full name: React-todomvc.TodoItemState
TodoItemState.editText: string
type TodoItemProps =
  interface
    abstract member editing : bool
    abstract member key : Guid
    abstract member todo : Todo
    abstract member onCancel : obj -> unit
    abstract member onDestroy : obj -> unit
    abstract member onEdit : obj -> unit
    abstract member onSave : obj -> unit
    abstract member onToggle : obj -> unit
  end

Full name: React-todomvc.TodoItemProps
abstract member TodoItemProps.key : Guid

Full name: React-todomvc.TodoItemProps.key
abstract member TodoItemProps.todo : Todo

Full name: React-todomvc.TodoItemProps.todo
abstract member TodoItemProps.editing : bool

Full name: React-todomvc.TodoItemProps.editing
abstract member TodoItemProps.onSave : obj -> unit

Full name: React-todomvc.TodoItemProps.onSave
abstract member TodoItemProps.onEdit : obj -> unit

Full name: React-todomvc.TodoItemProps.onEdit
abstract member TodoItemProps.onDestroy : obj -> unit

Full name: React-todomvc.TodoItemProps.onDestroy
abstract member TodoItemProps.onCancel : obj -> unit

Full name: React-todomvc.TodoItemProps.onCancel
abstract member TodoItemProps.onToggle : obj -> unit

Full name: React-todomvc.TodoItemProps.onToggle
Multiple items
type LiteralAttribute =
  inherit Attribute
  new : unit -> LiteralAttribute

Full name: Microsoft.FSharp.Core.LiteralAttribute

--------------------
new : unit -> LiteralAttribute
val ESCAPE_KEY : float

Full name: React-todomvc.ESCAPE_KEY
val ENTER_KEY : float

Full name: React-todomvc.ENTER_KEY
val ALL_TODOS : string

Full name: React-todomvc.ALL_TODOS
val ACTIVE_TODOS : string

Full name: React-todomvc.ACTIVE_TODOS
val COMPLETED_TODOS : string

Full name: React-todomvc.COMPLETED_TODOS
Multiple items
type TodoItem =
  inherit Component<TodoItemProps,TodoItemState>
  new : props:TodoItemProps * ctx:obj -> TodoItem
  member componentDidUpdate : prevProps:TodoItemProps -> unit
  member handleChange : e:SyntheticEvent -> unit
  member handleEdit : ev:MouseEvent -> unit
  member handleKeyDown : e:KeyboardEvent -> unit
  member handleSubmit : e:SyntheticEvent -> unit
  member render : unit -> ReactElement<obj>
  member shouldComponentUpdate : nextProps:TodoItemProps -> nextState:TodoItemState -> bool

Full name: React-todomvc.TodoItem

--------------------
new : props:TodoItemProps * ctx:obj -> TodoItem
val props : TodoItemProps
val ctx : obj
val this : TodoItem
Multiple items
val React : React.Globals

Full name: Fable.Import.React_Extensions.React

--------------------
module React

from Fable.Helpers

--------------------
module React

from Fable.Import
Multiple items
type Component<'P,'S> =
  interface ComponentLifecycle<'P,'S>
  new : ?props:'P * ?context:obj -> Component<'P,'S>
  member forceUpdate : ?callBack:Func<unit,obj> -> unit
  member context : obj
  member props : 'P
  member refs : obj
  member state : 'S
  member render : unit -> ReactElement<'P>
  member setState : f:Func<'S,'P,'S> * ?callback:Func<unit,obj> -> unit
  member setState : state:'S * ?callback:Func<unit,obj> -> unit
  ...

Full name: Fable.Import.React.Component<_,_>

--------------------
new : ?props:'P * ?context:obj -> React.Component<'P,'S>
val mutable editField : obj option
type 'T option = Option<'T>

Full name: Microsoft.FSharp.Core.option<_>
union case Option.None: Option<'T>
member TodoItem.handleSubmit : e:React.SyntheticEvent -> unit

Full name: React-todomvc.TodoItem.handleSubmit
val e : React.SyntheticEvent
type SyntheticEvent =
  interface
    abstract member bubbles : bool
    abstract member cancelable : bool
    abstract member currentTarget : EventTarget
    abstract member defaultPrevented : bool
    abstract member eventPhase : float
    abstract member isTrusted : bool
    abstract member nativeEvent : Event
    abstract member target : EventTarget
    abstract member timeStamp : DateTime
    abstract member type : string
    ...
  end

Full name: Fable.Import.React.SyntheticEvent
property React.Component.state: TodoItemState
String.Trim() : string
String.Trim([<ParamArray>] trimChars: char []) : string
val value : string
property String.Length: int
property React.Component.props: TodoItemProps
abstract member TodoItemProps.onSave : obj -> unit
member React.Component.setState : f:Func<'S,'P,'S> * ?callback:Func<unit,obj> -> unit
member React.Component.setState : state:'S * ?callback:Func<unit,obj> -> unit
abstract member TodoItemProps.onDestroy : obj -> unit
member TodoItem.handleEdit : ev:React.MouseEvent -> unit

Full name: React-todomvc.TodoItem.handleEdit
val ev : React.MouseEvent
type MouseEvent =
  interface
    inherit SyntheticEvent
    abstract member getModifierState : key:string -> bool
    abstract member altKey : bool
    abstract member button : float
    abstract member buttons : float
    abstract member clientX : float
    abstract member clientY : float
    abstract member ctrlKey : bool
    abstract member metaKey : bool
    abstract member pageX : float
    ...
  end

Full name: Fable.Import.React.MouseEvent
abstract member TodoItemProps.onEdit : obj -> unit
property TodoItemProps.todo: Todo
member TodoItem.handleKeyDown : e:React.KeyboardEvent -> unit

Full name: React-todomvc.TodoItem.handleKeyDown
val e : React.KeyboardEvent
type KeyboardEvent =
  interface
    inherit SyntheticEvent
    abstract member getModifierState : key:string -> bool
    abstract member altKey : bool
    abstract member charCode : float
    abstract member ctrlKey : bool
    abstract member key : string
    abstract member keyCode : float
    abstract member locale : string
    abstract member location : float
    abstract member metaKey : bool
    ...
  end

Full name: Fable.Import.React.KeyboardEvent
property React.KeyboardEvent.which: float
abstract member TodoItemProps.onCancel : obj -> unit
member TodoItem.handleSubmit : e:React.SyntheticEvent -> unit
member TodoItem.handleChange : e:React.SyntheticEvent -> unit

Full name: React-todomvc.TodoItem.handleChange
property TodoItemProps.editing: bool
property React.SyntheticEvent.target: Browser.EventTarget
member TodoItem.shouldComponentUpdate : nextProps:TodoItemProps -> nextState:TodoItemState -> bool

Full name: React-todomvc.TodoItem.shouldComponentUpdate
val nextProps : TodoItemProps
val nextState : TodoItemState
Object.ReferenceEquals(objA: obj, objB: obj) : bool
member TodoItem.componentDidUpdate : prevProps:TodoItemProps -> unit

Full name: React-todomvc.TodoItem.componentDidUpdate
val prevProps : TodoItemProps
val node : Browser.HTMLInputElement
Multiple items
val ReactDom : ReactDom.Globals

Full name: Fable.Import.React_Extensions.ReactDom

--------------------
module ReactDom

from Fable.Import
member ReactDom.Globals.findDOMNode : instance:React.ReactInstance -> 'E
member ReactDom.Globals.findDOMNode : instance:React.ReactInstance -> Browser.Element
property Option.Value: obj
Multiple items
val HTMLInputElement : Browser.HTMLInputElementType

Full name: Fable.Import.Browser.HTMLInputElement

--------------------
type HTMLInputElement =
  interface
    inherit HTMLElement
    abstract member checkValidity : unit -> bool
    abstract member createTextRange : unit -> TextRange
    abstract member accept : string
    abstract member align : string
    abstract member alt : string
    abstract member autocomplete : string
    abstract member autofocus : bool
    abstract member border : string
    abstract member checked : bool
    ...
  end

Full name: Fable.Import.Browser.HTMLInputElement
abstract member Browser.HTMLElement.focus : unit -> unit
abstract member Browser.HTMLInputElement.setSelectionRange : start:float * end:float -> unit
Multiple items
val float : value:'T -> float (requires member op_Explicit)

Full name: Microsoft.FSharp.Core.Operators.float

--------------------
type float = Double

Full name: Microsoft.FSharp.Core.float

--------------------
type float<'Measure> = float

Full name: Microsoft.FSharp.Core.float<_>
property Browser.HTMLInputElement.value: string
member TodoItem.render : unit -> React.ReactElement<obj>

Full name: React-todomvc.TodoItem.render
val className : string
val createObj : fields:#seq<string * obj> -> obj

Full name: Fable.Core.JsInterop.createObj
val internal li : b:IHTMLProp list -> c:React.ReactElement<obj> list -> React.ReactElement<obj>

Full name: Fable.Helpers.React.li
union case HTMLAttr.ClassName: string -> HTMLAttr
val internal div : b:IHTMLProp list -> c:React.ReactElement<obj> list -> React.ReactElement<obj>

Full name: Fable.Helpers.React.div
val internal input : b:IHTMLProp list -> c:React.ReactElement<obj> list -> React.ReactElement<obj>

Full name: Fable.Helpers.React.input
Multiple items
union case HTMLAttr.Type: string -> HTMLAttr

--------------------
type Type =
  inherit MemberInfo
  member Assembly : Assembly
  member AssemblyQualifiedName : string
  member Attributes : TypeAttributes
  member BaseType : Type
  member ContainsGenericParameters : bool
  member DeclaringMethod : MethodBase
  member DeclaringType : Type
  member Equals : o:obj -> bool + 1 overload
  member FindInterfaces : filter:TypeFilter * filterCriteria:obj -> Type[]
  member FindMembers : memberType:MemberTypes * bindingAttr:BindingFlags * filter:MemberFilter * filterCriteria:obj -> MemberInfo[]
  ...

Full name: System.Type
Multiple items
union case HTMLAttr.Checked: bool -> HTMLAttr

--------------------
module Checked

from Microsoft.FSharp.Core.ExtraTopLevelOperators

--------------------
module Checked

from Microsoft.FSharp.Core.Operators
union case DOMAttr.OnChange: (React.FormEvent -> unit) -> DOMAttr
abstract member TodoItemProps.onToggle : obj -> unit
val internal label : b:IHTMLProp list -> c:React.ReactElement<obj> list -> React.ReactElement<obj>

Full name: Fable.Helpers.React.label
union case DOMAttr.OnDoubleClick: (React.MouseEvent -> unit) -> DOMAttr
member TodoItem.handleEdit : ev:React.MouseEvent -> unit
val internal button : b:IHTMLProp list -> c:React.ReactElement<obj> list -> React.ReactElement<obj>

Full name: Fable.Helpers.React.button
union case DOMAttr.OnClick: (React.MouseEvent -> unit) -> DOMAttr
union case Prop.Ref: (obj -> unit) -> Prop
val x : obj
union case Option.Some: Value: 'T -> Option<'T>
union case HTMLAttr.Value: U2<string,ResizeArray<string>> -> HTMLAttr
type U2<'a,'b> =
  | Case1 of 'a
  | Case2 of 'b

Full name: Fable.Core.U2<_,_>
union case U2.Case1: 'a -> U2<'a,'b>
union case DOMAttr.OnBlur: (React.FocusEvent -> unit) -> DOMAttr
member TodoItem.handleChange : e:React.SyntheticEvent -> unit
union case DOMAttr.OnKeyDown: (React.KeyboardEvent -> unit) -> DOMAttr
member TodoItem.handleKeyDown : e:React.KeyboardEvent -> unit
type TodoFooterProps =
  interface
    abstract member completedCount : int
    abstract member count : int
    abstract member nowShowing : string
    abstract member onClearCompleted : obj -> unit
  end

Full name: React-todomvc.TodoFooterProps
abstract member TodoFooterProps.count : int

Full name: React-todomvc.TodoFooterProps.count
Multiple items
val int : value:'T -> int (requires member op_Explicit)

Full name: Microsoft.FSharp.Core.Operators.int

--------------------
type int = int32

Full name: Microsoft.FSharp.Core.int

--------------------
type int<'Measure> = int

Full name: Microsoft.FSharp.Core.int<_>
abstract member TodoFooterProps.completedCount : int

Full name: React-todomvc.TodoFooterProps.completedCount
abstract member TodoFooterProps.onClearCompleted : obj -> unit

Full name: React-todomvc.TodoFooterProps.onClearCompleted
abstract member TodoFooterProps.nowShowing : string

Full name: React-todomvc.TodoFooterProps.nowShowing
Multiple items
type TodoFooter =
  inherit Component<TodoFooterProps,obj>
  new : props:TodoFooterProps * ctx:obj -> TodoFooter
  member render : unit -> ReactElement<obj>

Full name: React-todomvc.TodoFooter

--------------------
new : props:TodoFooterProps * ctx:obj -> TodoFooter
val props : TodoFooterProps
val this : TodoFooter
member TodoFooter.render : unit -> React.ReactElement<obj>

Full name: React-todomvc.TodoFooter.render
val activeTodoWord : string
property React.Component.props: TodoFooterProps
property TodoFooterProps.count: int
val clearButton : React.ReactElement<obj> option
property TodoFooterProps.completedCount: int
abstract member TodoFooterProps.onClearCompleted : obj -> unit
val className : (string -> string)
val category : string
property TodoFooterProps.nowShowing: string
val internal footer : b:IHTMLProp list -> c:React.ReactElement<obj> list -> React.ReactElement<obj>

Full name: Fable.Helpers.React.footer
val internal span : b:IHTMLProp list -> c:React.ReactElement<obj> list -> React.ReactElement<obj>

Full name: Fable.Helpers.React.span
val internal strong : b:IHTMLProp list -> c:React.ReactElement<obj> list -> React.ReactElement<obj>

Full name: Fable.Helpers.React.strong
val internal ul : b:IHTMLProp list -> c:React.ReactElement<obj> list -> React.ReactElement<obj>

Full name: Fable.Helpers.React.ul
val internal a : b:IHTMLProp list -> c:React.ReactElement<obj> list -> React.ReactElement<obj>

Full name: Fable.Helpers.React.a
union case HTMLAttr.Href: string -> HTMLAttr
property Option.Value: React.ReactElement<obj>
type TodoAppProps =
  {model: TodoModel;}

Full name: React-todomvc.TodoAppProps
TodoAppProps.model: TodoModel
type TodoAppState =
  {nowShowing: string;
   editing: Guid option;
   newTodo: string;}

Full name: React-todomvc.TodoAppState
TodoAppState.nowShowing: string
TodoAppState.editing: Guid option
TodoAppState.newTodo: string
Multiple items
type TodoApp =
  inherit Component<TodoAppProps,TodoAppState>
  new : props:TodoAppProps * ctx:obj -> TodoApp
  member cancel : unit -> unit
  member clearCompleted : unit -> unit
  member componentDidMount : unit -> obj
  member destroy : todo:Todo -> unit
  member edit : todo:Todo -> unit
  member handleChange : ev:SyntheticEvent -> unit
  member handleNewTodoKeyDown : ev:KeyboardEvent -> unit
  member render : unit -> ReactElement<obj>
  ...

Full name: React-todomvc.TodoApp

--------------------
new : props:TodoAppProps * ctx:obj -> TodoApp
val props : TodoAppProps
val this : TodoApp
member TodoApp.componentDidMount : unit -> obj

Full name: React-todomvc.TodoApp.componentDidMount
val nowShowing : (string -> unit -> unit)
property React.Component.state: TodoAppState
val router : obj
member TodoApp.handleChange : ev:React.SyntheticEvent -> unit

Full name: React-todomvc.TodoApp.handleChange
val ev : React.SyntheticEvent
member TodoApp.handleNewTodoKeyDown : ev:React.KeyboardEvent -> unit

Full name: React-todomvc.TodoApp.handleNewTodoKeyDown
val ev : React.KeyboardEvent
property React.KeyboardEvent.keyCode: float
abstract member React.SyntheticEvent.preventDefault : unit -> unit
val v : string
property React.Component.props: TodoAppProps
member TodoModel.addTodo : title:string -> unit
member TodoApp.toggleAll : ev:React.SyntheticEvent -> unit

Full name: React-todomvc.TodoApp.toggleAll
member TodoModel.toggleAll : checked':bool -> unit
member TodoApp.toggle : todoToToggle:Todo -> unit

Full name: React-todomvc.TodoApp.toggle
member TodoModel.toggle : todoToToggle:Todo -> unit
member TodoApp.destroy : todo:Todo -> unit

Full name: React-todomvc.TodoApp.destroy
member TodoModel.destroy : todoToDestroy:Todo -> unit
member TodoApp.edit : todo:Todo -> unit

Full name: React-todomvc.TodoApp.edit
member TodoApp.save : todoToSave:Todo * text:string -> unit

Full name: React-todomvc.TodoApp.save
member TodoModel.save : todoToSave:Todo * text:string -> unit
member TodoApp.cancel : unit -> unit

Full name: React-todomvc.TodoApp.cancel
member TodoApp.clearCompleted : unit -> unit

Full name: React-todomvc.TodoApp.clearCompleted
member TodoModel.clearCompleted : unit -> unit
member TodoApp.render : unit -> React.ReactElement<obj>

Full name: React-todomvc.TodoApp.render
val todos : Todo []
val todoItems : React.ReactElement<obj> list
val filter : predicate:('T -> bool) -> source:seq<'T> -> seq<'T>

Full name: Microsoft.FSharp.Collections.Seq.filter
val map : mapping:('T -> 'U) -> source:seq<'T> -> seq<'U>

Full name: Microsoft.FSharp.Collections.Seq.map
val internal com<'T,'P,'S (requires 'T :> React.Component<'P,'S>)> : props:'P -> children:React.ReactElement<obj> list -> React.ReactElement<obj>

Full name: Fable.Helpers.React.com
val __ : TodoItemProps
member TodoApp.toggle : todoToToggle:Todo -> unit
member TodoApp.destroy : todo:Todo -> unit
member TodoApp.edit : todo:Todo -> unit
val editing : Guid
val text : obj
member TodoApp.save : todoToSave:Todo * text:string -> unit
member TodoApp.cancel : unit -> unit
val toList : source:seq<'T> -> 'T list

Full name: Microsoft.FSharp.Collections.Seq.toList
val activeTodoCount : int
val fold : folder:('State -> 'T -> 'State) -> state:'State -> array:'T [] -> 'State

Full name: Microsoft.FSharp.Collections.Array.fold
val accum : int
val completedCount : int
property Array.Length: int
val footer : React.ReactElement<obj> option
val __ : TodoFooterProps
member TodoApp.clearCompleted : unit -> unit
val main : React.ReactElement<obj> option
val internal section : b:IHTMLProp list -> c:React.ReactElement<obj> list -> React.ReactElement<obj>

Full name: Fable.Helpers.React.section
member TodoApp.toggleAll : ev:React.SyntheticEvent -> unit
val internal header : b:IHTMLProp list -> c:React.ReactElement<obj> list -> React.ReactElement<obj>

Full name: Fable.Helpers.React.header
val internal h1 : b:IHTMLProp list -> c:React.ReactElement<obj> list -> React.ReactElement<obj>

Full name: Fable.Helpers.React.h1
union case HTMLAttr.Placeholder: string -> HTMLAttr
member TodoApp.handleNewTodoKeyDown : ev:React.KeyboardEvent -> unit
member TodoApp.handleChange : ev:React.SyntheticEvent -> unit
union case HTMLAttr.AutoFocus: bool -> HTMLAttr
val model : TodoModel

Full name: React-todomvc.model
val render : unit -> unit

Full name: React-todomvc.render
member ReactDom.Globals.render : element:React.DOMElement<'P> * container:Browser.Element * ?callback:Func<Browser.Element,obj> -> Browser.Element
member ReactDom.Globals.render : element:React.ClassicElement<'P> * container:Browser.Element * ?callback:Func<React.ClassicComponent<'P,'S>,obj> -> React.ClassicComponent<'P,'S>
member ReactDom.Globals.render : element:React.ReactElement<'P> * container:Browser.Element * ?callback:Func<React.Component<'P,'S>,obj> -> React.Component<'P,'S>
val document : Browser.Document

Full name: Fable.Import.Browser.document
val ignore : value:'T -> unit

Full name: Microsoft.FSharp.Core.Operators.ignore
member TodoModel.subscribe : onChange:(unit -> unit) -> unit
Fork me on GitHub