Turning a TSX/JSX React into a string or DOM element - javascript

So I have an unusual requirement from my tech lead. I essentially need to turn a callback that returns JSX and turn the output into an HTMLElement or a string.
The thing is that we're building a reusable, low level infinite scroll component that reuses the DOM so that only 20 DOM elements are ever present on the componenent. Here is the codepen for the PoC I was sent for this:
https://codepen.io/hemant30/pen/rNWQEZy?editors=0010
The codepen works, but doesn't handle anything in React, it's purely DOM manipulation.
The callback the component receives is something like this:
(data: any) => {
return (
<div key={data.id}>
{data.id} - {data.name}
<br />
Text
</div>
);
}
The idea is to be able to then, when using this callback internally, turn it into something that can be added to an element through dangerouslySetInnerHTML.
I have tried multiple things and approaches, but my approaches haven't been approved. So I turn to you to see if there is a way to do this or not.

You can check renderToStaticMarkup method, it can transform jsx into string html.
import ReactDOMServer from 'react-dom/server'
const htmlString = ReactDOMServer.renderToStaticMarkup(
<div>
<Component/>
/* etc. */
</div>
);

Related

Why use refs in react? What is the use cases for it?

This question exists but it didn't give a lot of data or real world explanation: What are Refs in React or React-Native and what is the importance of using them
Let's say i want to integrate to 3rd party library how ref is going to help me?
Some 3rd party libraries expose methods to interact with their components.
For example, in react-native-elements npm, they have shake method for Input component. You can use this method to shake Input element when user input is invalid.
Common use case is as follows:
import React from 'react';
import { Input, Button } from 'react-native-elements';
const [value, setValue] = useState('');
const input = React.createRef();
return (
<View>
<Input
ref={input}
onTextChange={(text) => setValue(text)}
/>
<Button
title={'Submit'}
onPress={() => {
if (!isValid(value)) {
input.current.shake();
}
}}
/>
</View>
);
This is react native example, but the similar goes to react projects. I hope you get the picture. Animations like shake cannot be easily handled with state, so it's better to use useRef to call component methods directly.
Let's say i want to integrate to 3rd party library how ref is going to help me?
Refs let you access the DOM directly, thus you can use vanilla js libraries using refs, for example you could use jQuery like $(ref). This simplifies and makes getting DOM nodes less error prone than using other techniques such as adding classes/ids to every element and then using selectors since these methods do not stop you from accessing nodes not created by you.
Long story short, Refs let you treat react elements as though they were vanilla js
React useRef help us to accessing dom elements before its rendering.
You can go through it
https://reactjs.org/docs/refs-and-the-dom.html
Whenever you want to use the properties of child from a parent, we refer it with a ref id, this is to ensure we are executing on the right child component. The properties can be either states, props of functions defined in the child component.

Is it possible to lazy render component in StencilJS?

