React - getting a component from a DOM element for debugging - javascript

For the purposes of debugging in the console, is there any mechanism available in React to use a DOM element instance to get the backing React component?
This question has been asked previously in the context of using it in production code. However, my focus is on development builds for the purpose of debugging.
I'm familiar with the Chrome debugging extension for React, however this isn't available in all browsers. Combining the DOM explorer and console it is easy to use the '$0' shortcut to access information about the highlighted DOM element.
I would like to write code something like this in the debugging console:
getComponentFromElement($0).props
Even in a the React development build is there no mechanism to use maybe the element's ReactId to get at the component?

Here's the helper I use: (updated to work for React <16 and 16+)
function FindReact(dom, traverseUp = 0) {
const key = Object.keys(dom).find(key=>{
return key.startsWith("__reactFiber$") // react 17+
|| key.startsWith("__reactInternalInstance$"); // react <17
});
const domFiber = dom[key];
if (domFiber == null) return null;
// react <16
if (domFiber._currentElement) {
let compFiber = domFiber._currentElement._owner;
for (let i = 0; i < traverseUp; i++) {
compFiber = compFiber._currentElement._owner;
}
return compFiber._instance;
}
// react 16+
const GetCompFiber = fiber=>{
//return fiber._debugOwner; // this also works, but is __DEV__ only
let parentFiber = fiber.return;
while (typeof parentFiber.type == "string") {
parentFiber = parentFiber.return;
}
return parentFiber;
};
let compFiber = GetCompFiber(domFiber);
for (let i = 0; i < traverseUp; i++) {
compFiber = GetCompFiber(compFiber);
}
return compFiber.stateNode;
}
Usage:
const someElement = document.getElementById("someElement");
const myComp = FindReact(someElement);
myComp.setState({test1: test2});
Note: This version is longer than the other answers, because it contains code to traverse-up from the component directly wrapping the dom-node. (without this code, the FindReact function would fail for some common cases, as seen below)
Bypassing in-between components
Let's say the component you want to find (MyComp) looks like this:
class MyComp extends Component {
render() {
return (
<InBetweenComp>
<div id="target">Element actually rendered to dom-tree.</div>
</InBetweenComp>
);
}
}
In this case, calling FindReact(target) will (by default) return the InBetweenComp instance instead, since it's the first component ancestor of the dom-element.
To resolve this, increase the traverseUp argument until you find the component you wanted:
const target = document.getElementById("target");
const myComp = FindReact(target, 1); // provide traverse-up distance here
For more details on traversing the React component tree, see here.
Function components
Function components don't have "instances" in the same way classes do, so you can't just modify the FindReact function to return an object with forceUpdate, setState, etc. on it for function components.
That said, you can at least obtain the React-fiber node for that path, containing its props, state, and such. To do so, modify the last line of the FindReact function to just: return compFiber;

Here you go. This supports React 16+
window.findReactComponent = function(el) {
for (const key in el) {
if (key.startsWith('__reactInternalInstance$')) {
const fiberNode = el[key];
return fiberNode && fiberNode.return && fiberNode.return.stateNode;
}
}
return null;
};

