Skip to content
New issue

Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? # to your account

Get Rendered Element Ref (React.createRef()) #115

Closed
tiberiusferreira opened this issue May 22, 2019 · 7 comments
Closed

Get Rendered Element Ref (React.createRef()) #115

tiberiusferreira opened this issue May 22, 2019 · 7 comments
Labels
missing functionality Not quite a bug, but needs to be addressed

Comments

@tiberiusferreira
Copy link
Contributor

Hello, I've seen other issues which wanted a similar thing such as #75.

For a project in React I was implementing a chat and I had to "auto-scroll" to the last message when I got a new one. I did this by using:

  messagesEndRef = React.createRef();

  scrollToBottom = () => {
    if (this.messagesEndRef.current != null){
      (this.messagesEndRef.current).scrollIntoView({ behavior: 'smooth' })
    }
  };


  componentDidUpdate(prevProps, prevState, snapshot){
        this.scrollToBottom()
  }

render(){
 <div ref={this.messagesEndRef} />
}

I'm sure there are other use cases for getting a ref to the rendered element.

I think this could be implemented now manually by hooking into the did_mount and manually storing the Node information, but would be nice to see a more ergonomic way of doing it.

I don't have a solution in mind yet, I just wanted to see if this is something Seed would want to implement.

@David-OConnor David-OConnor added the missing functionality Not quite a bug, but needs to be addressed label May 22, 2019
@David-OConnor
Copy link
Member

David-OConnor commented May 22, 2019

Willing to add. Not having it invites cases where elements are called by id etc, which doesn't fit well with the rest of the framework. (Another example of when you'd want it is focusing an element, and other one-off tasks that don't deal with state).

One approach: Let the view assign affected elements a key. (Option<String> ?), and allow events (including those in lifecycle hooks) to locate elements by that key. Shouldn't be too hard, but I might be missing something. This provides a unique identifier not tied to the DOM.

Might not be able to dive in until June.

@David-OConnor
Copy link
Member

David-OConnor commented May 23, 2019

Edit on progress: I was able to execute one-off events like this by assigning elements ids, and using seed::document().get_element_by_id("id") in the update func. As you pointed out, this is not an elegant way of doing this, especially when you need to cast. Example:

fn update(msg: Msg, model: &mut Model, _: &mut Orders<Msg>) {
    match msg {
        Msg::Focus(id) => {
            seed::document()
                .get_element_by_id(id)
                .expect("Can't find by id")
                .dyn_ref::<web_sys::HtmlInputElement>()
                .expect("Can't cast as input element")
                .focus();
        },
        //...
    }
}

It's bad because it's verbose, because it involves manual DOM access and manipulation, and because it may be difficult to assign ids to dynamic elements.

May need to pass the top VDOM element, or App to update as a parameter, in order to give it access to the elements to search through.

@leroycep
Copy link

leroycep commented Sep 5, 2019

Another use case for getting a ref to the rendered elements is rendering charts to a Canvas element, using plotters for example. plotters would need to be changed so that it could accept a CanvasElement, but seed would also need to be able to give a CanvasElement.

@MartinKavik
Copy link
Member

MartinKavik commented Nov 19, 2019

There is a new API orders.after_next_render that you can use to access DOM elements.
Examples:

This API should cover the most cases where you need to get DOM elements, draw on canvas, set focus, etc.

And I can't find a real-world example, where something like ref would be so much better than standard functions like get_element_by_id to balance added complexity and potential problems.
Do you have any ideas?

@MartinKavik
Copy link
Member

Well, I found a case in one of my projects, where it would be nice to use reference.
This is a simpler similar case:
Imagine you have a module (Seed alternative to React components with own Model, view, etc.) which represents a custom button. It generates HTML code like this one:

<div class="my-button btn-wrapper">
     <div class="btn">Click me</div>
</div>

And you want to set focus on the inner element with class .btn even if you click directly on the .btn-wrapper.
So you add click handler on btn-wrapper, get the first .btn from DOM and then call .focus().
However there are some problems:

  • Class btn is too general - you can accidentally get other elements. So you decide to query it by .my-button > .btn. (I hope you've written the selector correctly and you don't plan to modify HTML structure, because I still have PTSD from debugging large jQuery code-bases.)
  • But .my_button > .btn is still too general if you included multiple my-button modules on the same page. So you have to generate random class / id or get unique id from the outside of the module.
  • Ok, we have unique id now. We call get_element_by_id and it's done! No. It panics in runtime because you didn't call it in after_next_render callback / Msg handler or somebody/something removed the node from DOM.
  • You managed to call get_element_by_id correctly. But it returns Element. We need to cast it to HtmlElement so we can call its .focus method. But casting can also fail in runtime...

There is canvas example that integrates a new API (draft) which should mitigate problems above: https://gist.github.com/MartinKavik/cab66fec496687dacee5b73962178b55

  • There is a new Seed type ElRef<ElType>:
    • It derives Default and probably also Debug and Clone.
    • The element reference alone is wrapped into Option and RefCell or Cell.
  • get returns None when:
    • Element hasn't been rendered yet.
    • Element isn't in the DOM at the time of calling get.
    • Element cannot be casted to required type.
    • There are some DOM errors.

@MartinKavik
Copy link
Member

@tiberiusferreira References have been implemented (PR #330) and merged into master. See examples (canvas, user_media or todomvc). Could you confirm that it works for you and eventually close the issue? Thanks.

@tiberiusferreira
Copy link
Contributor Author

Yes it does. Fantastic work!

# for free to join this conversation on GitHub. Already have an account? # to comment
Labels
missing functionality Not quite a bug, but needs to be addressed
Projects
None yet
Development

No branches or pull requests

4 participants