How to clone an HTML element in React? - javascript

I want to create a React component that clones an HTML element by its id.
I tried several ways but every time I get an error.
const [element,setElement] = useState()
useEffect(()=>{
setElement(document.querySelector('#svg'))
},[])
return element
Error: Objects are not valid as a React child (found: [object HTMLImageElement]). If you meant to render a collection of children, use an array instead.
const [element,setElement] = useState()
useEffect(()=>{
let el = React.cloneElement(document.querySelector('#svg'))
setElement(el)
},[])
return element
Error: Element type is invalid: expected a string (for built-in components) or a class/function (for composite components) but got: undefined. You likely forgot to export your component from the file it's defined in, or you might have mixed up default and named imports.

Be aware that querySelector() in React is an anti pattern. You should consider to use refs instead. Read more how to use refs in this thread: Using document.querySelector in React? Should I use refs instead? How?
To create a React component that clones an HTML element by its id with querySelector(), you can use React.createElement and pass it the HTML element, its tag name, and its properties as arguments.
Example:
import React, { useEffect, useState } from 'react';
const CloneElement = () => {
const [element, setElement] = useState(null);
useEffect(() => {
const htmlElement = document.querySelector('#svg');
setElement(
React.createElement(htmlElement.tagName, {
...htmlElement.attributes,
children: htmlElement.innerHTML,
})
);
}, []);
return element;
};
export default CloneElement;

Related

Why does my camelCase attributes work well with TypeScript but show an error in JS in React?

I'm working with my own React component library in coded in TypeScript, which I use with React JS projects. When I use my components in TypeScript they work well with their attributes, but in JS I have errors in the console. Exemple:
TS component:
const Button: React.FC<props> = ({ btnStyle, ...otherProps }) => { ...component }
Types declaration:
interface props {
btnStyle?: string,
}
Component used in JS or TS:
<Button btnStyle="plain">Button</Button>
And the error I get:
React does not recognize the `btnStyle` prop on a DOM element. If you intentionally want it to appear in the DOM as a custom attribute, spell it as lowercase `btnstyle` instead. If you accidentally passed it from a parent component, remove it from the DOM element.
How could I use camelCase like in TS in JS?
Thanks!
Okay I found the problem, it's coming from Styled Components, which puts the props on the generated HTML:
<button btnStyle="style">Button</button>
This is how I fixed it:
import React from "react"
interface props {
$btnStyle?: string
}
const Button: React.FC<props> = ({ btnStyle, children, ...otherProps }) => {
return (
<button $btnStyle={btnStyle} {...otherProps}>{children}</button>
)
}
Check here for the documentation

PropTypes validation fails in functional component

I have a functional component below which has props coming from parent component and I added propTypes for the whole props object. However, the lint fails with the below error message.
9:16 error 'data' is missing in props validation react/prop-types
9:22 error 'data.text' is missing in props validation
Excerpt from code
import PropTypes from 'prop-types'
const Child = (props) => <div>{props.data.text}</div>
Child.propTypes = {
props: PropTypes.object
}
Could anyone please help?
As discussed in the comments, you have a few issues:
PropTypes.object is a very vague type declaration, and most linters will ding you for that
You are referencing properties of that object inside of your functional component that are not declared in your proptypes
You are using your prop declaration to attempt to refer to the props argument as a whole, rather than the properties within.
A more correct way to write all of this would be like so:
import PropTypes from 'prop-types';
/* Note we destructure `data` directly from props in the args */
const Child = ({ data }) => (<div>{data.text}</div>);
Child.propTypes = {
data: PropTypes.shape({ // use `shape` to allow us to declare types of properties within
text: PropTypes.string, // define the text property's type
}),
}
Additionally, you may want to define some of these items as required, or provide default entries.
From Alexander Nied solution, I made it work writing the below way without using shape, I think it is more generic.
const Child = ({ data }) => (<div>{data.text}</div>);
Child.propTypes = {
data: PropTypes.object
}