I've just read through the docs, and afaik none of the externally-exposed APIs will let you directly go in and find a React component by ID. However, you can update your initial React.render() call and keep the return value somewhere, e.g.:
window.searchRoot = React.render(React.createElement......
You can then reference searchRoot, and look through that directly, or traverse it using the React.addons.TestUtils. e.g. this will give you all the components:
var componentsArray = React.addons.TestUtils.findAllInRenderedTree(window.searchRoot, function() { return true; });
There are several built-in methods for filtering this tree, or you can write your own function to only return components based on some check you write.
More about TestUtils here: https://facebook.github.io/react/docs/test-utils.html

i wrote this small hack to enable access any react component from its dom node
var ReactDOM = require('react-dom');
(function () {
var _render = ReactDOM.render;
ReactDOM.render = function () {
return arguments[1].react = _render.apply(this, arguments);
};
})();
then you can access any component directly using:
document.getElementById("lol").react
or using JQuery
$("#lol").get(0).react

In case someone is struggling like me to access React component/properties from a chrome extension, all of the above solutions are not going to work from chrome extension content-script. Rather, you'll have to inject a script tag and run your code from there. Here is complete explanation:
https://stackoverflow.com/a/9517879/2037323

Here is a small snippet i'm currently using.
It works with React 0.14.7.
Gist with the code
let searchRoot = ReactDom.render(ROOT, document.getElementById('main'));
var getComponent = (comp) => comp._renderedComponent ? getComponent(comp._renderedComponent) : comp;
var getComponentById = (id)=> {
var comp = searchRoot._reactInternalInstance;
var path = id.substr(1).split('.').map(a=> '.' + a);
if (comp._rootNodeID !== path.shift()) throw 'Unknown root';
while (path.length > 0) {
comp = getComponent(comp)._renderedChildren[path.shift()];
}
return comp._instance;
};
window.$r = (node)=> getComponentById(node.getAttribute('data-reactid'))
to run it, open Devtools, highlight an element you want to examine, and in the console type : $r($0)

I've adapted #Venryx's answer with a slightly adapted ES6 version that fit my needs. This helper function returns the current element instead of the _owner._instance property.
getReactDomComponent(dom) {
const internalInstance = dom[Object.keys(dom).find(key =>
key.startsWith('__reactInternalInstance$'))];
if (!internalInstance) return null;
return internalInstance._currentElement;
}

React 16+ version:
If you want the nearest React component instance that the selected DOM element belongs to, here's how you can find it (modified from #Guan-Gui's solution):
window.getComponentFromElement = function(el) {
for (const key in el) {
if (key.startsWith('__reactInternalInstance$')) {
const fiberNode = el[key];
return fiberNode && fiberNode._debugOwner && fiberNode._debugOwner.stateNode;
}
}
return null;
};
They trick here is to use the _debugOwner property, which is a reference to the FiberNode of the nearest component that the DOM element is part of.
Caveat: Only running in dev mode will the components have the _debugOwner property. This would not work in production mode.
Bonus
I created this handy snippet that you can run in your console so that you can click on any element and get the React component instance it belongs to.
document.addEventListener('click', function(event) {
const el = event.target;
for (const key in el) {
if (key.startsWith('__reactInternalInstance$')) {
const fiberNode = el[key];
const component = fiberNode && fiberNode._debugOwner;
if (component) {
console.log(component.type.displayName || component.type.name);
window.$r = component.stateNode;
}
return;
}
}
});

Install React devtools and use following, to access react element of corresponding dom node ($0).
for 0.14.8
var findReactNode = (node) =>Object.values(__REACT_DEVTOOLS_GLOBAL_HOOK__.helpers)[0]
.getReactElementFromNative(node)
._currentElement;
findReactNode($0);
Ofcourse, its a hack only..

Related

Shadow DOM in web components with Vue3

I'm currently testing web components with Vue3 and wondering how this Shadow DOM really works. Some third party library is accessing elements with getElementById() and throwing an error because the element is null.
Apparently that's because there's no access from the web component to the actual DOM. So meaning the functions can't even find the HTML elements used in the components itself. Can anyone explain why that is exactly? And how would I access the elements then? Maybe with shadowRoot?
Test.vue:
<template>
<div id="test">Hello World!</div>
</template>
<script lang="js">
import {
ref,
onMounted
} from "vue";
export default {
setup(props) {
onMounted(() => {
// NULL
console.log(document.getElementById("test"));
});
}
}
</script>
main.js:
import { defineCustomElement } from 'vue'
import Test from './Test.vue'
const ExampleElement = defineCustomElement(Test)
// register
window.customElements.define('test-component', ExampleElement)
Yes, shadowDOM is meant to encapsulate content.
If you do not want that behaviour, then do not use shadowDOM
But if you are using a tool, it might enforce shadowDOM on you,
In that case, ditch the tool and create a Component with Vanilla JavaScript, it ain't rocket science.
If you are learning Web Components it is best to learn the Technology first, and not a Tool... because a Fool with a Tool, is still a Fool.
If the Custom Element (with shadowDOM) exists in the DOM, and it is registered with mode:"open", you can query its contents with:
document.querySelector("test-component").shadowRoot.querySelector("#test")
If you want to find all Web Components in the page, you can do something like this:
// findElements takes a function definition, the output must be Truthy or Falsy
function findElements( accept = x => customElements.get(x.localName) || 0) {
function log() {
console.log(`%c findElements `, `background:purple;color:yellow`, ...arguments);
}
let node, elements = [], shadowRootCount = 0;
function diveNode( diveRoot ) {
// IE9 was last to implement the TreeWalker/NodeIterator API ... in 2011
let iterator = document.createNodeIterator(
diveRoot,
NodeFilter.SHOW_ELEMENT,
node => accept(node) ? NodeFilter.FILTER_ACCEPT : NodeFilter.FILTER_REJECT
);
while ( node = iterator.nextNode() ) {
if (node.shadowRoot) {
log(`dive into shadowRoot #${++shadowRootCount} at`, node.outerHTML);
[...node.shadowRoot.children].forEach( diveNode );
}
elements.push(node);
}
}
diveNode( document.body ); // initial dive location
log(elements.length, `elements found`,[elements]);
//return elements;
}
findElements((x) => true); // find all DOM elements
findElements(); // find all Custom Elements

