Problems in React that explored in Respo

I wrote a post in Chinese talked about some problem I found during using React in our app. I thought a lot about React and Reagent and finally leading me to Respo, a Responsive DOM library. The post is written in Chinese, I will pick a summary here since it 's too hard for me to write such long post in English:

So, the problems:

  • performance of server side rendering

    I talked about this issue a bit before that React is making use or caching in server side rendering and it's slow in rendering. Servers use caches a lot. I think the best choice for React is to implement component level caching.

    That's like memoisation for every component and the mechanism is built inside library itself. I believe it would be quite a lot to do but also there's a lot space to optimise.

  • code reusing of state is bad, comparing to Store

    We have Redux for abstracting stores, but state is using a mutable JavaScript Object. And this.setState is highly coupled with component instance it is bound to, which means we are not able to reuse this.setState. And it also looks strange when immutable-js is used and the state is still mutable.

  • state get in the way of hot code swapping

    Dan Abramov also mentioned that in his post that "React destroys both the DOM and the local state of your components". If you have learnt about cursor, that's not an unsolvable problem. We may save the states in a tree or something and manage component state like that.

    I don't know if that's possible based on current works of state. But I believe by creating a new library this problem can be solved without making component look weird. I will talk about that later.

  • modularisation of library

    React does not expose the Diff result to developer. I found that interesting if we can divide diff/patching into two library. I experimented putting diff stage on server and putting patch stage on browser. It offers possibilities that we can build app in a different way. It may also benefit works like rendering with Web Workers and Canvas. (Since Facebook is reusing code in React Native, it that already changing in React during the progress?)

  • condition and switch expression in JSX

    CoffeeScript and JSX, we all known that. JSX is currently bad at returning results with if statement.

  • size

    preact is saying it's 3k, that's much smaller than React. I know React is doing a lot more work than other copycats but it's still too large for many websites.

  • toolchains is huge

    Someone already complained that on Twitter(I forgot the link). I would like to compare React ecosystem to ClojureScript(cljs for short). I recently spent a lot time playing with cljs and Boot. So cljs is totally a new language on top of JavaScript, and it's not being more complicated than React in making a tiny app with immutable data and hot code swapping. Yes we can make use of project templates, but it's not enough to keep React still simple.

complexity in Redux

Redux is bringing too much into React. I known there's great idea behind Redux but I want that idea rather than Redux. It's too early to say that's good or bad, and I switch to my own implementation that adopted core ideas from Redux several days after using Redux, but I think it's too much.

And about Respo, here are the repos, I haven't begun working on the docs yet, just code. There are in ClojureScript but in Cirru syntax:

Here's how I define components in Respo:

; removed code for namespace and styles

; event handers may be wrapped with a closure with data
(defn handle-toggle [task state]
  (fn [simple-event dispatch mutate] (dispatch :toggle (:id task))))
; send action with dispach, dispatch function is built inside Respo
; mutate is like `setState` but different, read `:get-state` and `:update-state`

(defn handle-change [task state]
  (fn [simple-event dispatch mutate]
    (dispatch :update {:id (:id task), :text (:value simple-event)})))

(defn handle-remove [task state]
  (fn [simple-event dispatch mutate] (dispatch :rm (:id task))))

(def task-component
 {:name :task, ; set name so Respo can recognise component

  ; for hash-map, I use merge as the transformer, define it as you like
  ; for example: (fn [old-store param1 param2] (some-fn old-store))
  :update-state merge,

  ; this initial state will be passed to `update-state` as `old-store` in the example above
  :get-state (fn [task] {:draft ""}),
  (fn [task] ; render returns a function, `task` is the props, multiple arguments is ok
    (fn [state] [:div ; Hiccup style
                 {:style style-task}
                  {:on-click (handle-toggle task state), ; call event handler, pass in the data it needs
                   :style (style-toggle (:done? task))}]
                  {:placeholder "Describe the task",
                   :value (:text task),
                   :style style-input,
                   :on-change (handle-change task state)}]
                  {:on-click (handle-remove task state),
                   :style style-remove}]]))})
; removed some code of namespace and styles

(defn handle-change [props state]
  (fn [simple-event dispatch mutate]
    (mutate {:draft (:value simple-event)}))) ; mutate all trigger `update-state` function

(defn handle-add [store state]
  (fn [simple-event dispatch mutate]
    (dispatch :add (:draft state))
    (mutate {:draft ""})))

(def todolist-component
 {:name :todolist,
  :get-state (fn [store] {:draft ""}),
  (fn [store]
    (fn [state]
      (let [tasks store]
         {:style style-todolist}
          {:style style-header}
           {:placeholder "new task",
            :value (:draft state),
            :style style-input,
            :on-input (handle-change store state)}]
           {:inner-text "Add",
            :on-click (handle-add store state),
            :style style-add}]]
            (map (fn [task] [(:id task) [task-component task]])) ; insert task-component
            (into (sorted-map)))]]))),
  :update-state merge})

On the issue I talked above:

  • performance of server side rendering

    Rendering component is calling a function so it's not hard to implement memoisation in Respo. I haven't do it yet, but it wouldn't be very hard. The tough part is to make caches really usable, I'm not sure about doing that in a short time.

  • code reusing of state is bad, comparing to Store

    In Respo, mutate is a function without this, so reusing the function is less tricky. I still need to look into that for more details.

  • state get in the way of hot code swapping

    States in Respo is global and storing in a map like(x for component state):

    [0] x
    [0 1] x
    [0 1 1] x
    [0 1 1 1] x
    [0 2] x

    During rerendering states do not lose. So it's ok in Respo.

  • modularisation of library

    The respo is running as a Node.js server. So sure it's decoupled from respo-client. I can pass DOM diff results via WebSocket and the app in browser works. It's heavily effected by the network but anyway it works.

  • condition and switch expression in JSX

    In cljs, if cond case are all expressions.

  • size

    cljs is as huge as React, no need to say.

  • toolchains is huge

    I consider the layers above cljs is very simple. However, cljs itself is huge.

  • complexity in Redux

    Respo does not help with that.

About Respo itself: I'm still exploring with Respo and experiment my ideas about React. I have already use wanderlist(built with Respo) for more than a week so the prototype is already OK. But I will not suggest trying it, for several reason:

  • the code is in Cirru, you don't like Cirru, right?
  • the API corresponding to React.render is quite messy at this moment
  • child component not allowed to modify parent state
  • no lifecycle methods or DOM refs
  • DevTools is not ready
  • cljs is dynamic, debugging is hard

But I'm still in hope that you will be interested in Respo and here are serveral videos on Youtube about Respo, you may check them out:

Write your comment…

Be the first one to comment