I'm trying to use an internal Javascript library which is normally used for the web and which contains millions of lines of code (hence why I would really like to use it rather than change anything in it :D) with React Native.
This library has some DOM manipulations in it which are abstracted, and I'm trying to make the abstraction work using React Native.
Basically, what we do on the web is that we pass an HTML element to the library, and the library fills it with other elements. Later on, this library may remove elements from the tree or add some, depending on what we're telling it to do.
So I'd like to be able to pass this library a ReactElement, so that React Native handles the drawing part automatically.
Here is what the library does, very very simplified:
function libraryCode(element) {
element.append(otherElement);
}
var element = document.createElementNS('div');
libraryCode(element);
return element;
And here is what I'd like to be able to do:
render() {
var element = React.createElement(Element);
libraryCode(element);
return element;
}
But ReactElement doesn't have an append function. So I was wondering if there was a way to add functions to ReactElement someway. It would allow me to create a function called append that would add the child and re-render the element. I've tried and it's readonly obviously. I wanted to extend the ReactElement class but how to tell React to use my class instead of ReactElement?
var elem = React.createElement(Element);
elem.append = function() {};
I'm also open to new ideas, I'm quite new to React/React Native so maybe I'm going all wrong with this! :)
Thanks
What you need to do is use React.cloneElement and then do your appending by passing in children. This should allow you to do:
function libraryCode(element, otherElement) {
return React.cloneElement(element, null, [otherElement]);
}
EDIT
The closest I got to getting it to work but not conform to your needs is this:
var oldCreateElement = React.createElement;
React.createElement = function(...args) {
let element = Object.assign({}, oldCreateElement(...args));
element.append = (otherElement) => {
// Unfortunately you simply can't replace the
// reference of `element` by the result of this operation
return React.cloneElement(element, null, [
React.cloneElement(otherElement, {
// You must give a `key` or React will give you a warning
key: Math.random(),
}),
]);
};
return element;
};
This will only let you do:
var element = React.createElement(Element);
element = element.append(<Text>Hi</Text>);
return element;
But not:
var element = React.createElement(Element);
element.append(<Text>Hi</Text>);
return element;
Anyway, this is very hacky and NOT recommended and frowned upon because it requires monkey-patching.
Related
I am using two script editors (based on AceEditor) within one form, each uses separate completer. Currently completer is instantiated inside a component like this:
const langTools = ace.require("ace/ext/language_tools");
langTools.addCompleter(completer);
where completer is a prop.
This approach results in merged completions for every script field (within one form) as completers use a singleton inside language_tools.
To clarify completer1 should suggest "foo", completer2 should suggest "bar", but currently both completers suggest "foo", "bar"
In vanilla ace-editor it is recommended to copy editor completers to avoid using them by reference e.g.:
editor.completers = editor.completers.slice();
editor.completers.push(myCompleter);
Is there a way to achieve a similar result inside react-ace component?
It turned out to be as simple as using a ref to <AceEditor ref={ref} {...props}/> and then instead of relying on language_tools using ref.current.editor.completers in a similar fashion to vanilla ace-editor solution.
I ended with creating a hook for handling completer initialization and clean-up.
function useCompleter(completer?: CompleterInterface) {
// ref is required to access `editor` of vanilla ace-editor
const ref = useRef<ReactAce>(null);
// Init completer
useEffect(() => {
// Apply optional completer
if (completer && ref?.current) {
// prevent using editor.completers by reference, so separate completers per editor are possible
ref.current.editor.completers = ref.current.editor.completers.slice();
ref.current.editor.completers.push(completer);
}
return () => {
if (completer && ref?.current) {
ref.current.editor.completers = [];
}
};
}, [completer]);
return ref;
}
Based off this Q&A:
React wrapper: React does not recognize the `staticContext` prop on a DOM element
The answer is not great for my scenario, I have a lot of props and really dislike copy-pasting with hopes whoever touches the code next updates both.
So, what I think might work is just re-purposing whatever function it is that React uses to check if a property fits to conditionally remove properties before submitting.
Something like this:
import { imaginaryIsDomAttributeFn } from "react"
...
render() {
const tooManyProps = this.props;
const justTheRightProps = {} as any;
Object.keys(tooManyProps).forEach((key) => {
if (imaginaryIsDomAttributeFn(key) === false) { return; }
justTheRightProps[key] = tooManyProps[key];
});
return <div {...justTheRightProps} />
}
I have found the DOMAttributes and HTMLAttributes in Reacts index.t.ts, and could potentially turn them into a massive array of strings to check the keys against, but... I'd rather have that as a last resort.
So, How does React do the check? And can I reuse their code for it?
The following isn't meant to be a complete answer, but something helpful for you in case I forget to come back to this post. The following code is working so far.
// reacts special properties
const SPECIAL_PROPS = [
"key",
"children",
"dangerouslySetInnerHTML",
];
// test if the property exists on a div in either given case, or lower case
// eg (onClick vs onclick)
const testDiv = document.createElement("div");
function isDomElementProp(propName: string) {
return (propName in testDiv) || (propName.toLowerCase() in testDiv) || SPECIAL_PROPS.includes(propName);
}
The React internal function to validate property names is located here: https://github.com/facebook/react/blob/master/packages/react-dom/src/shared/ReactDOMUnknownPropertyHook.js
The main thing it checks the properties against is a "possibleStandardNames" property-list here: https://github.com/facebook/react/blob/master/packages/react-dom/src/shared/possibleStandardNames.js
So to reuse their code, you can copy the property-list in possibleStandardNames.js into your project, then use it to filter out properties that aren't listed there.
I need to resolve all React components into their respective React elements (e.g., type=div,ul,li), including all nested components that there may be. I'm doing it successfully already; however, I'm getting a warning about calling a React component directly. Also, it's quite possible I'm going about it wrong in the first place.
function resolveNestedDomElements(reactElement) {
// is there a better way to check if it's a component?
if (React.isValidElement(reactElement) && _.isFunction(reactElement.type)) {
// is there a better way to access a component's children?
reactElement = reactElement.type(reactElement.props);
// Warning: Something is calling a React component directly. Use a
// factory or JSX instead. See https://fb.me/react-legacyfactory
// Note: Unfortunately, the factory solution doesn't work.
}
if (!React.isValidElement(reactElement)) {
return reactElement;
}
let { children } = reactElement.props;
if (React.Children.count(children)) {
if (React.isValidElement(children)) {
children = resolveNestedDomElements(children);
} else {
children = React.Children.map(
children,
resolveNestedDomElements
);
}
}
return React.cloneElement(reactElement, reactElement.props, children);
}
Your original code now returns an error in 0.14.7, with no output, from React, containing the same message that used to warn (again, behavior as described in the link provided above).
Updating your code to this, as per the update here:
function resolveNestedDomElements(reactElement) {
if (React.isValidElement(reactElement) && typeof reactElement.type === 'function') {
reactClass = React.createFactory(reactElement.type)
reactElement = reactClass(Object.assign({},reactElement.props));
}
if (!React.isValidElement(reactElement)) {
return reactElement;
}
let { children } = reactElement.props;
if (React.Children.count(children)) {
if (React.isValidElement(children)) {
children = resolveNestedDomElements(children);
} else {
children = React.Children.map(
children,
resolveNestedDomElements
);
}
}
return React.cloneElement(reactElement, reactElement.props, children);
}
This no longer warns (or, in current versions, it now just returns an error in the original form you posted), but the output is not resolved, either. It's unclear to me why this has changed.
The answer seems to be that it seems impossible to resolve anymore. I can't quite follow what's happening in the React source (I believe the render functions of components eventually are called by ReactDOM.render these days, but how that works is unclear to me still). Calling ReactDOM.render doesn't work without a DOM. There is a react-test-utils.shallowRenderer that looks like it may be helpful, that I need to experiment with.
It's a bit absurd how difficult this is. I'm not sure what architecture decision I'm missing at this point.
I'm looking at one of my colleague's ReactJs code and noticed that he is passing an array of custom objects down to 5 levels of child components as props. He is doing this b/c the bottom level child component needs that array's count to perform some UI logic.
At first I was concerned about passing a potentially large array of objects down to this many levels of component hierarchy, just so the bottom one could use its count to do something. But then I was thinking: maybe this is not a big deal since the props array is probably passed by reference, instead of creating copies of this array.
But since I'm kind of new to React, I want to go ahead and ask this question here to make sure my assumptions are correct, and see if others have any thoughts/comments about passing props like this and any better approach.
In regards to the array being passed around I believe it is indeed a reference and there isn't any real downside to doing this from a performance perspective.
It would be better to make the length available on Child Context that way you don't have to manually pass the props through a bunch of components that don't necessarily care.
also it seems it would be more clear to pass only the length since the component doesn't care about the actual objects in the array.
So in the component that holds the array the 5th level child cares about:
var React = require('react');
var ChildWhoDoesntNeedProps = require('./firstChild');
var Parent = React.createClass({
childContextTypes: {
arrayLen: React.PropTypes.number
},
getChildContext: function () {
return {
arrayLen: this.state.theArray.length
};
},
render: function () {
return (
<div>
<h1>Hello World</h1>
<ChildWhoDoesntNeedProps />
</div>
);
}
});
module.exports = Parent;
And then in the 5th level child, who is itself a child of ChildWhoDoesntNeedProps
var React = require('react')
var ArrayLengthReader = React.createClass({
contextTypes: {
arrayLen: React.PropTypes.number.isRequired
},
render: function () {
return (
<div>
The array length is: {this.context.arrayLen}
</div>
);
}
});
module.exports = ArrayLengthReader;
I don't see any problems with passing a big array as a props, even the Facebook is doing that in one of their tutorial about Flux.
Since you're passing the data down to this many lever you should use react contex.
Context allows children component to request some data to arrive from
component that is located higher in the hierarchy.
You should read this article about The Context, this will help you with your problem.
with React v0.12 the #jsx pragma is gone which means it is no longer possible to output jsx with anything other than the React.METHODNAME syntax.
For my use case I am trying to wrap the React object in another object to provide some convenience methods thus, in my component files, I want to be able to write:
var myConvenienceObject = require('React-Wrapper');
var Component = myConvenienceObject.createSpecializedClass({
render: function () {
return <div />
}
})
However the jsx compiler automatially converts <div /> into React.createElement("div", null)
With older versions of React it was possible to handle this using the pragma at the top of the file. However, since that has been removed, I was wondering if there was any way currently to change the name of the object compiled by jsx so <div /> would be transformed into myConvenienceObject.createElement("div", null)
No, it's no longer possible to use a custom prefix for JSX. If you need to do this, you'll need to modify the JSX transform code, or create a fake React.
var React = require('react'), FakeReact = Object.assign({}, React, {
createElement: function(component, props, ...children){
// ...
// eventually call the real one
return React.createElement(component, props, ...children);
}
});
module.exports = FakeReact;
And then to use it you import the fake react and call it React.
var React = require('fake-react');
// ...
render: function(){ return <div />; }
If you would like to make some elements contains in your myConvenienceObject, you could consider the children props as shown in the doc. But this may need some changes in the myConvenienceObject too, to accept the children.
By the way, i'm not sure where is this createSpecializedClass functions comes from and what it does