Fork me on GitHub
#rum
<
2018-05-14
>
Matt Butler14:05:41

I'm trying to wrap my head around this bug (https://github.com/madvas/cljs-react-material-ui/issues/17) the discussion pertains to reagent however rum also experiences this bug. Could someone point me to how (and where) rum handles cursor position on regular old [:input] elements, so I can see what a possible fix might look like? Thanks 🙂

Roman Liutikov14:05:54

have you tried proposed solution in that issue (providing default value)?

Roman Liutikov14:05:11

also make sure that the initial value is an empty string, not nil

4
Roman Liutikov14:05:33

At least in Rum (in Sablono to be precise) when switching from nil to a string as a value on input, Sablono renders different components (controlled (with internal local state) and uncontrolled React components). This usually triggers such behaviours

Roman Liutikov14:05:06

I believe I’ve fixed this in Sablono some time ago, but not sure if that version is being used in Rum now

Matt Butler14:05:41

I have used the default value approach before, however as covered in that thread it has some trade-offs. If for whatever reason, my component re-renders twice, the second state update isnt reflected in the text-field. Im also usually against using default-value where possible as its less "safe".

Roman Liutikov14:05:18

what about nil->string is that your case?

Matt Butler14:05:07

No, I dont think its ever getting nil passed. My blocker to using default value (ignoring if its a good idea in general) is that im going value1 -> value2. Due to some async code and while the rest of the component updates to value2 the text-field component doesnt.

Matt Butler14:05:11

I've fixed that, by forcing my code to be synchronous before rendering the component for the first time, but that feels like a hack. If i want to do any manipulating of the text field value programmatically, then im forced to use :value either way.

Matt Butler14:05:22

So i think what i want to do is see how easy it would be to get :value working as it does for a regular input field. Is there anywhere in rum/sablono that talks about handling cursor position etc.?

Matt Butler14:05:25

I think I have a use case where the return of an async http call should update the value in place. I don't know if its possible to do this without :value

Matt Butler14:05:57

or destroying the eagerly destroying the component using :key somehow.

Roman Liutikov15:05:56

I guess you could try accessing underlying DOM element via refs and changing the value directly

Matt Butler15:05:18

Yeah, suppose thats always an option 🙂

Matt Butler15:05:56

Do you know why in rum a regular [:input] behaves as expected? It seems like reagent has a lot of code to handle this (keeps its own cursor position) etc. and i cant find the corresponding code in rum/sablono.

Niki15:05:05

this problem is a mess, been fixed couple of times in Rum and Sablono, never to the full extent. See https://github.com/tonsky/rum/issues/152 https://github.com/r0man/sablono/issues/148 https://github.com/tonsky/rum/issues/86

Matt Butler15:05:54

thanks tonsky, ill take a look 🙂

Matt Butler19:05:57

You weren't lying were you tonsky, I've been reading/poking around for the last few hours and I'm not sure I even understand the problem in its fullest. I fear I lack the requisite knowledge about how rum+sabolono and react are "glued" together. If there's a good place to start to get your head around this stuff if you could let me know that would be great 🙂

Matt Butler19:05:12

Especially since in my case I'm wrapping a raw react component, I'm not sure if I have an opportunity to "step in" to call (wrap-form-element) or something.

Niki21:05:25

I’m afraid it’s just source code. Rum is pretty straightforward and small, Sablono’s wrap-form-element is pretty comprehensible too

Matt Butler11:05:34

@U050UBKAA Based on a comment here https://github.com/tonsky/rum/issues/152 and this reagent approach to a similar problem https://github.com/metametadata/problems/blob/919abde1997dafef26e1dc8388bb58645ac6f79b/src/hello_world/core.cljs#L45 I ended up going with storing the value locally, sync updating it then calling forceUpdate. Once the async value is passed as props you can diff against the local version. Inflexible and could probably do with some cleanup but appears to work, at least for my use case.

(rum/defcs fixed-text-field <
  {:will-mount
   (fn [state]
     (let [local-state (atom (:value (first (:rum/args state))))
           component   (:rum/react-component state)]
       (add-watch local-state :local-value
                  (fn [_ _ _ _]
                    (rum/request-render component)))
       (assoc state :local-value local-state)))
   :should-update
   (fn [old-state new-state]
     (let [old-props (vec (:rum/args old-state))
           new-props (vec (:rum/args new-state))
           new-value (:value (first (:rum/args new-state)))
           local-value (:local-value old-state)]
       ;is the local atom value not equal to the new prop value, if so update and rerender
       (if (not= new-value @local-value)
         (do
           (reset! local-value new-value)
           true)

         ; other props changed

         (not= (update old-props 0 dissoc :value)
               (update new-props 0 dissoc :value)))))}
    [state component-options]
    (let [local-value (:local-value state)
          this (:rum/react-component state)]
      (ui/text-field (-> component-options
                         (assoc :value @local-value)
                         (update :onChange (fn wrap-on-change [original-on-change]
                                             (fn [event value]
                                               (swap! local-value (fn [] value))
                                               (.forceUpdate this)
                                               (original-on-change event value))))))))

igat6417:05:56

Guys, how to implement counter component below using Rum? Have no idea how to define an initial state for the stateful components using passed arguments.

class Counter extends React.Component {
  constructor(props) {
    super(props)
    this.state = {
      count: props.initialCount
    }
  }
  handleClick: () => {
    this.setState({count: this.state.count + 1})
  }
  render() {
    return (
      <span onClick={this.handleClick}>this.state.count</span>
    )
  }
}

igat6418:05:29

Thank you for the answer. Yes, I read it. But there are no examples that cover described case with initial state that somehow passed from the outside world. Actually just right now I came up with solution (using closure) for case above.

(defn counter-component [initial-count]
  (rum/defcs counter < (rum/local initial-count ::count)
    [state]
    (let [count (::count state)]
      [:div { :on-click (fn [_] (swap! count inc)) } @count])))