How to convert react component to html string? - javascript

I have found similar to my issue here.
But I have a little bit different scenario. I have string of html rather than just string. So, I wanted to do:
Let's suppose MyComponent is just returning h3 element for now:
const MyComponent = ({children}) => (<h3>{children}</h3>)
var parts = "<h1>I</h1><p>am a cow;</p>cows say moo. MOOOOO."
.split(/(\bmoo+\b)/gi);
for (var i = 1; i < parts.length; i += 2) {
parts[i] = <MyComponent key={i}>{parts[i]}</MyComponent>;
}
// But I need html to be rendered
return <div dangerouslySetInnerHTML={{ __html: parts }} />
This will be rendered in the browser like this:
I
am a cow;
cows say ,[object Object],. ,[object Object],.
What I can think here to resolve the issue is converting component with string of html first.
parts[i] = convertToStringOfHtml(<MyComponent key={i}>{parts[i]}</MyComponent>);
But I don't have idea how to convert component to string of html.

You can do with react-dom
import { renderToString } from 'react-dom/server'
//
//
parts[i] = renderToString(<MyComponent key={i}>{parts[i]}</MyComponent>)
view more here https://reactjs.org/docs/react-dom-server.html

You can also do something like this
import React from "react";
import ReactDOM from "react-dom";
import ReactDOMServer from "react-dom/server";
const Hello = () => <div>hello</div>;
const html = ReactDOMServer.renderToStaticMarkup(<Hello />);
console.log(html.toString());

Related

How to Split String in React Js?

