I have a server response which contains an html string which is passed to a react component as a prop. Within the html there are codes which need to be replaced with React components.
I've used react-string-replace to get this to work but it doesn't seem to work with HTML as the tags are escaped by React. Does anyone know how this might be fixed?
import React from 'react';
import replace from 'react-string-replace';
const reg = /\{(name)\}/g;
const Name = props => {
return (
<div>Hello {props.name}</div>
)
}
class Embed extends React.Component {
render() {
const person = this.props.person
const component = <Name name={person.name} key={person.id} />;
const output = replace(
this.props.content,
reg,
prop => component
);
return (
<div>{output}</div>
)
}
}
const Greeting = props => {
return <Embed content="<div><h1>Greetings</h1> <strong>{name}</strong></div>" person={{ name: 'John', id: 123 }} />;
};
export default Greeting;
Try this -
class Embed extends React.Component {
render() {
const person = this.props.person
const component = <Name name={person.name} key={person.id} />;
const output = replace(
this.props.content,
reg,
prop => component
);
return (
<div dangerouslySetInnerHTML={{ __html: output }}></div>
)
}
}
I suggest much better way to do it like below -
const reg = /\{(name)\}/g;
const Name = props => {
return (
<div>Hello {props.name}</div>
)
}
class Embed extends React.Component {
render() {
const person = this.props.person;
const component = <Name name={person.name} key={person.id} />;
return (
<div><h1>Greetings</h1> <strong>{component}</strong></div>
);
}
}
const Greeting = props => {
return (<Embed person={{ name: 'John', id: 123 }} >
</Embed>);
};
ReactDOM.render(
<Greeting/>,
document.getElementById('test')
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="test">
</div>
Wants to remind you this:
From Facebook React Doc:
dangerouslySetInnerHTML is React's replacement for using innerHTML in
the browser DOM. In general, setting HTML from code is risky because
it's easy to inadvertently expose your users to a cross-site scripting
(XSS) attack. So, you can set HTML directly from React, but you have
to type out dangerouslySetInnerHTML and pass an object with a __html
key, to remind yourself that it's dangerous.
Try this:
const Name = props => {
return (
<div>
Hello {props.name}
</div>
)
}
class Embed extends React.Component {
render() {
let content = this.props.content.replace('{name}', this.props.person.name);
return (
<div>
<Name person={this.props.person}/>
<div dangerouslySetInnerHTML={{ __html: content}}></div>
</div>
)
}
}
const Greeting = props => {
return <Embed content="<div><h1>Greetings</h1> <strong>{name}</strong></div>" person={{ name: 'John', id: 123 }} />;
};
ReactDOM.render(<Greeting />, document.getElementById('container'));
Check working example on jsfiddle: https://jsfiddle.net/h81q9nqd/
Related
I am using React + Typescript. I am working on a component that can return dynamic HTML element depends on props:
interface Props {
label?: string;
}
const DynamicComponent = forwardRef<
HTMLButtonElement | HTMLLabelElement,
Props
>((props, ref) => {
if (props.label) {
return (
<label ref={ref as React.ForwardedRef<HTMLLabelElement>}>
{props.label}
</label>
);
}
return (
<button ref={ref as React.ForwardedRef<HTMLButtonElement>}>BUTTON</button>
);
});
Is it possible to type ref's interface in a way that will depend on the label prop?
export default function App() {
const btnRef = useRef<HTMLButtonElement>(null); // Allowed
const labelRef = useRef<HTMLLabelElement>(null); // Allowed
// const labelRef = useRef<HTMLButtonElement>(null); // Not allowed, because `label` prop was not provided
return (
<>
<DynamicComponent ref={btnRef} />
<DynamicComponent ref={labelRef} label="my label" />
</>
);
}
Sandbox link
In order to do that we need to use function overloading, with higher order function pattern and typeguards:
FIXED
import React, { forwardRef, useRef, Ref } from 'react'
interface Props {
label?: string;
}
// typeguard
const isLabelRef = (props: Props, ref: React.ForwardedRef<any>): ref is React.ForwardedRef<HTMLLabelElement> => {
return true // ! NON IMPLEMENTED
}
// typeguard
const isButtonRef = (props: Props, ref: React.ForwardedRef<any>): ref is React.ForwardedRef<HTMLButtonElement> => {
return true // ! NON IMPLEMENTED
}
// Higher order COmponent with overloads
function DynamicComponent<T extends HTMLButtonElement>(reference: Ref<T>): any
function DynamicComponent<T extends HTMLLabelElement>(reference: Ref<T>, props: Props): any
function DynamicComponent<T extends HTMLElement>(reference: Ref<T> | undefined, props?: Props) {
const WithRef = forwardRef<HTMLElement, Props>((_, ref) => {
if (props && isLabelRef(props, ref)) {
return (
<label ref={ref}>
{props.label}
</label>
);
}
if (props && isButtonRef(props, ref)) {
return (
<button ref={ref}>BUTTON</button>
);
}
return null
});
return <WithRef ref={reference} />
}
export default function App() {
const btnRef = useRef<HTMLButtonElement>(null); // Allowed
const labelRef = useRef<HTMLLabelElement>(null); // Allowed
return (
<>
{DynamicComponent(btnRef)}
{DynamicComponent(labelRef, { label: 'sdf' })}
</>
);
}
Playground
As you might have noticed, I use only props from DynamicComponent.
Also T generic parameter serves for narrowing the ref type
I left isButtonRef and isLabelRef unimplemented
UPDATE
Seems that my previous example is useless. Sorry for that.
My bad. I have already fixed it.
As an alternative solution, you can override built in forwardRef function.
interface Props {
label: string;
}
declare module "react" {
function forwardRef<T extends HTMLButtonElement, P>(
render: ForwardRefRenderFunction<HTMLButtonElement, never>
): ForwardRefExoticComponent<
PropsWithRef<{ some: number }> & RefAttributes<HTMLButtonElement>
>;
function forwardRef<T extends HTMLLabelElement, P extends { label: string }>(
render: ForwardRefRenderFunction<HTMLLabelElement, { label: string }>
): ForwardRefExoticComponent<
PropsWithRef<{ label: string }> & RefAttributes<HTMLLabelElement>
>;
}
const WithLabelRef = forwardRef<HTMLLabelElement, Props>((props, ref) => (
<label ref={ref}>{props.label}</label>
));
const WithButtonRef = forwardRef<HTMLButtonElement>((props, ref) => (
<button ref={ref}>{props}</button>
));
function App() {
const btnRef = useRef<HTMLButtonElement>(null);
const labelRef = useRef<HTMLLabelElement>(null);
const divRef = useRef<HTMLDivElement>(null);
return (
<>
<WithButtonRef ref={btnRef} />
<WithLabelRef ref={labelRef} label="my label" />
<WithLabelRef ref={divRef} label="my label" /> //expected error
</>
);
}
I have a functional element in react js like this,
function FilterOptions() {
const [isShown, setIsShown] = useState(false);
return (
<div className="filter__options">
{["Category", "Design", "Size", "Style"].map((ourOption) => (
<div
onMouseEnter={() => setIsShown(true)}
onMouseLeave={() => setIsShown(false)}
className="filter__options__container"
>
<div className="filter__options__button">
{ourOption}
</div>
{isShown && <div className="filter__options__content"> Here I want to return the element using props </div>}
</div>
))}
</div>
);
}
I have created a files called, Category.js, Design.js, Size.js, Style.js.
Now I want to use the props so that I can concatenate like this <{ourOption}> <{ourOption}/> so that this will return element.
Any idea how to do this guys?
Choosing the Type at Runtime
First: Import the components used and create a lookup object
import Category from 'Category';
import Design from 'Design';
import Size from 'Size';
import Style from 'Style';
// ... other imports
const components = {
Category,
Design,
Size,
Style,
// ... other mappings
};
Second: Lookup the component to be rendered
function FilterOptions() {
const [isShown, setIsShown] = useState(false);
return (
<div className="filter__options">
{["Category", "Design", "Size", "Style"].map((ourOption) => {
const Component = components[ourOption];
return (
...
<div className="filter__options__button">
<Component />
</div>
...
))}}
</div>
);
}
Alternatively you can just import and specify them directly in the array to be mapped.
function FilterOptions() {
const [isShown, setIsShown] = useState(false);
return (
<div className="filter__options">
{[Category, Design, Size, Style].map((Component) => (
...
<div className="filter__options__button">
<Component />
</div>
...
))}
</div>
);
}
Instead of strings you could iterate over Array of Components
{[Category, Design, Size, Style].map((Component) => (
<Component/>
);
Ill do this as react document
//create components array
const components = {
photo: Category,
video: Design
.....
};
{
Object.keys(components).map((compName) => {
const SpecificSection = components[compName];
return <SpecificSection />;
})
}
Here is a small sample code that you can work with. Use direct component instead of trying to determine by strings.
const Comp1 = () => {
return <p>Comp1 Here</p>
}
const Comp2 = () => {
return <p>Comp 2 Here</p>
}
export default function App() {
return (
<div className="App">
{[Comp1, Comp2].map(Komponent => {
// use Komponent to prevent overriding Component
return <Komponent></Komponent>
})}
</div>
);
}
I am trying to return a component without a set name at runtime. Like so:
<div className="project-demo">
<CustomComponent demo={project.demo}/>
</div>
and being called like this:
const CustomComponent = ({ demo }) => {
return (
<{ demo } />
)
}
Any advice?
JSX expects components to have capitalized names
const CustomComponent = ({ demo }) => {
const Demo = demo;
return (
<Demo />
)
}
or better:
const CustomComponent = ({ Demo }) => {
return (
<Demo />
)
}
The title is pretty straightforward, I need to access a property (a ref to be precise) on a child element that is passed through the children of my component, which means that I can't pass the ref in the parent afaik.
Here's a minimal example to highlight my issue:
import React from "react";
class Child extends React.Component {
myRef = React.createRef();
render() {
return <div ref={this.myRef}>child</div>;
}
}
const Parent = ({ children }) => {
const myChild = React.Children.toArray(children).find(
child => child.type === Child
);
// I want to access this
console.log(myChild.myRef);
// but it's undefined
return (
<div>
<h1>Parent</h1>
{children}
</div>
);
};
// I can't really change this component
export default function App() {
return (
<div className="App">
<Parent>
<Child />
</Parent>
</div>
);
}
I made a codesandbox highlighting my issue https://codesandbox.io/s/eloquent-wing-e0ejh?file=/src/App.js
Rather than declaring ref in <Child/>, you should declare ref in your <Parent/> and pass it to the child.
import React from "react";
class Child extends React.Component {
render() {
return <div ref={this.props.myRef}>child</div>;
}
}
const Parent = ({ children }) => {
const myRef = React.useRef(null);
// access it from here or do other thing
console.log(myRef);
return (
<div>
<h1>Parent</h1>
{ children(myRef) }
</div>
);
};
export default function App() {
return (
<div className="App">
<Parent>
{myRef => (
<Child myRef={myRef} />
)}
</Parent>
</div>
);
}
I am at the very beginning of creating an icon picker for sanity.io with react icons npm package. I am stuck on trying to map over an object and returning the right code for react to work using Object.values(ReactIcons).map... if I just console log one of the objects values like so ReactIcons.Fa500Px I get the following function
ƒ (props) {
return Object(__WEBPACK_IMPORTED_MODULE_0__lib__["a" /* GenIcon */])({"tag":"svg","attr":{"viewBox":"0 0 448 512"},"child":[{"tag":"path","attr":{"d":"M103.3 344.3c-6.5-14.2-6.9-18.3 7.4-…
Now If I take the same code form the console.log and put it in a jsx or React component brackets like so <ReactIcons.Fa500Px /> it renders the icon just fine
However if I try to do that inside the map method with something like this I just get a bunch of elements in the dom the look like <x></x>. However the console.log(x) returns a series of the functions that are the same format as the one above that I just placed inside brackets before, which resulted in a icon being rendered.
{Object.values(ReactIcons).map(x =>{
return (
<>
{console.log(x)}
<x/>
</>
);
})}
My final attempt to get this to work was to create a Icon function and pass props into it and render it as a component. Which did not work but here is that attempt.
function Icon(props){
return(
<>
{props.value}
</>
)
}
{Object.values(ReactIcons).map(x =>{
return (
<>
{console.log(x)}
<Icon value={x}/>
</>
);
})}
Here's the entire code base in order just to make sure maybe I am putting my Icon function in the wrong place.
import React from 'react'
import PropTypes from 'prop-types'
import FormField from 'part:#sanity/components/formfields/default'
import PatchEvent, {set, unset} from 'part:#sanity/form-builder/patch-event'
import * as ReactIcons from 'react-icons/fa'
console.log(ReactIcons);
const createPatchFrom = value => PatchEvent.from(value === '' ? unset() : set(String(value)))
function Icon(props){
return(
<>
{props.value}
</>
)
}
class IconPickerCustom extends React.Component{
static propTypes = {
value: PropTypes.string,
onChange: PropTypes.func.isRequired
};
render = () =>{
const {type, value, onChange} = this.props
return (
<>
<FormField label={type.title} description={type.description}>
<input
type="text"
value={value === undefined ? '' : value}
onChange={event => onChange(createPatchFrom(event.target.value))}
ref={element => this._inputElement = element}
/>
</FormField>
{Object.values(ReactIcons).map(x =>{
return (
<>
{console.log(x)} // has same result as console log below, except it is all the icons
<Icon value={x}/> //neithr works
<x /> //neither works
</>
);
})}
{console.log(ReactIcons.Fa500Px)}
<ReactIcons.Fa500Px/>
</>
)
}
}
export default IconPickerCustom;
I guess that you would like to loop in the object key instead
{Object.keys(ReactIcons).map(x =>{
let Elm = ReactIcons[x]
return (
<Elm />
);
})}
I'm just guessing I'm not sure
I got help form the sanity slack, and this ended up solving my problem
import React, { useState } from 'react';
import PropTypes from 'prop-types'
import FormField from 'part:#sanity/components/formfields/default'
import PatchEvent, {set, unset} from 'part:#sanity/form-builder/patch-event'
import * as ReactIcons from 'react-icons/fa'
// const createPatchFrom = value => PatchEvent.from(value === '' ? unset() : set(String(value)))
const Icon =({name}) =>{
const TagName = ReactIcons[name];
return !!TagName ? <TagName /> : <p>{name}</p>;
}
const Box = ({icon}) =>{
return(
<>
{icon.map((iconsz) => {
return(
<>
<button>
<Icon name={iconsz}/>
</button>
</>
)
}
)}
</>
)
}
class IconPickerCustom extends React.Component{
constructor(props){
super(props)
const arr = [];
Object.keys(ReactIcons).map(go => {
arr.push(go);
this.state = {icon: arr};
}
)
this.handleChange = this.handleChange.bind(this)
}
// createPatchFrom = value => PatchEvent.from(value === '' ? unset() : set(String(value)));
handleChange = event => {
const value = event;
const arr = [];
Object.keys(ReactIcons).map(go =>
{
if(go.toLowerCase().includes(value)){
arr.push(go);
}
this.setState({
icon: arr
});
}
)
};
render = () =>{
const {icon} = this.state
return (
<>
<input
type="text"
onChange={event => this.handleChange(event.target.value)}
/>
<Box icon={icon}/>
</>
)
}
}
export default IconPickerCustom;