As you want to build complex component, it would be great if you can wrap any DOM with component such as "lazy-load" component with condition (#Prop() condition: boolean) so to illustrate what I want:
<lazy-load condition={some boolean condition, like certain link get clicked and section is now active}>
<data-fetch>
</data-fetch>
</lazy-load>
in this example, "data-fetch" will make a HTTP call to grab some large data, and I want to defer this component added to DOM until condition we specify in the lazy-load component to be true.
So I started to implement render() of lazy-load component as something along the line of
#Prop() condition: boolean;
render() {
if(!this.condition) {
return null;
}
return (
<slot/>
);
}
and try to use it as
<lazy-load condition={false}>
<data-fetch>
</data-fetch>
</lazy-load>
but no matter what I tried, data-fetch component get added to DOM (and while we can set visibility to hide element, we would waste HTTP call) I understand I can put the same condition in the data-fetch itself and then not make a fetch call when condition is false, but if possible I want generic wrapper component to achieve this (if you are familiar with AngularJS or Angular, I want to find a way to do equivalent of ng-if and *ngIf off of generic wrapper component)
Maybe this is a limitation due to how "slot" tag supposed to work? (also, I'm using it with #Component({shadow: false}) so I know I'm not using standard shadowDOM from the web component spec so maybe what I'm trying to do is not feasible?
Thank you very much for your time in advance to even read this question and I appreciate any help I can get. I feel if we can do this, we might be able to build component that can quickly differ loading until whenever we feel it should load/render.
Yeah it's an issue with not using shadow: true, because in the polyfill the slotted content just becomes part of the light DOM (but gets placed where the slot element is). Beware that even if you enable Shadow DOM, it'll still fallback to the polyfill if the browser doesn't support it. You could raise an issue about this in Github but I'm not sure if/how it would be possible to solve this "dynamic slot" problem.
But I think you can take a simpler approach:
{myCondition && <data-fetch />}
That way the data-fetch element will only be added once the condition becomes true.
You could also refactor this into a functional component:
import { FunctionalComponent } from '#stencil/core';
interface Props {
if: boolean;
}
export const LazyLoad: FunctionalComponent<Props> = ({ if }, children) =>
if && children;
import { LazyLoad } from './LazyLoad';
<LazyLoad if={condition}>
<data-fetch />
</LazyLoad>

Is the reason props in React shouldn't be changed/mutated is because React wants elements to be as predictable as possible?

From React documentation.
Conceptually, components are like JavaScript functions. They accept
arbitrary inputs (called “props”) and return React elements describing
what should appear on the screen.
Considering:
function Welcome(props) {
return <h1>Hello, {props.name}</h1>;
}
or
class Welcome extends React.Component {
render() {
return <h1>Hello, {this.props.name}</h1>;
}
}
Will give us the ability to do this:
<Welcome name="Luke" />;
<Welcome name="Leia" />;
to use as we wish in the DOM,
Hello, Luke
Hello, Leia
Now when people prescribe props shouldn't be changed, it would make sense the reason is in my thinking would be like the same as changing the values of attributes of an image tag?
HTML:
<img id="Executor" alt="Picture of Executor" src="/somepath/vaders-star-destroyer-executor.jpg"/>
JS:
Meanwhile in a Javascript file a long time ago in a galaxy far, far away...
var imageOfVadersStarDestroyer = document.getElementById('Executor');
imageOfVadersStarDestroyer.src = "/somepath/vaders-star-destroyer-avenger.jpg"
Because if we keeping changing an elements attribute values this can cause confusion and slower renderings?
So is the reason why the prescription is to never change props in React is because is the library is trying to make elements as predictable as possible?
Setting props outside of React is dangerous and should be avoided. Why? The main reason is that it doesn't trigger re-renders. Hence bugs and unexpected behaviour.
Re-rendering
Most of the time, props are data that is store as state in the parent component, which is manipulated by calling setState() (or the second function returned by React.useState()). Once setState() is called, React re-renders and computes what has changed under the hood, with the latest props and state. Manually assigning values to props, therefore won't notify React that the data has changed and something has to be re-rendered.
The good practice
Making props read-only allows React components to be as pure as possible, which is obviously a good practice anyway even when writing plain JS. Data won't be changed unexpectedly and can only be done so by calling setState() (You might have heard of the single source of truth, which is what React is trying to leverage).
Imagine you notice something went wrong in the app and the data shown to the end user is completely different from the source, it would be a pain trying to find out where the data has been manipulated wouldn't it? :)
never change props in React
means that you should never do this.props.name = "userName" because of React's one way data binding, props are read only, to update a component's props, you should pass a function from the parent that will do that ( in the parent ) , or dispatch an action if you're using redux, a change in the props will trigger a re-render
props is a constant in this case. You will always need it in your components.
But there is a cleaner way to write it or even omit it.
Regular way with Function Expression (same as your exemple)
function Welcome(props) {
return <h1>Hello, {props.name}</h1>;
}
ES6 Object Destructing - explicit
function Welcome(props) {
const {name} = pros
return <h1>Hello, {name}</h1>;
}
ES6 Object Destructing - inplicit, cleaner way
function Welcome({name}) {
return <h1>Hello, {name}</h1>;
}
And of course, you can use the class way which requires the usage of this.props.yourAttr
However, in the new version 3 of create-react-app, changed class components to functional components. You can see this exact modification on Github here.
You can need to learn more about destructing assignment in the old and good MDN linked here or an in-depth approach both array and object destructuring here.

Render multiple React components into a single DOM element

I'm looking to render multiple modals into a single ReactDOM element. Here's the HTML structure that React renders to.
<body>
<div id="modal-socket"></div> // Insert multiple here
<div id="wrapper">
// Other content goes here
</div>
</body>
There's a long story behind why I need to render multiple components into #modal-socket but I want to do something akin to this:
ReactDOM.render(<AddMeasurableModal />, document.getElementById("modal-socket"));
ReactDOM.render(<AddMeasurableModal />, document.getElementById("modal-socket"));
ReactDOM.render(<AddMeasurableModal />, document.getElementById("modal-socket"));
Obviously this replaces the current content of #modal-socket on each render call.. So I don't get my end result. Boo.
Did a search and found a few answers on it but none meet my needs.
Cheers.
As you told in a comment, the dynamic way would be something like this
Inside of a main component you could do:
Imagine having an array like:
let myArray = [
{
prop1: 'hello world'
},
{
prop1: 'Hey there!'
}
]
//Then in the render function (you can put that array into the state or something)
render(){
return (
<div>
{myArray.map((entry,index) => {
return <AddMeasurableModal key={index} {...entry} />
})}
</div>
)
}
this will create as many AddMeasurableModal components as there are entrys in the myArray variable and add every property stored as props onto the component (In this case, every AddMeasurableModal component has access to the this.props.prop1 value, because of the {...entry} spread syntax)
Notice how I only put myArray.map() into the render function inside of {}?
React renders every array of components without further configuration inside of the render function. And Array.map() returns an array. Just make sure to return only valid react elements! When doing this, don't forget to add a uniqe key prop to each element to avoid warnings.
EDIT: in this case, the key prop is the current index in the array, but when fetching data from a server I would recommend to use a uniqe id from the database or something to avoid rendering bugs.
If you don't want to map over an array, you can just set a number of components and then loop over them, creating an array of components and put them into the render function.
Wrap your multiple modals into 1 container and render that, eg:
let modals = (
<div>
<AddMeasurableModal />
<AddMeasurableModal />
<AddMeasurableModal />
</div>
);
ReactDOM.render(modals, document.getElementById("modal-socket"));

React: Bubbling up click events on nested components

I'm creating a react file tree, and I have the tree setup as a React component. The tree can take a contents prop that is an array of either strings, or other <Tree /> components (this enables the nested file structure UI). These tree components can be nested indefinitely.
I need to register a click event on the children of the nested tree components, but I'm having trouble getting it to work beyond the first level of nesting. A simplified example of what I'm dealing with:
//In App - the top level component
const App = React.createClass({
_handleChildClick () {
console.log("this is where all child clicks should be handled");
},
render () {
return (
<Tree
handleChildClick={this._handleChildClick}
contents={[
<Tree />
]}
/>
);
}
});
//And in the tree component
<div onClick={this.props.handleChildClick}></div>
If you want to see more detail - here's the github repo.
I tried researching this question and saw people using {...this.props} but I'm not sure if that applies to my scenario - if it does, I couldn't get it to work.
Thanks for any help on this.
The reason why the click handling does not work beyond the first level is because your second level Tree component (the one inside the contents array) does not get the appropriate prop handleChildClick passed in. (BTW I think the convention is to call the prop onChildClick while the handler function is called handleChildClick - but I digress.)
Do I understand correctly that you actually want to inform each layer from the clicked component up to the top? For this to happen, you need to extend the props of the tree component that is inside the contents array - it needs to receive the click handler of its parent component. Of course, you cannot write this down statically, so it needs to be done dynamically:
Your Tree component, before actually rendering its children, should extend each of them with the component's click handler, which can be done using the function React.cloneElement (see API documentation and a more detailed discussion). Directly applying this to your component makes things a bit messy, because you are passing the component's children in a prop, so you need to figure out which prop to modify. A bit of a different layout would help you quite a lot here:
<Tree handleChildClick={this._handleChildClick}>
<Tree />
</Tree>
looks nicer IMHO and makes the structure much clearer. You can access the inner components via this.props.children, and cloneElement will be much simpler to use.
So, in your Tree component, you could have a render method like this:
render () {
const newChildren = this.props.children.map(child =>
React.cloneElement(child, {onChildClick: this._handleChildClick}));
return (
<div>{newChildren}</div>
);
}
Please note that this code will not work if you have a mixture of strings and Tree components, therefore my third and last suggestion would be to wrap those strings into a very thin component to allow for easier handling. Alternatively, you can of course do a type comparison inside the map.

Categories