How to extract "GoogleUpdate.exe" from the string "C:\Program Files (x86)\Google\Update\GoogleUpdate.exe"?
You can do that with a combination of split() and pop():
const str = "C:\\Program Files (x86)\\Google\\Update\\GoogleUpdate.exe";
const parts = str.split("\\");
const fileName = parts.pop();
console.log(fileName); // outputs "GoogleUpdate.exe"
Note: You would need to escape the \ character
In javascript, character '\' is a escape character!
so, if you write your code lolike this:
const tmp = "C:\Program Files (x86)\Google\Update\GoogleUpdate.exe";
console.log(tmp);
you will be got result
C:Program Files (x86)GoogleUpdateGoogleUpdate.exe
if you want keep '\' in your variable, you can manual fix
const tmp = "C:\\Program Files (x86)\\Google\\Update\\GoogleUpdate.exe";
actualy, it's not smart,maybe string template literals is better.
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/raw
const tmp = String.raw`C:\Program Files (x86)\Google\Update\GoogleUpdate.exe`
console.log(tmp);
tmp.split('\\').pop();
It can be extracted from the given string by using the following steps:
Split the string by "". This will create an array of strings where each element is separated by a backslash.
Take the last element of the array, which is "GoogleUpdate.exe".
import React, { useState } from "react";
const Example = () => {
const [string, setString] = useState("C:\\Program Files
(x86)\\Google\\Update\\GoogleUpdate.exe");
const splitString = string.split("\\");
const extracted = splitString[splitString.length - 1];
return (
<div>
<p>Original String: {string}</p>
<p>Extracted String: {extracted}</p>
</div>
);
};
export default Example;
Just use the Split Method:
import React from 'react';
function Example() {
const path = "C:\Program Files(x86)\Google\Update\GoogleUpdate.exe";
const filename = path.split('\').pop();
return (
<div>
<p>The filename is: {filename}</p>
</div>
);
}
export default Example;
Another Way to achive this
import React, { useState } from 'react';
const Example = () => {
const [string, setString] = useState(
'C:\\Program Files (x86)\\Google\\Update\\GoogleUpdate.exe'
);
const splitString = string.split('\\');
const extracted = splitString.pop();
return (
<div>
<p>Original String: {string}</p>
<p>Extracted String: {extracted}</p>
</div>
);
};
export default Example;

How can I create JSX elements from an array of React-Icon names?

import React from "react"
import {GiCard2Spades,GiCard3Spades} from "react-icons/gi"
const x = ["GiCard8Spades","GiCard9Spade"]
const y = x.map(item => <item />) //basically to get <GiCard8Spades /> elements
return(<div>{y}</div>)
I know I can right away make array manually with JSX items like but need other way at this situation.
If you're dead set on using strings, you could try
import * as Icons from "react-icons/gi"
const x = ["GiCard8Spades","GiCard9Spade"];
return(<div>{x.map((item) => Icons[item])}</div>)
try this way
import React from "react"
import {GiCard2Spades,GiCard3Spades} from "react-icons/gi"
const x = [GiCard8Spades,GiCard9Spade];
return(<div>{x.map(item => item)}</div>)//basically to get <GiCard8Spades /> elements
This way you will create an array of JSX.Elements, not an array of strings
Yea Im kinda stupid, should just used
import React from "react"
import {GiCard2Spades,GiCard3Spades} from "react-icons/gi"
const x = [GiCard8Spades,GiCard9Spades]
const y = x.map(item => React.createElement(item))
return(<div>{y}</div>)

How do I pass a React Context's state through an onClick function created in a method inside that context class?

I have some code that makes an array of div elements to place inside a CSS grid, which is rendered via React since that's extremely fast after the first render. I want to be able to interact with those elements via multiple components, so I set up some context that stores the state of the grid.
I'm running into a problem now where I want to set an onClick function on those grid elements so that when they are clicked on, that updates the context's state. In this case I'm trying to keep track of the selected elements by an array. However, I can't seem to reference selectedTiles in the onClick function. It either won't compile, crashes on runtime, or doesn't do anything.
To be clear, if I remove the code lines relating to selectedTiles, it works and toggles the class just fine. I am also binding the function already in the constructor.
Here's the code I have right now, in the three relevant files.
index.tsx
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import * as serviceWorker from './serviceWorker';
import UserInterface from './components/userinterface';
import DisplayGrid from './components/displaygrid';
import {GridContextProvider} from './gridcontext';
ReactDOM.render(
<React.StrictMode>
<GridContextProvider>
<UserInterface />
<div className="grid-wrapper">
<DisplayGrid />
</div>
</GridContextProvider>
</React.StrictMode>,
document.getElementById('root')
);
serviceWorker.unregister();
displaygrid.js
import React from 'react';
import {GridContext} from '../gridcontext'
class DisplayGrid extends React.Component{
static contextType = GridContext;
render(){
const gridData = this.context;
return (
<div>
{gridData.name}<br />
<div className= "grid-container" style={{gridTemplateColumns: gridData.columns}}>
{gridData.tiles}
</div>
</div>
);
}
}
export default DisplayGrid;
gridcontext.js
import React from "react";
const GridContext = React.createContext();
class GridContextProvider extends React.Component {
constructor(props) {
super(props);
const topGridData = JSON.parse(localStorage.getItem('grids'))[0];
this.state = this.makeGrid(topGridData.dims[0], topGridData.dims[1], topGridData.dims[2],
topGridData.visible, topGridData.hex);
this.selectTile = this.selectTile.bind(this);
this.makeGrid = this.makeGrid.bind(this);
}
selectTile(event) {
let tilesArray = this.state.selectedTiles;
if(event.currentTarget.className === "grid-tile"){
event.currentTarget.className = "grid-tileb";
tilesArray.push(event.currentTarget.key);
} else {
event.currentTarget.className = "grid-tile";
tilesArray.splice(tilesArray.indexOf(event.currentTarget.key),tilesArray.indexOf(event.currentTarget.key));
}
}
makeGrid(x, y, tilesize, visible, hex){
let columnStr = "";
let tileArray = [];
const widthStr = tilesize.toString() + "px"
for (let i = 0; i < y; i++) {
for (let j = 0; j < x; j++) {
if(i===0) columnStr = columnStr + "auto ";//x loops over columns so this runs once for all columns.
let div = (
<div
key={"x" + j.toString() + "y" + i.toString()}//for example at coordinates 5,6 id is x5y6. starts at 0.
className="grid-tile"
style={{
width: widthStr,
height: widthStr,
border: "1px solid rgba(0, 0, 0," + (visible ? "0.6)" : "0.0)")
}}
onClick={this.selectTile}
>
</div>
)
tileArray.push(div);
}
}
const gridsDataArray = JSON.parse(localStorage.getItem('grids'));
const index = JSON.parse(localStorage.getItem('currentGrid'));
return {
columns: columnStr,
tiles: tileArray,
selectedTiles: [],
name: gridsDataArray[index].name,
bgurl: gridsDataArray[index].bgurl
};
}
render() {
return (
<GridContext.Provider value={{
columns: this.state.columns,
tiles: this.state.tiles,
selectedTiles: this.state.selectedTiles,
name: this.state.name,
bgurl: this.state.bgurl,
setNameBgurl: (newName, newBgurl) => {
this.setState({name : newName,
bgurl : newBgurl});
},
setGrid: (x, y, tilesize, visible, hex) => {
this.setState(this.makeGrid(x, y, tilesize, visible, hex));
}
}}>
{this.props.children}
</GridContext.Provider>
);
}
}
export { GridContext, GridContextProvider };
This code gives me a "TypeError: this is undefined" crash on runtime when I click on a tile, pointing at line 15 of gridcontext, which is let tilesArray = this.state.selectedTiles; which is strange since the method is bound.
Other feedback would be welcome since I'm new to React and not much better with javascript overall; I don't think I've quite wrapped my head around how javascript uses "this". I've been mostly trying to look up tutorials but it seems there are a lot of different ways to use React so it's hard to tell what practices are standard or not.
selectTile(key, className) {
let tilesArray = this.state.selectedTiles;
if(className && className === "grid-tile"){
// do something
// if u want to change element's className. maybe u need ref
} else {
// do something
}
}
makeGrid(x, y, tilesize, visible, hex){
...
for (let i = 0; i < y; i++) {
for (let j = 0; j < x; j++) {
if(i===0) columnStr = columnStr + "auto ";//x loops over columns so this runs once for all columns.
// u may assume
const key = "x" + j.toString() + "y" + i.toString()
// const className = "grid-tile"
// just pass whatever u need. that depends on u
let div = (
<div
key={key}//for example at coordinates 5,6 id is x5y6. starts at 0.
className="grid-tile"
style={{
width: widthStr,
height: widthStr,
border: "1px solid rgba(0, 0, 0," + (visible ? "0.6)" : "0.0)")
}}
// the `key` and the `className` depends on u
//
onClick={()=> this.selectTile(key, className)}
>
</div>
)
tileArray.push(div);
}
}
}
...
I figured out why the error is happening. I use makeGrid in the constructor before it's bound, which references selectTile, and the variable pointer for "this" is somehow set to a stale variable pointer that is undefined. Moving the binds up to the top fixes that error.
Immediately after fixing that error, a second one is found. TypeError: tilesArray is undefined This is because I'm referencing selectTile before the state is fully formed, and it again leaves a stale variable pointer behind.
The constructor must now look like this:
constructor(props) {
super(props);
const topGridData = JSON.parse(localStorage.getItem('grids'))[0];
this.selectTile = this.selectTile.bind(this);
this.makeGrid = this.makeGrid.bind(this);
this.state = {selectedTiles: []}
this.state = {
selectedTiles: this.state.selectedTiles,
...this.makeGrid(topGridData.dims[0], topGridData.dims[1], topGridData.dims[2],
topGridData.visible, topGridData.hex)
}
}
I immediately ran into a third problem, which is that the initial empty array is being kept as a stale variable pointer despite the attempt to reuse the variable. I changed the onClick to onClick={(event) => this.selectTile(this.state.selectedTiles, event)} to send in the state as an argument, and it works(though I do have to change selectTile to use a temporary array since I can't change the state directly via array mutation). I tried to use onClick={this.selectTile.bind(this, this.state.selectedTiles)} and it turns out that has the same problem as onClick={this.selectTile}.
Also it turns out that "key" is not a property stored in the div element, React kindof yanks it out and stores it internally for dealing with lists. I wound up setting the id property for each element to the same string and refer to that in selectTile for modifying the array.

React add HTML tags on string using predetermined components (alternative to dangerouslySetInnerHTML )

I do not want to employ dangerouslySetInnerHTML and the objective here is to put bold tags around each instance of a word that appears in my string.
Convention React wisdom might suggest that something like this could work?
const Bold = (props) => {
return (
<b>
{props.txt}
</b>
);
};
Here is where I try to incorporate the code
if (longString.includes(searchTerm)) {
longString = longString.replace(rest, <Bold txt={rest} />);
}
Problem is it comes out as [object Object] instead of desired <b>searchTerm</b>
How do I do set up the string swap so that it doesn't print the word [object] but rather prints the object?
You could try renderToString from react-dom
import { renderToString } from 'react-dom/server'
if (longString.includes(searchTerm)) {
longString = longString.replace(rest, renderToString(<Bold txt={rest} />) );
}

React renders again the same div elements - Reconciliation question

In React documentation I was reading about reconciliation. The following interesting scenario just came to my mind.
In my examples the code uses a button element to toggle a boolean value on click event. Based on that the code decides with ternary operator which element should React render.
Let's have two components to represent my example:
const First = () => {
return <div>element - no difference</div>
}
const Second = () => {
return <div>element - no difference</div>
}
There are no difference in the rendered elements at the end.
First example
Have First and Second functional components in the first example as the following:
const YourComponent = () => {
const [renderFirst, setRenderFirst] = useState(true);
return <>
<button onClick={() => setRenderFirst(!renderFirst)}>Toggle</button>
{renderFirst ? <First /> : <Second /> }
</>
}
Second example
In the second example just using div elements but ending with the same results:
const Contact = () => {
const [renderFirst, setRenderFirst] = useState(true);
return <>
<button onClick={() => setRenderFirst(!renderFirst)}>Toggle</button>
{renderFirst ? <div>element - no difference</div> : <div>element - no difference</div> }
</>
}
Question
My understanding as per the documentation states:
Whenever the root elements have different types, React will tear down the old tree and build the new tree from scratch.
At the end in either way the rendered result will end up <div>element - no difference</div>. The Second example is not rendering again the DOM element obviously.
Why is React rendering the First example then? Are those considered different types in this case?
Thank you!
By rendering I assume you mean changes to be "rendered" aka committed to the DOM. The reconciliation process will still be triggered in both examples.
The answer is simple. In your first example you are returning a React component (<First /> or <Second />) whereas in the second example you are returning a React element (one of the two div's).
React cannot know beforehand what each of your two components will do (they could have their own logic), so in the latter case, React will just see that you want to replace First with Second and just re-render. In the former case, you are only returning elements which can be objectively compared.
In addition to #Chris answers, I made a small test approving the answer.
My main consideration was if JSX will generate a new instance although the components may unmounted due to the condition.
import React, { useState, useEffect, useRef } from 'react';
import ReactDOM from 'react-dom';
// You can check either
const Component = <div>element - no difference</div>;
const Contact = () => {
const [renderFirst, setRenderFirst] = useState(true);
const componentRef = useRef();
const first = useRef();
useEffect(() => {
console.log(componentRef.current);
const [child] = componentRef.current.children;
if (!first.current) {
first.current = child;
}
}, []);
useEffect(() => {
const [child] = componentRef.current.children;
console.log(child !== first.current ? 'different' : 'same');
});
return (
<>
<button onClick={() => setRenderFirst(prev => !prev)}>Toggle</button>
<div ref={componentRef}>
{renderFirst ? (
<div>element - no difference</div>
) : (
<div>element - no difference</div>
)}
{/* {renderFirst ? Component : Component} */}
</div>
</>
);
};
ReactDOM.render(<Contact />, document.getElementById('root'));

Categories