Comments (49)
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.
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.
@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.
@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.
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.
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.
@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.
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.
Nice \o/ :sparkles: :shipit:
from preact.
This also fixes developit/preact-compat-example#2 💥
from preact.
@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.
@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.
@developit great, thanks, no hurry
from preact.
Yay for refs! Don't see another way of getting values of form fields when dynamically constructing it except via refs.
from preact.
@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.
@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.
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.
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.
@nightwolfz does that work in React?
from preact.
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.
@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.
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.
@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.
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.
@developit do you think this will make it into a 3.x release, or 4.0?
from preact.
@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.
Woohoo!
from preact.
@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.
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.
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.
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.
😍
from preact.
Thank you! @developit
from preact.
👍
from preact.
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.
Perfect @developit, thanks for the instant response!
from preact.
@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.
Thanks @developit, u rock!
from preact.
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.
@PierBover what do you want to accomplish exactly? Pass parent dom element to child component as a prop?
from preact.
Hey @idchlife
I'd like to pass the component instance to be able to call its methods.
from preact.
@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.
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.
@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.
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.
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.
Any update on this? It's been months since the last activity.
from preact.
Gotcha. Thanks. Sorry for my ignorance.
from preact.
no problem! just wasn't sure if I'd missed something 👍
from preact.
Related Issues (20)
- "Components keep being added when updating state" issue persists when using tags other than div HOT 1
- preact/compat's SetStateAction is different from React's SetStateAction HOT 4
- useSelector hook stops being called under certain conditions since preact 10.19.4 HOT 3
- Implement onscrollend event attribute for TypeScript JSX
- TypeScript JSX focus events don't have consistent case HOT 5
- Typescript issues in 10.19.4+ with @mui/material HOT 5
- Provide a boundary between Components and DOM HOT 8
- `preact` `10.9.4+` requires double click or move pointer out of `@headlessui/react` `Tab` panel/button HOT 2
- Unmount hooks should be called during the commit phase to ensure consistency with React HOT 2
- When I attempt to extend HTMLAttributes every intrinsic element becomes of type any HOT 2
- Current plan for event types? HOT 3
- onFocusIn and onFocusOut events incorrectly set HOT 4
- Cannot read properties of undefined (reading '__m')
- `hydrate` doesn't replace attributes at root replaced node HOT 4
- Unable to use hooks in compiled package
- Compatibility issue: Jest mock works in React / fails in Preact when using ForwardRef in a component HOT 2
- Using useState leads to error: Uncaught (in promise) TypeError: Cannot read properties of undefined (reading '__H') HOT 3
- Render mui cache inside shadow dom leaves style sheets in head and content unstyled HOT 2
- `defaultValue` incorrectly (?) diffed against prerendered HTML
- Preact 10.20 + @mui/x-data-grid v7 causes datagrid to appear multiple times in dev-mode, but not in preview-mode HOT 1
Recommend Projects
-
React
A declarative, efficient, and flexible JavaScript library for building user interfaces.
-
Vue.js
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
-
Typescript
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
-
TensorFlow
An Open Source Machine Learning Framework for Everyone
-
Django
The Web framework for perfectionists with deadlines.
-
Laravel
A PHP framework for web artisans
-
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.
-
Visualization
Some thing interesting about visualization, use data art
-
Game
Some thing interesting about game, make everyone happy.
Recommend Org
-
Facebook
We are working to build community through open source technology. NB: members must have two-factor auth.
-
Microsoft
Open source projects and samples from Microsoft.
-
Google
Google ❤️ Open Source for everyone.
-
Alibaba
Alibaba Open Source for everyone
-
D3
Data-Driven Documents codes.
-
Tencent
China tencent open source team.
from preact.