React has a ref property for all components, that refers to the DOM element. Any plans to add this one on Preact?
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.
Nice o/ :sparkles: :shipit:
This also fixes https://github.com/developit/preact-compat-example/issues/2 :boom:
@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.
@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.
@developit great, thanks, no hurry
Yay for refs! Don't see another way of getting values of form fields when dynamically constructing it except via refs.
@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.
@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!
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.
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>
}
}
@nightwolfz does that work in React?
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...
@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).
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!
@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.
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!
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);
@developit do you think this will make it into a 3.x release, or 4.0?
@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.
Woohoo!
@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 }} />
);
}
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.
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.
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
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 :rainbow:
:heart_eyes:
Thank you! @developit
馃憤
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!
@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>
);
}
}
Perfect @developit, thanks for the instant response!
@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
Thanks @developit, u rock!
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}/>
@PierBover what do you want to accomplish exactly? Pass parent dom element to child component as a prop?
Hey @idchlife
I'd like to pass the component instance to be able to call its methods.
@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...
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.
@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_?
@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.
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 } />;
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.
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.
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).
Any update on this? It's been months since the last activity.
@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.
Gotcha. Thanks. Sorry for my ignorance.
no problem! just wasn't sure if I'd missed something 馃憤
Most helpful comment
100% voted for function refs only, so that seals the deal.
4.0.1now (only) supports function refs. There are also now some decent tests :rainbow: