react native create element with string - javascript

I see a lot people creating a route mapping in React native similar to the below:
if (route.id === 'Blah') {
return (<Blah prop1={this.method} prop2={this.other method} />);
} else if (route.id === 'OtherView') {
return (<OtherView prop1={this.method} />);
}
this can quickly become many lines of code, I'd like to do something like this:
return (React.createElement(route.id, {propsToPass}));
This doesn't work in React Native as apparently 'strings are not allowed as the first parameter in React Native since those are meant to be used for html tags in regular React.'
So how can this be done? I got it working if I supply a ReactClass as the first param, or with eval(route.id) (but I know that can be dangerous).
How can I create a React Native element with a string?

You could setup an allowed components namespace:
var routeComponents = {
"Blah": Blah,
"OtherView": OtherView
}
if(routeComponents[route.id]) {
return React.createElement(routeComponents[route.id], {propsToPass});
} else {
// Error
}

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

Check if property exists using React.js

I'm new to using react.js, and am trying to write a re-usable component that has an optional property passed to it. In the component, that optional property pulls data from a db using meteor, then I want to check if a property exists on the returned object (parent_task exists on task), and if exists, adds a link. This seems fairly simple, but I keep getting errors. Does anyone have any suggestions on what I might be missing? Is there a jsx gotcha that I'm missing?
<Header task={params.task_id} /> // rendering component with property
// Task List Header
Header = React.createClass({
mixins: [ReactMeteorData],
getMeteorData() {
var handle = Meteor.subscribe('tasks');
return {
taskLoading: ! handle.ready(),
task: Tasks.findOne({_id: this.props.task})
}
},
getParentTaskLink() {
if (!this.data.taskLoading) {
var current_task = this.data.task;
if (parent_task in current_task) { // or current_task.hasOwnProperty(parent_task)
console.log("parent_task exists!");
}
}
},
render() {
return (
<div className="bar bar-header bar-calm">
{this.getParentTaskLink()} // eventually return anchor element here
<h1 className="title">Hello World</h1>
</div>
)
}
});
what is the prop in question? how about
{this.props.propInQuestion ? link : null}
I figured this out. Apparently it was a syntax issue - you need to use a string when searching for properties in objects. The line below works:
if ('parent_task' in current_task)
For me works:
if ('myProperty' in this.props) {}
or
if (this.props.myProperty !== undefined) {}
or
if (this.props.hasOwnProperty('myProperty')) {}
Next condition will not work for number property, as 0 value will not work (such as for empty string):
if (this.props.MaxValue) {}
Check if a property exists using React.js
There are two options you can use. the && operator and If statement to check if the props exist.
Option 1 will check if the property exists then run the second part of the code. It works like an if without the if.
Option 1
this.props.property && this.props.property
Option 2
if(this.props.property){
this.props.property
}
This also works with function names.
You can use this also check to render components and tags.
This works for me
if(this.props.test === undefined){
console.log('props.test is not defined')
}
I suggest to try this elegant solution to check callback property on your component:
if(typeof this.props.onClickCallback === 'function') {
// Do stuff;
}
or applying destructuring:
const { onClickCallback } = this.props;
if(typeof onClickCallback === 'function') {
// Do stuff;
}
The most upvoted answer
props.propInQuestion ? 'a' : 'b'
Doesn't work if the prop is a boolean and you're trying to check for existence.
Based on How do I check if an object has a key in JavaScript? the fastest way is props.hasOwnProperty('propInQuestion'), with the caveat that this will not search the prototype chain.
In functional components, you can use like this.
if(props.myProperty){
//do something
}else{
//do something
}
if(props.hasOwnProperty('propertyName')){
//do something
} else {
//do something else
}
You need to return out of getParentTaskLink() with the link you need.
if (current_task.parent_task) {
return (link);
} else { return null; }

JSX syntax error on PHPStorm

I'm trying to conditionally set a property on a collection of elements.
render: {
var buttons = [];
for (var i = 1; i <= this.props.totalWeeks; i++) {
buttons.push(
<button
onClick={ this.changeWeek.bind(this, i) }
disabled={ i === this.state.currWeek }>{ i }
</button>);
}
}
Everything works great in the browser. But PHPStorm (version 8.0.3) marks the expression { i === this.state.currWeek } as an error for wrong attribute value.
I've tried changing that with a function call, a variable, etc., but can't seem to make error go away. I've also tried to turn off that inspection rule on PHPStorm, but can't find the one setting that would turn that off.
QUESTION
How can I make that error go away in PHPStorm? If that's a bug, then how can I get rid of that by conditionally adding HTML attributes to a group of elements some other way?
1) If it's render of react (not your custom function), it should be "render() { return ; }" against your code
It's 100% syntax error, browser ignores it, because it should, if you use it in class definition body, syntax is next:
class Test {
objectExample: { some: "value" }
functionExample() { return someExecutionCode(); }
lambdaFunctionExample = () => { return someExecutionCode(); }
}
But you mix 1st and 2nd lines in same time, start as object definition, with body as a function, which are not fits to each other.
2) Your render function NOT return anything, it's making array, but not return it.

React Native: How to export a method with a return value?

What is the best way to export a method with a return value in React Native?
I know there is RCT_EXPORT_METHOD, but that only works for methods that are (void) and therefore don't return anything. Preferably I don't need to export the whole class, just a few methods.
The other option would be to have a callback, but I would like to avoid that if possible as it bloats up the code too much in my use case. Are there are any other options I might have missed?
You can also now use promises, which tend to look a little nicer in your JS.
Objective C:
RCT_REMAP_METHOD(getThing, resolver: (RCTPromiseResolveBlock)resolve
rejecter:(RCTPromiseRejectBlock)reject)
{
if( condition ) {
NSString *thingToReturn = #"ALL OK";
resolve(thingToReturn);
} else {
reject([NSError errorWithDomain:#"com.companyname.app" code:0 userInfo:#{ #"text": #"something happend" }]);
}
}
Then in JS:
async onPress() {
try {
const status = await CustomModule.getThing();
// do something with status
} catch(e) {
console.error(e);
}
}
Try return values with Callbacks
RCT_EXPORT_METHOD(findEvents:(RCTResponseSenderBlock)callback)
{
NSArray *events = ...
callback(#[[NSNull null], events]);
}
Seems no way yet. That should be a feature to support.

React - getting a component from a DOM element for debugging

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..

Categories

Resources