Appending JSX in React Native - javascript

I'm searching for a way to add a JSX element programmatically in React Native. Not conditionally.
It needs to be independent function, so far I couldn't find anything about this. Let me give you code example;
const appendJSX = () => {
const jsx = <Text> Hello! </Text>
append(jsx) // <---- can we do something like this?
}
let's say I call this function with useEffect and it should add whatever jsx I have inside the function. Up until this point I always see things like pushing inside of an array or something like that.
UPDATE
Equivalent behaviour that works on web;
useEffect(() => {
const div = document.createElement("div");
div.innerText = "appended div"
document.body.append(div)
}, [])
As you can see we don't have to touch any JSX in application. Reaching document.body and appending whatever we want is possible in React Web. But how can we achieve this in React Native?

Not quite sure what you want to do, but as for to add a JSX manually. Here's the answer.
JSX is already part of the language in most of the cases.
const a = <Text />
export default a
Will translates into:
const a = createElement(Text, null, null)
export default a
Therefore in most of common cases, if you continue using React somewhere else, then the variable a holds a React element without any compilation error.
You might wonder what a actually really holds, it's an object:
const a = {
$$typeof: Symbol(ReactElement),
props: null,
type: Text
}
So you can see the only dependencies in above piece is Text and the ReactElement Symbol. As long as you can resolve them, you are good to export this to anywhere. The latter is normally taken care by Babel.
NOTE:
There's a difference between Text and <Text />. If you just want to export a Text which is a function component, there'll tutorial online, also you can dig into any third party library, because essentially that's what they do, export Text so other people can use it.

Related

render with different child components from props

I am currently working in a scenario, where I need to be able to import a component from library, but tell it to choose different components for some of its child components to render with. In this case, it needs to choose different button components, for example. Now I already got this working, as in, it does what it needs to do, but I am wondering if there is maybe a more fitting/appropriate way of doing it.
export const container = ({component, children}) => {
const ButtonComponent = component?.button ?? Button;
return (
<div>
<ButtonComponent size="large">Do something</ButtonComponent>
</div>
)
}
In this case, Buttons are defined in this same library, but on the side of the application where the library is consumed, the buttons are modified, variants are added, some properties are added that are not part of the original component of the library. And I am telling the component to use a different component like this:
<container component={FancyButton} />
As I said this works, but it feels like there might maybe be a more elegant solution to this. This is relevant, because the library uses an atomic design methodology approach, where some of the more complex components use less complex components, but they are being modified for a specific usecase. So all the buttons are being modified to have additional variants, etc. But if I then go a head and for example use the modal, it uses the regular buttons, not the modified buttons. This is the solution that I came up with, that allows me to tell the component to use these modified buttons instead.
Does this make sense? Is this an anti-pattern? Is there a more efficient/elegant solution to this?
€1: Here's a codesandbox demonstrating what this does: https://codesandbox.io/s/practical-ives-jxk3v
const defaultComponents = {
button: BaseButton
}
export const Card = ({ components=defaultComponents, children }) => {
return (
<div className="card">
<h2>Thing</h2>
{children}
<components.button className="card__button">Click me</components.button>
</div>
);
};
I don't know is this help. I will do like this.
use = when you passing props in a function mean the default value of that prop.

Will JSX conditional rendering out of an object code split?

Will conditional rendering out of an object code split and lazy load as expected? Here's a short example of what I'm talking about.
const Component1 = lazy(() => import('some path'));
const Component2 = lazy(() => import('some path'));
const Component3 = lazy(() => import('some path'));
render () {
const { selectionIndex } = this.state;
<Suspense fallback={<div>Loading...</div>}>
{{
one: <Component1 />,
two: <Component2 />,
three: <Component3 />,
}[selectionIndex]}
</Suspense>
}
I want to know whether all three components will load on render, or just the one selected by selectionIndex. I'm trying to use this to conditionally select something to display based on a menu set by state, but I don't want to load everything at once.
They will not get rendered all at once. You can experiment by yourself, put console.log inside components is an easy way to find out.
React for web consists of two libs, "react" and "react-dom". "react" is in charge of encapsulating your logic intention into declarative data structures, while "react-dom" consumes these data structures and handles the actual "rendering" part of job.
The JSX element creation syntax <Component {…props} /> translates to plain JS as an API call to React.createElement(Component, props). The return value of this API call is actually just a plain object of certain shape that roughly looks like:
{
type: Component,
props: props
}
This is the aforementioned "declarative data structure". You can inspect it in console.
As you can see, calling React.createElement just return such data structure, it will not directly call the .render() method or functional component’s function body. The data structure is submitted to "react-dom" lib to be eventually "rendered".
So your example code just create those data structures, but the related component will not be rendered.
seems like its conditionally loaded based on selectionIndex. all the other components are not loaded at once.
P.S.: if you ever feel like which will get load first, just put a console log in that component and debug easily
conditionally load demo link - if you open this, the components are being loaded initially based on selectionIndex value being "one".
I'm not going to go into too much technical detail, because I feel like #hackape already provided you with a great answer as to why, point of my answer is just to explain how (to check it)
In general, I'd recommend you to download download the React Developer Tools
chrome link
firefox link
and then you can check which components are being rendered if you open the components tab inside your developer console. Here's a sandboxed example, best way to find out is to test it yourself afterall :-)
As you can see in the developer tools (bottom right), only the currently set element is being rendered

dynamic image for autocomplete / components

