ruby-hyperloop / hyper-react

The project has moved to Hyperstack!!
https://hyperstack.org/
MIT License
285 stars 14 forks source link

controlled inputs do not work #248

Open catmando opened 6 years ago

catmando commented 6 years ago

Hyper-react (hyper-store actually) bundles all state changes and fires them after the end of the render cycle.

This defaults heuristics in React's handling of inputs.

Only way to see this is by an example:

Say you have INPUT(value: state.val, type: :text)

The input will initially render with the contents set the state of val, with the cursor at the end.

Until the val state mutates no matter what the user types the text displayed in the input will remain the same.

When val does mutate the input will be re-rendered, and the current state of val will be displayed, with the cursor at the end.

Now lets say you add an input handler like this:

.on(:change) { |evt| mutate.val evt.target.value }

So on every change we mutate val and we would expect per the above description for the input to keep filling in with the value, BUT the cursor would keep jumping to the end.

React fixes this by treating the value parameter of inputs specially. What react does is watch and if the input is rerendered with the same value that the user would be seeing anyway, react does not actually update the input, and cursor stays put.

BUT hyperloop gets us in trouble here, because effectively all mutates are delayed until AFTER the event handler completes. This confuses react, so what react sees is: User types something, there is no re-rerender during the change event, so the input is ignored. Then an instant later a rerender occurs, which react interprets as a new asynchronous state change.

It is possible we can in the latest version of react stop queuing up the state changes, but we have to check into this. It's also possible that we might be able to somehow hook into reacts event handling cycle so that we can fire off our state code, but once, at the last minute. Finally it may be very possible that we can have the outermost render wrapper notice that there are pending state changes, and fire off the changes but within the event cycle.

Meanwhile here is a work around that developers can use (or we could just embed into hyperloop) by using another react feature called defaultValue. The defaultValue param initializes an input, and from then on the input will just track its value internally. The only way to get an input with a defaultValue to accept a new defaultValue is to change its key param.

class Input < Hyperloop::Component
  param :value
  param :on_change, type: Proc
  def key
    @key ||= 0
    @key += 1 unless @current_value == params.value
    @key
  end
  render do
    INPUT(key: key, type: :text, defaultValue: params.value)
    .on(:change) do |evt|
      @current_value = evt.target.value
      on_change(evt)
    end
  end
end

What we do is basically simulate how react does things.

sfcgeorge commented 6 years ago

Turns out I had this can't type in middle issue in addition to my can't type dates issue. So I have added your fix on top of my fix with more details in a comment https://github.com/ruby-hyperloop/hyper-react/issues/245