React and typescript ReactRouterProps issue

I need to deconstruct my general props and match (to get an "id" from the URL).
Component (using props):
interface Props extends RouteComponentProps<{id: string}>{
initialProjectName: string;
workspaceId: string;
}
const AddResources: React.FC<Props> = ({
initialProjectName,
workspaceId,
match,
}) => {
const projectId = match.params.id; // used here without any error
But the parent component is showing error when I pass props
Parent
<div>
<h1>Start Project Page</h1>
<AddResources
initialProjectName={initialProjectName}
workspaceId={workspaceId} // error
/>
</div>
Error Message
By using RouteComponentProps you specify that your component requires the route props, but you will also need to make sure you pass these props. If the component is directly underneath a Route (i.e. as a child or by using <Route component={..}/>, the route props are passed automatically, if not, you can use withRouter (docs) to obtain them:
const AddResourcesWithRouter = withRouter(AddResources);
and use AddResourcesWithRouter istead of AddResources.
I think you should try useHistory, useRouteMatch, useLocation or wrap withRouter(AddResources)

How to get access to the underlying component of a react html element?

I'd like to create a <Breadcrumb> component which receives a <Link> component as a dependency.
This could be a <Link> component from react-router-dom or an <a> component.
How can I get direct access to the <a> component so that I may inject it into <Breadcrumb>?
Here is a brief example of what I'm trying to do.
const createBreadcrumbComponent = (Link) => {
return function Breadcrumb(props) {
// return JSX that renders this Link component
// A basic example:
return <Link href="/">{"Home"}</Link>
}
}
An example of using this with react-router-doms own <Link component:
// import {Link} from 'react-router-dom';
const Breadcrumb = createBreadcrumbComponent(Link);
I would like do the same as above, but substitute Link with a. Unfortunately there doesn't seem to be an actual component that represents a (with React.createElement() we pass the string "a" instead).
You can always use ref to access the underlying DOM element from a React Component. But, this is not always the case.
Following are the cases, which will enable you to use ref -
The component is a native HTML element, for eg - a, div, img etc.
The React native component is wrapped with a forwardRef. This doc explains it nicely - https://reactjs.org/docs/forwarding-refs.html
An example for using ref can be seen below -
import React, {useRef} from 'react';
const Comp = () => {
const elRef = useRef();
useEffect(() => {
console.log(elRef.current); // -> the p element
}, []);
return <p ref={elRef}>Hello</p>;
}
There is a third way of getting the underlying element, but I don't recommend it since it is deprecated. You can use ReactDOM.findDOMNode(Component). This is a part of react-dom package.
You can read more about it here - https://reactjs.org/docs/react-dom.html#finddomnode
I think the solution is to create a wrapper around <a>, for instance:
const A = (props) => <a {...props}/>;
const Breadcrumb = createBreadcrumbComponent(A);
I was hoping there would be a way to obtain a component directly from React that already encompasses the <a> element

Dynamic Components: React vs Vue

In React we can add dynamic component this way (I grabbed it from the react docs https://reactjs.org/docs/jsx-in-depth.html):
import React from 'react';
import { PhotoStory, VideoStory } from './stories';
const components = {
photo: PhotoStory,
video: VideoStory
};
function Story(props) {
const SpecificStory = components[props.storyType];
return <SpecificStory story={props.story} />;
}
It is just a function that returns a template with the right component (depends on props)
In Vue we can do the same logic using:
<template>
<component :is="currentComponent"></component>
</template>
and currentComponent will be a computed property(usually) or just a property in a data
My question is: what option is cheaper for the performance and rendering?
if you want to load the component dynamically then you would go for computed property because the property value is dynamic and should be on state and i guess you are using vuex to retrive the data using ...mapGetters on computed property.
Using data property is mainly used for fixed values or declaration

Categories