I am trying to make a crypto react native app, I am trying to have icons to match with an autocomplete. So it would work very similar to the coinmarketcap search, where you can type a coin symbol and you will see the corresponding coin with its icon.
I think there could be a few ways to do this and I think this would be a good place for discussion on what is the best way.
For starters, I created a script that would take all the icons in a directory (SVG or PNG), and create a json that would have the symbol as the value and the key will be a reference to the icon.
With this method there are a few problems I have encountered. The solutions have been covered in this post.
React Native - Dynamic Image Source.
However, it has been hard for me to find a good solution to either
A. Encode all the image in a directory into a base64encoded string and put it into a json
B. Create an array on the React Native end which will have the require('path-to-image')
Relevant code examples are in the post so i dont want to repeat it, but I guess I would just like to know which one is the best practice. I think that doing it as a array of modules would be best. But I am not sure have to dynamically create something like that.
example of the dictionary I created is something like this:
Given a JSON object like this how would you extend it so it would become
const image = {
key1: 'path/to/key/one.png'
key2: 'path/to/key/two.png'
}
To
{
key1: require('path/to/key/one.png'),
key2: require('path/to/key/two.png')
}
you would it to fit into a react native component like so
<Image
source={ (images[symbol])}
/>
You have a couple of options, and I think you nailed the best one in your case (tested in React Native 0.50.3):
import {Image} from 'react-native'
export default (props) => <Image source={icons[props.currency]} />
const icons = {
bitcoin: require('../path/to/bitcoin.png'),
ethereum: require('../path/to/ethereum.png'),
...
}
Lol that's basically exactly what you already wrote in your question. I've also gotten away with storing a bunch of require(...) statements in an array and pulled by index, e.g:
import {Image} from 'react-native'
export default (props) => <Image source={icons[props.index]} />
const icons = [
bitcoin: require('../path/to/bitcoin.png'),
ethereum: require('../path/to/ethereum.png'),
...
]
This approach is really only useful if you don't know the key to identify your target reference by (e.g if you wanted to cycle through a bunch of images randomly). For the described use case I'd go with key lookup.

Render custom React component within HTML string from server

I have a HTML string that comes from the server, for example:
const myString = '<p>Here goes the text [[dropdown]] and it continues</p>`;
And I split this string into 3 parts so the result is the following:
const splitString = [
'<p>Here goes the text ',
'[[dropdown]]',
' and it continues</p>'
];
Then I process those 3 parts in order to replace the dropdown with a React component:
const processedArr = splitString.map((item) => {
if (/* condition that checks if it's `[[dropdown]]` */) {
return <Dropdown />;
}
return item;
}
So after all, I get the processed array, which looks like this:
['<p>Here goes the text ', <Dropdown />, ' and it continues</p>']
When I render that, it renders the HTML as a text (obviously) with the Dropdown component (that renders properly) in between the text. The problem here is that I cannot use { __html: ... } because it has to be used such as <div dangerouslySetInnerHTML={{ __html: ... }} />. I cannot add <div> around the string because that would cut out the <p> tag.
I thought about splitting the parts into tags and then in some sort of loop doing something like:
React.createElement(tagName, null, firstTextPart, reactComponent, secondTextPart);
but that would require fairly complex logic because there could be multiple [[dropdown]]s within one <p> tag and there could be nested tags as well.
So I'm stuck right now. Maybe I'm looking at the problem from a very strange angle and this could be accomplished differently in React. I know that React community discourages rendering HTML from strings, but I cannot go around this, I always have to receive the text from the server.
The only stackoverflow question I found relevant was this one, however that supposes that content coming from backend has always the same structure so it cannot be used in my case where content can be anything.
EDIT:
After some more digging, I found this question and answer which seems to be kinda solving my problem. But it still feels a bit odd to use react-dom/server package with its renderToString method to translate my component into a string and then concatenate it. But I'll give it a try and will post more info if it works and fits my needs.
So after playing with the code, I finally came to a "solution". It's not perfect, but I haven't found any other way to accomplish my task.
I don't process the splitString the way I did. The .map will look a bit different:
// Reset before `.map` and also set it up in your component's constructor.
this.dropdownComponents = [];
const processedArr = splitString.map((item) => {
if (/* condition that checks if it's `[[dropdown]]` */) {
const DROPDOWN_SELECTOR = `dropdown-${/* unique id here */}`;
this.dropdownComponents.push({
component: <Dropdown />,
selector: DROPDOWN_SELECTOR
});
return `<span id="${DROPDOWN_SELECTOR}"></span>`;
}
return item;
}).join('');
Then for componentDidMount and componentDidUpdate, call the following method:
_renderDropdowns() {
this.dropdownComponents.forEach((dropdownComponent) => {
const container = document.getElementById(dropdownComponent.selector);
ReactDOM.render(dropdownComponent.component, container);
});
}
It will make sure that what's within the span tag with a particular dropdown id will be replaced by the component. Having above method in componentDidMount and componentDidUpdate makes sure that when you pass any new props, the props will be updated. In my example I don't pass any props, but in real-world example you'd normally pass props.
So after all, I didn't have to use react-dom/server renderToString.
How about break the text apart and render the component separately?
your react component should look like this (in JSX):
<div>
<span>first text</span>
{props.children} // the react component you pass to render
<span>second part of the text</span>
</div>
and you would just call out this component with something like:
<MessageWrapper>
<DropdownComponent/> // or whatever
</MessageWrapper>

React DnD: 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.

Categories

Resources