When using HyperHTMLElement it's possible to access the contents of the component by simply using this.children or this.querySelector(), since it's an element.
But how would I achieve similar behavior when using hyper.Component?
The hypothetical example I have in mind is from React docs: https://facebook.github.io/react/docs/refs-and-the-dom.html - I'd like to focus a specific node inside my DOM.
I have a codepen sandbox where I'm trying to solve this: https://codepen.io/asapach/pen/oGvdBd?editors=0010
The idea is that render() returns the same Node every time, so I could save it before returning and access it later as this.node:
render() {
this.node = this.html`
<div>
<input type="text" />
<input type="button" value="Focus the text input" onclick=${this} />
</div>
`;
return this.node;
}
But that doesn't look clean to me. Is there a better way to do this?
The handleEvent pattern is there to help you. The idea behind that pattern is that you never need to retain DOM references when the behavior is event-driven, 'cause you can always retrieve nodes via event.currentTarget, always pointing at the element that had the listener attached, or event.target, suitable for clicks happened in other places too within a generic click handler attached to the wrap element, in your demo case the div one.
If you'd like to use these information, you can enrich your components using an attribute to recognize them, like a data-is="custom-text-input" on the root element could be, and reach it to do any other thing you need.
onclick(e) {
var node = e.target.closest('[data-is=custom-text-input]');
node.querySelector('[type=text]').focus();
}
You can see a working example in a fork of your code pen:
https://codepen.io/WebReflection/pen/RLNyjy?editors=0010
As alternative, you could render your component and address its content once as shown in this other fork:
https://codepen.io/WebReflection/pen/LzEmgO?editors=0010
constructor() {
super().node = this.render();
}
at the end of the day, if you are not using custom elements but just basic, good'ol DOM nodes, you can initialize / render them whenever you want, you don't need to wait for any upgrade mechanism.
What is both nice and hopefully secure here, is that there's no way, unless you explicitly expose it, to address/change/mutate the instance related to the DOM element.
I hope these possibilities answered your question.
This is something I've worked on in the past via https://github.com/joshgillies/hypercomponent
The implementation is actually quite trivial.
class ElementalComponent extends hyper.Component {
constructor () {
super()
const _html = super.html
this.html = (...args) => {
this.node = _html.apply(this, args)
return this.node
}
}
}
class HelloWorld extends ElementalComponent {
render () {
return this.html`<div>Hello World!</div>`
}
}
This works really well and is inline with your question. However, it's worth noting hyperHTML can render not only a single node but also multiple nodes. As an example:
hyper`<div>Hello World!</div>` // returns a single DOM Node
hyper`<div>Hello</div> <div>World!</div>` // returns multiple DOM Nodes as an Array.
So this.node in the above ElementalComponent can be either a DOM Node, or Array based on what the renderer is doing.
Related
I'm working at a project in which I have to display graphs.
For displaying graphs I'm using vis.js in particular react-vis-network a implementation for using parts of vis.js in React with its stateful approaches.
Initial nodes and edges are loaded before my component is mounted and are passed as props for an initial state.
I attached two eventHandler one direct to a vis.js (the underlying DOM library) and the other at a decorator (button).
The desired/expected behaviour:
A node is removed by clicking either the node or the corresponding button.
Observed behavior:
Sometimes a node is removed and sometimes a node just disappears for a few ms and is reattached but without a decorator/button.
I already tried to start with an empty state and attaching the nodes,edges in componentDidMount() but I got the same result. I hope you can give me a hint.
BTW: Is the way I use to attach components a/the right way?
Every other help to improve my class is appreciated also
class MyNetwork extends Component {
constructor(props){
super(props);
let componentNodes = [];
for (let node of props.nodes){
componentNodes.push(this.createNode(node));
}
let componentEdges = [];
for (let edge of props.edges){
componentEdges.push(this.createEdge(edge));
}
this.state = {nodes:componentNodes,edges:componentEdges};
["_handleButtonClick"].forEach(name => {
this[name] = this[name].bind(this);
});
}
createNode(node){
const Decorator = props => {
return (
<button
onClick={() =>{this._handleButtonClick(props);}}
>
Click Me
</button>
);
};
node.decorator = Decorator;
return React.createElement(Node,{...node})
}
createEdge(edge){
return React.createElement(Edge,{...edge})
}
addNode(node){
this.setState({
nodes: [...this.state.nodes, this.createNode(node)]
})
}
_handleButtonClick(e) {
if(e){
console.log("clicked node has id:" +e.id);
this.removeNode(e.id);
}
}
onSelectNode(params){
console.log(params);
window.myApp.removeNode(params[0]);
}
removeNode(id) {
let array = [...this.state.nodes]; // make a separate copy of the array
let index = array.findIndex(i => i.props.id === id );
array.splice(index, 1);
this.setState({nodes: array});
}
render() {
return (
<div id='network'>
<Network options={this.props.options} onSelectNode={this.onSelectNode}>
{[this.state.nodes]}
{[this.state.edges]}
</Network>
</div>
);
}
}
export default MyNetwork
Before clicking node 2
After clicking node 2
Update 1
I created a live example at stackblitz which isn't working yet caused by other failures I make and can't find.
The components I use are:
Network
Node
Edge
Edge and Node are extending Module
I reworked my MyNetwork component according to some mistakes xadm mentioned.
Components (espacially dynamic) shouldn't be stored in state.
I implemented two new functions nodes() and edges() // line 15-41*
key prop should be used, too.
key is used now // line 18 + 32*
Passed props cannot be modified, you still have to copy initial data
into state. State is required for updates/rerendering.
line 9*
*line numbers in live example I mentioned above
Update 2
I reworked my code and now the life sample is working.
My hope is that I could use the native vis.js events and use them in MyNetwork or other Components I will write.
I read about using 3rd Party DOM event in this question can't figure out to adapt it for my particular case. Because I don't know how to attach the event handler to . Is this possible to do so I can use the event in other components?
Or should I open another question for this topic?
I see several possibilities of problems here.
<Decorator/> should be defined outside of <MyNetwork /> class. Click handler should be passed as prop.
Components (espacially dynamic) shouldn't be stored in state. Just render them in render or by rendering method (called from render). Use <Node/> components with decorator prop, key prop should be used, too.
Passed props cannot be modified, you still have to copy initial data into state. State is required for updates/rerendering. You probably need to remove edge(-es) while removing node.
Create a working example (on stackblitz?) if a problem won't be resolved.
It sounds like React is re-initializing your component when you are clicking a button. Maybe someone smarter than I am can figure out why that is happening...
But since no one has commented on this yet, one way I have handled these sorts of issues is to take the state management out of the display component. You say you are passing the nodes and edges via props from a parent component. You might consider moving the addNode, removeNode, createEdge, and other methods up to the parent component so that it is maintaining the state of the node/edge structure and your display component <MyNetwork/> is only displaying what it receives as props.
Perhaps this isn't an option in your app, but I generally use Redux to remove the state management from the components all together. I find it reduces situations like this where "who should own the state" isn't always clear.
I have the following code in my component:
renderName: function(name) {
return (
{name}
)
}
then in render method I want to inspect what will be HTML DOM representation of running this method:
render: function() {
let namePart = this.renderName('Berlin');
...
}
How can I see code
<div>
Berlin
</div>
Maybe there is some method for compiling this object that I can see during inspection in DevTools into HTML?
If you want to reference this in React, one way is to use
ReactDOM.findDOMNode(this)
Here are the docs on it.
https://facebook.github.io/react/docs/react-dom.html#finddomnode
ReactDOM.findDOMNode(this)
If this component has been mounted into the DOM, this returns the corresponding native browser DOM element. This method is useful for reading values out of the DOM, such as form field values and performing DOM measurements. In most cases, you can attach a ref to the DOM node and avoid using findDOMNode at all.
It suggests using refs if you need to avoid using findDOMNode.
I don't fully understand it but apparently it isn't recommended to use findDOMNode().
I'm trying to create drag and drop component but I'm not sure how I should access refs from the component variable. This is an example of what I currently have:
const cardTarget = {
hover(props, monitor, component) {
...
// Determine rectangle on screen
const hoverBoundingRect = findDOMNode(component).getBoundingClientRect();
...
}
}
Source
Edit
It might be caused by my component being both the drag and drop source and target as I can get it to work in this example but not this one.
Assuming you're using es6 class syntax and the most recent version of React (15, at time of writing), you can attach a callback ref like Dan did in his example on the link you shared. From the docs:
When the ref attribute is used on an HTML element, the ref callback receives the underlying DOM element as its argument. For example, this code uses the ref callback to store a reference to a DOM node:
<h3
className="widget"
onMouseOver={ this.handleHover.bind( this ) }
ref={node => this.node = node}
>
Then you can access the node just like we used to do with our old friends findDOMNode() or getDOMNode():
handleHover() {
const rect = this.node.getBoundingClientRect(); // Your DOM node
this.setState({ rect });
}
In action:
https://jsfiddle.net/ftub8ro6/
Edit:
Because React DND does a bit of magic behind the scenes, we have to use their API to get at the decorated component. They provide getDecoratedComponentInstance() so you can get at the underlying component. Once you use that, you can get the component.node as expected:
hover(props, monitor, component) {
const dragIndex = monitor.getItem().index;
const hoverIndex = props.index;
const rawComponent = component.getDecoratedComponentInstance();
console.log( rawComponent.node.getBoundingClientRect() );
...
Here it is in action:
https://jsfiddle.net/h4w4btz9/2/
Better Solution
A better solution is to just wrap your draggable component with a div, define a ref on that and pass it to the draggable component, i.e.
<div key={key} ref={node => { this.node = node; }}>
<MyComponent
node={this.node}
/>
</div>
and MyComponent is wrapped in DragSource. Now you can just use
hover(props, monitor, component) {
...
props.node && props.node.getBoundingClientRect();
...
}
(props.node && is just added to avoid to call getBoundingClientRect on an undefined object)
Alternative for findDOMNode
If you don't want to add a wrapping div, you could do the following.
The reply of #imjared and the suggested solution here don't work (at least in react-dnd#2.3.0 and react#15.3.1).
The only working alternative for findDOMNode(component).getBoundingClientRect(); which does not use findDOMNode is:
hover(props, monitor, component) {
...
component.decoratedComponentInstance._reactInternalInstance._renderedComponent._hostNode.getBoundingClientRect();
...
}
which is not very beautiful and dangerous because react could change this internal path in future versions!
Other (weaker) Alternative
Use monitor.getDifferenceFromInitialOffset(); which will not give you precise values, but is perhaps good enough in case you have a small dragSource. Then the returned value is pretty predictable with a small error margin depending on the size of your dragSource.
React-DnD's API is super flexible—we can (ab)use this.
For example, React-DnD lets us determine what connectors are passed to the underlying component. Which means we can wrap them, too. :)
For example, let's override the target connector to store the node on the monitor. We will use a Symbol so we do not leak this little hack to the outside world.
const NODE = Symbol('Node')
function targetCollector(connect, monitor) {
const connectDropTarget = connect.dropTarget()
return {
// Consumer does not have to know what we're doing ;)
connectDropTarget: node => {
monitor[NODE] = node
connectDropTarget(node)
}
}
}
Now in your hover method, you can use
const node = monitor[NODE]
const hoverBoundingRect = node.getBoundingClientRect()
This approach piggybacks on React-DnD's flow and shields the outside world by using a Symbol.
Whether you're using this approach or the class-based this.node = node ref approach, you're relying on the underlying React node. I prefer this one because the consumer does not have to remember to manually use a ref other than the ones already required by React-DnD, and the consumer does not have to be a class component either.
Suppose I have a custom element in which I iterate over an array of data and render custom subelements.
<my-list action-handler.bind="actionHandler">
<my-element item.bind="element" repeat.for="element of data"></my-element>
</my-list>
In my-list.html I render the contents with <contents></contents> and in general the real code is a bit more complex and has replaceable template parts, but that's not the problem.
I want the actionHandler that I bind to MyList to be available in all of the children. For the sake of elegancy, we did this in my-list.js:
#children('*:not(div)') listItems;
//...
attached(){
this.listItems.forEach((e) => {
e.actionHandler = this.actionHandler;
});
}
And everything worked just fine until we started to load data dynamically. In this case listItems is empty since the element is initialized before the child elements re-rendered.
So, the question is:
How would I make #children be re-calculated?
I understand that we could bind the actionHandler to all children when we were repeating over them, but this would start to look really ugly when we add some more logic like
Thanks!
If <my-element> has a dependency on it's parent <my-list> element's actionHandler, you can express that declaratively and let Aurelia's dependency injection system do the rest. Here's a couple of ways to do that...
If <my-element> will always be within a <my-list> (doesn't necessarily need to be directly within):
import {inject} from 'aurelia-dependency-injection';
#inject(MyList)
export class MyElement {
constructor(list) {
this.actionHandler = list.actionHandler;
}
}
If you need access <my-element>'s closest parent custom element, regardless of what it is:
import {inject, Parent} from 'aurelia-dependency-injection';
#inject(Parent.of(Element))
export class MyElement {
constructor(parent) {
this.actionHandler = parent.actionHandler;
}
}
I know this wasn't exactly what you were asking but I'm proposing this in case it's a simpler way to meet your requirements.
The Aurelia Cheat Sheet is telling that:
#children(selector) - Decorates a property to create an array on your class that has its items automatically synchronized based on a query selector against the element's immediate child content.
so i guess it is not possible to use #children with dynamic data.
You might should go with the Parent approach, like the previous answer is suggesting.
My friend and I are having an argument. In the interest of full disclosure, I'm the one who's a big fan of React and its benefits.
In React components, when attaching a DOM event to each element in a list of elements, the traditional pattern is to bind() the generic click handler with the values you want to pass along to that function as parameters. As written below:
<button onClick={this.onButtonClick.bind(this, buttonIndex)}></button>
where buttonIndex is some value that changes as the list of buttons is iterated over. This pattern allows onButtonClick to be generic, and expect buttonIndex as a parameter. Like so:
onButtonClick: function(buttonIndex) {
... some logic
}
My friend argues that this way of doing things is extremely inefficient. This requires that a new function be created and kept in memory to handle each button's event. I agree with his point, but I feel that the React devs wouldn't encourage this pattern in their docs, (at least twice) if the library didn't efficiently handle the events and their handlers.
The pattern he used to avoid this was to use the data- attribute and get the value (in this example, buttonIndex) off the DOM element itself:
<button data-buttonIndex={buttonIndex} onClick={this.onButtonClick}></button>
...
onButtonClick: function() {
var buttonIndex = $(this).data('buttonIndex');
...some logic
}
Again, I'm biased cus I'm the React fan. But this feels wrong, for two reasons:
Getting values off the DOM to pass data around (as state) kinda defeats the purpose of React in a lot of ways, right?
data- attributes are extremely ambiguous in my opinion. They can be set from several different places (HTML, JS, PHP, etc.). And they don't suggest any implicit purpose. That "data" could be used anywhere, (JS, CSS, etc.)
Does React do some special magic to be efficent with its DOM events? And if not, is there an alternative pattern that doesn't use the data- attribute and is more explicit about its use?
I think in general binding functions directly in render is the idiomatic way because they do it in the docs as you pointed out and in our experience has not been significantly less performant. However, there are cases you don't want to rebind the function on every render, for example if you're comparing props in shouldComponentUpdate (like with PureRenderMixin). To do something very similar as your friend suggests but without looking at the DOM with jQuery (and I believe is a common pattern) you can pass the index as a prop.
class Parent extends React.Component {
render() {
return [...some array].map((item, index) => {
return <Child item={item} index={index} />;
});
}
}
class Child extends React.Component {
constructor() {
super();
this.handleClickButton = this.handleClickButton.bind(this);
}
handleClickButton() {
// use this.props.index here
}
render() {
return <button onClick={this.handleClickButton}></button>;
}
}
Note when using ES6 classes you need to bind to this manually in constructor since you're accessing this.props. You don't need to if you're using React.createClass. More about ES6 classes in React docs here.
I'm not sure this is a good idea, but... memoize!
class Foo {
constructor(){
this.getClickHandler = _.memoize(this.getClickHandler);
}
getClickHandler(index){
return (event) => {
doSomething();
};
}
render(){
// ...
<button onClick={this.getClickHandler(index)}>Click me</button>
// ...
}
}
This way you avoid creating a new function, avoid data-attributes, and avoid the performance cost of looking up anything in the dom.
I don't think I've ever profiled and found creating functions in render to be an issue. This is definitely something you should optimize only when the numbers tell you to do so.