'this' is undefined in react for adding checkbox events

I encountered error in react wherein this is undefined. This is my first time developing a react application.
In UI, it says Unhandled Rejection (TypeError): Cannot read property 'setState' of undefined while in console the value of this is undefined.
Thank you for your help.
Here is the existing code:
import React, { useState, useEffect, useRef } from "react";
//import makeData from "../makeData";
import { useTableState } from "react-table";
import { Button } from "../Styles";
import Table from "../TransactionPanelTable";
// Simulate a server
const getServerData = async ({ filters, sortBy, pageSize, pageIndex }) => {
await new Promise(resolve => setTimeout(resolve, 500));
// Ideally, you would pass this info to the server, but we'll do it here for convenience
const filtersArr = Object.entries(filters);
// Get our base data
let rows = [];
for (let i = 0; i < 1000; i++) {
rows.push({
transaction_seq: 1234,
rec_count: 1234,
user_id: "test",
updated_at: "",
duration: 1.23
});
}
// Apply Filters
if (filtersArr.length) {
rows = rows.filter(row =>
filtersArr.every(([key, value]) => row[key].includes(value))
);
}
// Apply Sorting
if (sortBy.length) {
const [{ id, desc }] = sortBy;
rows = [...rows].sort(
(a, b) => (a[id] > b[id] ? 1 : a[id] === b[id] ? 0 : -1) * (desc ? -1 : 1)
);
}
// Get page counts
const pageCount = Math.ceil(rows.length / pageSize);
const rowStart = pageSize * pageIndex;
const rowEnd = rowStart + pageSize;
// Get the current page
rows = rows.slice(rowStart, rowEnd);
let checkedMap = new Map();
rows.forEach(row => checkedMap.set(row, false)); //preset each to false, ideally w/ a key instead of the entire row
this.setState({ checkedMap: checkedMap });
//handleCheckedChange(row) {
// let modifiedMap = this.state.checkedMap;
// modifiedMap.set(row, !this.state.checkedMap.get(row));
// this.setState({ checkedMap: modifiedMap });
//}
return {
rows,
pageCount
};
};
'this' is undefined because you haven't bounded the context of the component to the method.
As you are new to react, i would suggest you to go through concepts like bind,es6 and other js lingos so that you can code better and avoid such errors.
In this case you need to bind the context either by using bind method or by using es6 arrow functions which are class functions.
Add below code in constructor function where you are calling this getServerData function.
getServerData = getServerData.bind(this);
Here bind will return new method which will set context (this) to your calling class.
It is preferable to set state in class with in that promise resolved function.
First, let's answer your question. this is undefined because you are using ES6 modules and you are using an arrow function const getServerData = async (...) => {.
Inside arrow functions, the this binding is not dynamic, but is instead lexical. That means that this actually refers to the originating context (outside your arrow function).
In a basic js script, that would point to the Window global context. But you are using ES6 module, with import statements. One of the goals of ES6 modules is to isolate code. So inside such a module, there is no default access to a global Window context, so your this end up being undefined.
Now, several misconceptions are recognizable in your code.
writing an ES6 modules, you should have an export statement somewhere inside in order to consume it outside.
The request to a server should preferably be isolated in another function, so that the structure of your component will be more easy to apprehend, especially if you are learning React.
Read the very good React doc to understand the basics of it.
You should not use async when defining a React functional component
Since you have no state defined in your component (importing useState is not enough, you must use it: State Hooks doc), you cannot use setState.
this.setState refer to using React class syntax to define your component, useState refer to using State Hooks. You cannot do both in a same component. You have to choose.
So to sum-up, your getServerData function should be outside of your Component and should not have to do anything with state management. And you should build a React component that will call your function in a lifecycle method.
Stay confident, you will get it sooner than you think! ;-)
this.getServerData = this.getServerData.bind(this);

Objects duplicating every component mount. How can I make it run only once ? In react

