Giter VIP home page Giter VIP logo

Comments (49)

developit avatar developit commented on April 30, 2024 3

100% voted for function refs only, so that seals the deal. 4.0.1 now (only) supports function refs. There are also now some decent tests 🌈

from preact.

developit avatar developit commented on April 30, 2024 1

Regarding overriding render, I agree it's hazardous. preact-compat broke in 3.0 as a result of overriding a private method on Component, so I'm already once-scorned ;) (though at least with render we know that's not going anywhere and the signature is set in stone)

Regarding hooks, I've found them quite useful (*). The hooks aren't really advertised much since they are obviously not available in React and that means scary lock-in (though overriding React.createElement() would be pretty close). If the size stays down, I'm looking to add a bunch of new hooks in 4.0 for augmenting/tracking element and component recycling :)

*: especially for doing performance testing:

import { options } from 'preact';

// doing animations? sync your repaints with the browser's:
options.debounceRendering = requestAnimationFrame;

// want to time rendering?
let time = 0, renders = 0;
options.debounceRendering = f => setTimeout( () => {
 let start = performance.now()
 f();
 time += performance.now() - start;
 renders++;
 if (!renders%10) console.log( renders/time*1000 + 'fps' );
}, 1);

from preact.

developit avatar developit commented on April 30, 2024 1

@cportela Preact only supports function refs. String refs are supported by preact-compat.

class Foo {
  render() {
    return (
      <div>
        <OtherComponent ref={ c => this.otherComponent = c } />
      </div>
    );
  }
}

from preact.

developit avatar developit commented on April 30, 2024 1

@PierBover for sure. I misread your original question though, so I'm not sure this is what you want. Here's a simple example anyway:

class Outer extends Component {
  render() {
    // this.inner is the <div> from Inner
    return <Middle ref_={ c => this.inner = c } />;
  }
}

class Middle extends Component {
  render(props) {
    return <Inner ref={props.ref_} />;
  }
}

class Inner extends Component {
  render() {
    return <div />;
  }
}

Now that I re-read your question though, I think it's worth knowing why you want to be able to directly invoke methods on a component. While it's certainly possible, invoking methods on a component is an anti-pattern - it creates a tight coupling between two components that would otherwise be usable in isolation. In my experience, I've found that exposing the same functionality as props & events ends up scaling better long-term.

class Outer {
  // using an event architecture means information shared between child components is
  // owned and managed by their parent, nicely matching the existing hierarchy ownership.
  handleThing = data => {
    this.setState({ thing: data.thing });
  };

  render(props, state) {
    return (
      <div>
        <One onThing={this.handleThing}>
        <Two thing={state.thing}>
      </div>
    );
  }
}

I wrote an article about this pattern called Props Down, Events Up that you might find interesting.

from preact.

developit avatar developit commented on April 30, 2024 1

ref_ isn't special, the only reason that example works is because Middle is passing props.ref_ into Inner as a ref property. It's just ferrying the function reference down the tree in a non-special property until someone uses it as a ref.

render(props) {
  return <Inner ref={props.ref_} />;
}

There is also this option relating to your original question:

class Outer {
  render(props, state) {
    return (
      <div>
        <One ref={ one => this.setState({ one }) }>
        <Two one={state.one}>
      </div>
    );
  }
}

Just be aware that the intial render pass of Two may not have a one prop since the sibling has not yet been rendered.

from preact.

developit avatar developit commented on April 30, 2024 1

I wouldn't necessarily associate the shared state tree thing with this directly, but it might be a way to circumvent needing to associate two components directly. We're using a simple Store implementation for shared access to an object, and passing it around via context (using a Provider/connect model).

from preact.

developit avatar developit commented on April 30, 2024 1

@JeromeDane not sure what you are looking for an update on... If you mean an update on support for the ref prop, it's already been supported for almost a year (when this issue was closed, in Feb of 2016). Support for String refs will never be included in Preact's core since they are deprecated and a poor design that impacts performance. They have always been available in preact-compat, however.

There is also a standalone helper utility called linkRef that emulates React's String ref behaviour in a way that doesn't fall victim to the same issues.

from preact.

developit avatar developit commented on April 30, 2024

Yup, actually working on this right now! Refs were supported in preact-compat, but in a hacky way (using the DOM). I'm adding them to Preact core similar to how contexts work.

from preact.

fdaciuk avatar fdaciuk commented on April 30, 2024

Nice \o/ :sparkles: :shipit:

from preact.

developit avatar developit commented on April 30, 2024

This also fixes developit/preact-compat-example#2 💥

from preact.

rsaccon avatar rsaccon commented on April 30, 2024

@developit that is great. Just today I was looking into into the sources of preact-compat and react-toolbox to better understand what could have gone wrong with that example.

from preact.

developit avatar developit commented on April 30, 2024

@rsaccon ah, hopefully I can save you some trouble. I'm not likely to get a refs release out until tomorrow morning. For preact-compat, it was using an undocumented and now removed render hook. It should have been using componentDidMount and componentDidUpdate.

from preact.

rsaccon avatar rsaccon commented on April 30, 2024

@developit great, thanks, no hurry

from preact.

idchlife avatar idchlife commented on April 30, 2024

Yay for refs! Don't see another way of getting values of form fields when dynamically constructing it except via refs.

from preact.

developit avatar developit commented on April 30, 2024

@idchlife I only use Controlled Forms, and have never had a use for refs. Personally I find that approach to be much more consistent with the unidirectional rendering model, but I know others may not find it that way.

from preact.

idchlife avatar idchlife commented on April 30, 2024

@developit actually you have one nice approach! And even with dynamic forms you can solve this by adding fields values into object in state. This will be event better. Somehow forgot about this and did this task with looping through fields and getting them by e.target.querySelector([name="${field.name}"])

Thanks!

from preact.

developit avatar developit commented on April 30, 2024

Ah yeah I was doing that for a bit too. When you throw redux into the mix it makes you realize asking the DOM for state is scary/unpredictable. Checkboxes are the worst! Cheers.

from preact.

nightwolfz avatar nightwolfz commented on April 30, 2024

Yay for refs! Don't see another way of getting values of form fields when dynamically constructing it except via refs.

There's an elegant way of doing this:

class UserRegister extends Component {

    constructor(props) {
        super(props);
        this.state = {}
    }

    handleChange(key){
        return {
            value: this.state[key],
            requestChange: (value) => this.setState({[key]: value})
        }
    }

    render(){
        return <section>
            Username
            <input type="text" valueLink={this.handleChange("username")}/>
        </section>
    }
}

from preact.

developit avatar developit commented on April 30, 2024

@nightwolfz does that work in React?

from preact.

unindented avatar unindented commented on April 30, 2024

There are legitimate uses of ref. Right now I need to call the native showModal method on a dialog element. Without a ref, I have to query the DOM...

from preact.

developit avatar developit commented on April 30, 2024

@unindented good point, and that's not something that's particularly easy to achieve via JSX (other than wrapping <dialog> in a component and mapping a show boolean attribute, but that's rescoping the issue).

from preact.

BenoitZugmeyer avatar BenoitZugmeyer commented on April 30, 2024

I was spying on this for a while now and I'd like to bring some thoughts about how to implement this properly. The difficulty is to know which component is generating the vdom, because it could be passed as children of another component. Thus, the common approach is to maintain a global stack of currently rendering components, and link the last component to the generated vdom. This works, but I find this quite ugly.

I had a simple idea to solve this in a clean way: what if the JSX pragma was something like createElement(this)? <div /> would be transformed to createElement(this)("div", null), thus the createElement function would know which component did generate the element. If this is not a component instance, it is probably rendered through a stateless functional component, and it couldn't have refs anyway.

Instead of returning a new function everytime, createElement could be implemented like the following, with a pragma configured to createElement(this)._h:

const defaultRenderer = { _h: h } // where h is the current hyperscript-like function
function createElement(component) {
  return component && component._h ? component : defaultRenderer;
}

// the base component class would have the _h method, using the hyperscript-like
// function and the vnode hook to handle ref attributes.

A pattern React and React-like libraries are failing to solve is when the vdom of a component is rendered by a foreign component. See this example: the Bar component is calling a callback from Foo to get some vdom to render. The fooElement is stored in the Bar refs, but in a developper point of view, vdom returned by Foo#renderInternal should be related to Foo. With the above solution, preact would handle this gracefully.

Anyway, this was my two cents, feel free to use it or not!

from preact.

developit avatar developit commented on April 30, 2024

@BenoitZugmeyer very interesting. I agree that the example does sortof seem like the ref should belong to the parent, but I immediately thought of this counter-example:

let renderInternal = () => (<div ref="fooElement">foo</div>);

class A extends Component {
  render() {
    return <div>{ renderInternal() }</div>;
  }
}

class B extends Component {
  render() {
    return <div>{ renderInternal() }</div>;
  }
}

... where each component should really be given a ref to the rendered div.

I'm not sure where to fall on this one, perhaps that is why I was timid about refs in the first place.

Side note: regarding your contextualized hyperscript reviver, you could also do this:

import { options } from 'preact';

// preact actually supports a vnode hook that is invoked at the end of `h()`:
let oldHook = options.vnode;
options.vnode = vnode => {
  oldHook(vnode);
  let ref = vnode.attributes && vnode.attributes.ref;
  if (ref && current) current[ref] = vnode;
};

let current;

let oldRender = Component.prototype.render;
Component.prototype.render = function(...args) {
  current = this.refs = {};
  let r = oldRender.call(this, ...args);
  if (current===this.refs) current = null;
  return r;
};

... obviously some logic would be needed post-mount to swap vnodes for their DOM counterparts. Perhaps that's the "internal" bit.

from preact.

BenoitZugmeyer avatar BenoitZugmeyer commented on April 30, 2024

Ok it makes sense, I never used this pattern before and find it somewhat hazardous, but it would be the limit of my solution. Preact could warn the user about it though (if this is not a component).

In you example, render should be written like this:

Component.prototype.render = function(...args) {
  let previous = current;
  current = this.refs = {};
  let r = oldRender.call(this, ...args);
  if (current===this.refs) current = previous;
  return r;
};

but yes, it would work, this is the common approach. I love how the vnode hook is public, in my opinion this is a real advantage over other implementations!

from preact.

unindented avatar unindented commented on April 30, 2024

@developit do you think this will make it into a 3.x release, or 4.0?

from preact.

developit avatar developit commented on April 30, 2024

@unindented likely 4.x, because it's backwards-incompatible (previously a ref property would be applied to the DOM as an attribute). That's not to say it won't be soon, I'm actually hoping to publish a beta this evening.

from preact.

unindented avatar unindented commented on April 30, 2024

Woohoo!

from preact.

unindented avatar unindented commented on April 30, 2024

@developit React also supports callbacks as refs (https://facebook.github.io/react/docs/more-about-refs.html#the-ref-callback-attribute), and talks about string refs being legacy in the docs (https://facebook.github.io/react/docs/more-about-refs.html#the-ref-string-attribute).

That'd mean that preact wouldn't need to store the reference to the component. Users would manage that themselves:

  render () {
    return (
      <Dialog ref={(dialog) => { this._dialog = dialog }} />
    );
  }

from preact.

developit avatar developit commented on April 30, 2024

Do you think just supporting function refs would be preferable? I don't use refs so I had thought the string form was more common.

Interesting point though: if preact core only supported function refs, preact-compat could actually use them to implement string refs. Wouldn't work the other way around, so that's another point in favor of just doing function refs in core.

from preact.

unindented avatar unindented commented on April 30, 2024

I think function refs is the way React is going. They seem to be trying to get rid of string refs, but I guess they face resistance because of backwards compatibility.

You don't have that problem here, so I'd go with only function refs in core, and string refs in compat.

from preact.

developit avatar developit commented on April 30, 2024

Perfect. I can definitely see why they want to ditch string refs, they are not free even if you don't use them. Function refs are nearly free because no refs object needs to be allocated, it's more of a hook.

I'm conducting a quick poll here to gauge damage if we didn't support String refs:
https://twitter.com/_developit/status/702142526296293377

from preact.

fdaciuk avatar fdaciuk commented on April 30, 2024

😍

from preact.

idchlife avatar idchlife commented on April 30, 2024

Thank you! @developit

from preact.

unindented avatar unindented commented on April 30, 2024

👍

from preact.

cportela avatar cportela commented on April 30, 2024

So @developit ... did you guys implement the refs as functions or strings in 4.5.1?
Either I am doing something wrong or they are not there yet...
Thanks!

from preact.

cportela avatar cportela commented on April 30, 2024

Perfect @developit, thanks for the instant response!

from preact.

developit avatar developit commented on April 30, 2024

@cportela No problem! :) I just created a little library you can use to get String refs working without using preact-compat, if you don't have any other need for it. I might even use this myself:

https://gist.github.com/developit/63e7a81a507c368f7fc0898076f64d8d

from preact.

cportela avatar cportela commented on April 30, 2024

Thanks @developit, u rock!

from preact.

PierBover avatar PierBover commented on April 30, 2024

Hey

Is it possible to pass a ref to a prop in another component using Preact?

<One ref={c => this.one = c}/>
<Two one={this.one}/>

from preact.

idchlife avatar idchlife commented on April 30, 2024

@PierBover what do you want to accomplish exactly? Pass parent dom element to child component as a prop?

from preact.

PierBover avatar PierBover commented on April 30, 2024

Hey @idchlife

I'd like to pass the component instance to be able to call its methods.

from preact.

idchlife avatar idchlife commented on April 30, 2024

@PierBover may I ask, you new to react/preact? Did you consider using flux for between components interaction or create methods in parent component and accomplish your task like:

class ParentComp extends Component {
  emitter = new EventEmitter();

  constructor() // Some config code for emitter

  render() {
    return (<div>
                 <One addListenerOnChanges={(callback) => this.emitter.addListener(callback)} />
                 <Two onChangeCallback={() => this.emitter.emit(CHANGE_EVENT)} />
               </div>)
  }
}

So you will have Two component emit some event in parent EventEmitter and emitter then will pass event into One which will attach callback.

This is one solution. Actually I recommend flux for those purposes...

from preact.

developit avatar developit commented on April 30, 2024

I have done this by using a ref_ prop. ref isn't visible from a component's props, so you have to use some other name when passing it down.

from preact.

PierBover avatar PierBover commented on April 30, 2024

@idchlife the problem with that approach is that it needs a lot of boilerplate. The point of passing refs is the make the code as dynamic and generic as possible, instead of hand written boilerplate specific logic.

@developit can you post an example? How do you get a reference of the instance of the component in ref_?

from preact.

PierBover avatar PierBover commented on April 30, 2024

Thanks a lot @developit

So the component instance is passed on every prop that is a callback?

I thought this only happened with the ref prop.

I'm referring to <Middle ref_={ c => this.inner = c } />;

from preact.

PierBover avatar PierBover commented on April 30, 2024

Oh right!

Wow that's convoluted...

I've been reading more and it seems the best solution for me will be moving away from state/refs/props for this kinds of plumbings and use MobX instead with a mini RX implementation of Observable.

I despise Redux to be honest. It's solid but overly tedious for my use cases.

from preact.

JeromeDane avatar JeromeDane commented on April 30, 2024

Any update on this? It's been months since the last activity.

from preact.

JeromeDane avatar JeromeDane commented on April 30, 2024

Gotcha. Thanks. Sorry for my ignorance.

from preact.

developit avatar developit commented on April 30, 2024

no problem! just wasn't sure if I'd missed something 👍

from preact.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.