My object is a independent js file that I created.
componentDidMount() {
const node = ReactDOM.findDOMNode(this);
const widgetBuild = new window.WidgetFormBuilder({
form: $(node).parents('#dynamic_form_wrapper')
});
widgetBuild.initForm();
}
It is a little hard to workout without knowing more about WidgetFormBuilder.
However as good practice I would suggest...
componentDidMount() {
const node = ReactDOM.findDOMNode(this);
// Assign to the class instance
this.widgetBuild = new window.WidgetFormBuilder({
form: $(node).parents('#dynamic_form_wrapper')
});
this.widgetBuild.initForm();
}
componentWillUnmount() {
// Cleanup
// Check if WidgetFormBuilder has a destroy method or something similar.
// See https://reactjs.org/docs/react-component.html#componentwillunmount
this.widgetBuild = null;
}
shouldComponentUpdate() {
// Stop further re-renders, given you're using the DOM directly this could help prevent a few performance issues
// See https://reactjs.org/docs/react-component.html#shouldcomponentupdate
return false;
}
Finally, take a look at the react docs on third party libs.
I already fixed i just added a .destroy() function in my WidgetFormBuilder. :)
WidgetFormBuilder.prototype.destroyBuilder = function () {
const self = this;
const destroyEvents = function () {
$(self.form).unbind();
};
destroyEvents();
return this;
};

Setting custom props on a dynamically created React component

I'm refactoring some of my React code for ease of use in places where I can't use Babel directly (such as in short embedded JavaScript on pages). To assist with this I'm setting up a short function that builds the components and passes props to them. This code works just fine:
components.js:
import ResponsiveMenu from './components/responsive-menu';
window.setupMenu = (items, ele) => {
ReactDOM.render(<ResponsiveMenu items={items}/>, ele);
};
static-js.html:
<div id="menu"></div>
<script>
setupMenu({ items: [] }, document.getElementById('menu');
</script>
However, when I attempt to turn it into something more generic to handle more components like so:
components.js:
import ResponsiveMenu from './components/responsive-menu';
import AnotherComp from './components/another-comp';
window.setupComponent = (selector, name, props) => {
let eles;
if (typeof selector == 'string') {
eles = [];
let nl = document.querySelectorAll(selector), node;
for (let i = 0; node = nl[i]; i++) { eles.push(node); }
} else {
eles = $.toArray(selector); // A helper function that converts any value to an array.
}
return eles.map (
(ele) => {
let passProps = typeof props == 'function' ? props(ele) : props;
return ReactDOM.render(React.createElement(name, passProps), ele);
}
);
};
static-js.html:
<div id="menu"></div>
<script>
setupComponent('#menu', 'ResponsiveMenu', { items: [] });
</script>
I then get this error: Warning: Unknown prop "items" on <ResponsiveMenu> tag. Remove this prop from the element. For details, see (really unhelpful shortened link that SO doesn't want me posting)
Please help me understand why this works for the JSX version and not for the more manual version of creating the component.
When you pass string parameter to React.createElement, it will create native DOM element and there is no valid html DOM ResponsiveMenu.
You can store element into hash and store it into window variable.
Example:
// store component into window variable
window.components = {
ResponsiveMenu: ResponsiveMenu
}
//extract component from window variable by name
React.createElement(window.components[name], passProps)

What's an alternative for the deprecated setProps() in React.js?

I just upgraded my project's React version to 13.3 and setProps() is no longer working. I'm using it in this Mocha test and I'm not sure how to rewrite it now. What are my options?
it('reloads search results when props change ', function() {
var loadResultsSpy = sinon.spy(searchView, 'loadSearchResults');
var newProps = {searchBy: 'foo', searchTerm: 'bar'};
searchView.setProps(newProps);
expect(loadResultsSpy.calledWith(newProps.searchBy, newProps.searchTerm)).toBe(true);
});
In the most general case, React recommends just re-rending the top-level component, which essentially just updates the props if the new render is of the same component type. Since TestUtils.renderIntoDocument is simply a shortcut for creating a DOM node and doing a regular render into it, something like this will work (I'm making some assumptions about the setup of your tests):
var searchView, div;
beforeEach(function() {
var comp = <SearchView searchBy="baz" searchTerm="quix" />;
div = document.createElement('div');
searchView = React.render(comp, div);
});
it('reloads search results when props change', function() {
var loadResultsSpy = sinon.spy(searchView, 'loadSearchResults');
var comp = <SearchView searchBy="foo" searchTerm="bar" />;
React.render(comp, div);
expect(loadResultsSpy.calledWith("foo", "bar")).toBe(true);
});

Categories

Resources