React Js - Create component from String doesn't work - javascript

I want to create a list of component using strings.
Searching on Google/StackOverflow, this seems to be easy:
import Light from 'light.jsx';
[...]
getComponent()
{
let DynamicComponent = "Light";
return <DynamicComponent />;
}
Unfortunally, this approach doesn't work.
I get this error non console:
Warning: <Light /> is using uppercase HTML. Always use lowercase HTML
tags in React. Warning: The tag <Light> is unrecognized in this
browser. If you meant to render a React component, start its name with
an uppercase letter.
I've tried many combinations like:
return React.createElement(DynamicComponent, {}) };
return React.createElement("Light", {}) };
return <div> { React.createElement(DynamicComponent, {}) }</div>;
They all returns the same error.
Obviously if I use:
return <Light />
it works.

As you mentioned in the comment, you are putting all the import in a file like this:
import Light from './light.jsx';
import Fan from './fan.jsx';
export {
"Light": Light,
"Fan": Fan
}
And trying to use:
import * as Dynamic from 'allimport.js';
let DynamicComponent = "Dynamic.Light";
return <DynamicComponent />
Everything is proper, just remove the "" around "Dynamic.Light", it should be Dynamic.Light.
For more details check this answer: Dynamically rendering component from string: ReactJS

Sorry to bother you but I still get some errors:
(1) The import you wrote rise this error: "Unexpected token" at the first DoubleQuotes. All the export statement is red-underlined by IDE;
(2) I can't remove the doublequotes from Dynamic.Light because this assume I KNOW that a Light component exists.
But all this stuffs Im trying to make working is to prevent the developer to know them. I receive a list of what to render from DB managing this information in a loop. I need to be more flexible.
listOfComponentFromDB.map(name){
let DynamicComponent = "Dynamic."+name;
allRendered.push(<DynamicComponent />);
}
return allRendered;
With this approach to add a new component to the master render, it will be non necessary to modify the master render because you will have only to put the ne fan.jsx in the directory, add it to the import and INSERT the new row on the DB.

Related

How do I generate a stable unique ID in React with SSR?

I'm using SSR in Next.js. I'm trying to generate a unique ID in a component to use in a DOM element's id attribute. This component may be used multiple times on a page so each component instance needs its id to be unique. However, doing something like the following on each render of the component results in a server/client mismatch (component creates new ID on each render, so server and client do not match):
const gradientId = `linear-gradient-${uuid().slice(0, 8)}`
Stuff I've tried:
generating the ID as a default useState value
generating the ID right outside the component (same file, just above component definition)
generating the ID inside a useMemo
All seem to suffer from the client/server mismatch issue. Is there a good way to do this? Is there something stable in the component instance I can base the ID on instead (maybe React generates one I can use?).
I should also note I’m stuck on React 17 so I don’t have access to React 18’s useId, which seems aimed at solving this exact problem!
Any ideas appreciated - thanks!
Well, there may be a better solution out there, but the key appeared to be in abandoning a random ID in favour of a more deterministic ID like an index.
Outside of my component (in the same file, just above the component definition), I can define a function to generate the ID and I can seed it with 0.
let sequentialId = 0
const getSequentialId = function() {
return sequentialId++
}
function MyComponent({ ...otherProps }) {
...etc...
}
then, in my component, I can simply call this instead of the uuid() function I was previously calling:
const gradientId = `linear-gradient-${getSequentialId()}`
This forces the server render to generate 0, 1, 2, etc. and then when the client runs (remember client instances are brand new component instances), it will also start at 0 and sequentially assign IDs in the same order as the server.
I’ve gone one step further and moved this function to a provider where it can be called from any component in case I need this again:
const PageSettingsContext = React.createContext({
_sequentialId: 0,
getSequentialId: function() {
return this._sequentialId++
},
})
function usePageSettings() {
return useContext(PageSettingsContext);
}
function PageSettingsProvider({ ...otherProps }) {
return (
<PageSettingsContext.Provider>
<App />
</PageSettingsContext.Provider>
);
}
export {
PageSettingsProvider,
usePageSettings,
}
This seems to clear up the error and hopefully this holds up over time. Any other ideas or insights welcome but think this works for me.

this.state.persons.map is not a function but persons is already an array

It's my first time working with React and I'm having some trouble with starting to use Axios. I watched a video, a very simple practical tutorial that showed how to use a get function, but I think something went wrong because even following the same exact steps I still get the error "this.state.persons.map is not a function". I want to stress the fact that the author of the video uses this exact same JavaScript code, and for him it works. Any explanation?
Here's the whole code for reference:
import React from "react";
import axios from "axios";
export default class personList extends React.Component{
state = {
persons: [],
};
componentDidMount(){
axios.get(`https://jsonplaceholder.typicode.com`)
.then(res =>{
console.log(res);
this.setState({persons: res.data});
});
}
render() {
return (
<ul>
{this.state.persons.map(person => <li key={person.id}>{person.name}</li>)}
</ul>
)
}
}
I looked around for an answer, but every other case that has been presented is either too different (using set arrays, json and whatnot) or it refers to a string used instead of an array, which causes the error, and obviously it's not my case.
You are making a GET request at https://jsonplaceholder.typicode.com which returns the whole webpage. If you want to fetch the users, use this URL instead: https://jsonplaceholder.typicode.com/users

Dynamic tag name

I'm trying to convert a dynamic tag naming system from React into a more hook like approach, what I'm doing is first I import and export a few components so that I can pull them all at once on a array, like this:
import Component1 from './Component1/Component1'
import Component2 from './Component2/Component2'
export {
Component1,
Component2
}
And then I load them like so:
import { useState, useEffect } from "react";
import * as Components from './../AllComponents/Components'
function importHook() {
const [states, setStates] = useState({
components: [],
currentComponent: 0
})
useEffect(() => {
Object.entries(Components).forEach(component => {
setStates(prevState => ({ components: [...prevState.components, component[1]]}))
})
}, []) // Look's into Components and adds every component to the 'components' array so that I can use that array in the next step.
return states;
}
export default importHook
And after that I proceed to do so:
import React from 'react'
import './MainComponent.scss'
import importHook from '../../importHook'
function MainComponent() {
const { components, currentComponent } = importHook() // Pull and set the values using the hook
const TagName = components[currentComponent] // Created the tag name by targeting the first function, seeing as currentComponent should be 0 at the time
console.log('Tag: ' + TagName) // undefined
return (
<div className="h-100">
<TagName /> // crashes everything
</div>
)
}
export default MainComponent
So I have figured out that the reason that <TagName /> crashes everything is because something runs twice.
If you remove <TagName /> so that you can get the output from console.log and change currentComponent inside the const TagName to 0, you notice that the first time console.log runs is returns undefined, while the second run returns the actual function that is stored inside the array.
So really my question is just, why / what executes twice? I have an idea, I'm assuming its because of the forEach that's adding to the array or something like that, but I'm not completely sure.
What can be done so that we can have all the values ready before returning here? I haven't tried this yet, but I assume I could introduce a if statement that would check and see if the variable is empty and if so, display some kind of loading screen or fidget, but this doesn't seem like the best solution to me, I know there are a lot of ways to do something but not all are good and since I'm really new to all of this its better to read / ask.
About the read part, couldn't find much about solving this or the implementation that I've mentioned, I have tried useEffect, useCallback and other such, not sure if I've been doing it wrong though...

How to conditionally return a component in React/Native?

I've used a tool to create React-native components from .svg files. It works well and I can load them using that way.
However, how do I conditionally load them?
I've imported some company logos as such:
import Tesla from '../../static/brandlogos/svg/Tesla'
import Amazon from '../../static/brandlogos/svg/Amazon'
import Google from '../../static/brandlogos/svg/Google'
import Facebook from '../../static/brandlogos/svg/Facebook'
import Apple from '../../static/brandlogos/svg/Apple'
Indeed, if I invoke the component as such, it works:
<Amazon />
However, I wish to(of course) conditionally load a component depending on the props this component receives. So, I create a function:
renderLogo (brandName) {
const Logos = {
Amazon,
Facebook,
Tesla,
Google,
Apple
};
if (Logos[brandName]) {
return <Amazon /> // This works!
}
if (Logos[brandName]) {
return Logos[brandName] // This doesn't!
}
if (Logos[brandName]) {
return Logos.Amazon // This also doesn't!
}
}
However, I simply cannot figure out how to create either a map or array to loop through, and render the specific component. If I straight up return the component, of course it works.
But how do I save each "Logo" in an array or map, and conditionally load + return this logo only?
I could, of course, hard code everything but that would be bad.
Simply do like this
if (Logos[brandName]) {
// Keep in mind the first letter should be capital (i.e. "C" in this case)
let Comp = Logos[brandName]
return <Comp />
}
I think, this post on Dynamic Component Names with JSX answers your question nicely:
components = {
foo: FooComponent,
bar: BarComponent
};
render() {
const TagName = this.components[this.props.tag || 'foo'];
return <TagName />
}
In this example you have your tag from the prop - the most typical case for many components.

react - render dynamic component insider render function of other component

I am trying to develop a web app using react and i have a issue.
my component get a 'exists component name' and I try to render this new component inside render function of the current component.
my current component render function
render(){
let Xxx = null;
if( this.props.onHex ){
console.log( this.props.onHex );
Xxx = <this.props.onHex />
}
return(
<div className="myClass">
<div className="anotherClass">
{Xxx}
</div>
</div>
);
}
}
it not works for me, the console log returns the name of the new component "Unit". when I replace the Xxx = <this.props.onHex /> with this Xxx = <Unit /> it works and render the Unit's render function.
it looks like react not recognise <Unit/> as component.
what I am doing wrong please advise.
my Unit code:
export default class Unit extends Component{
render(){
return(
<div>test</div>
);
}
}
UPDATE:
when I use const XxxName = Unit; Xxx = <XxxName />; it works for me but I want to be able to render the component from string ( I got this string from json ).
I guess I can create all my possible components at this situation inside a file load them into array or something and get them by string, but it's not something that can live with I have a lot of components maybe if I will some how load them from separate folder ( individual file for each component ) it will be half solution. but I still looking how to load component from string.
jsFiddle with another similar issue http://jsfiddle.net/dhjxu5oL/
UPDATE 2:
I am not found elegant way to reach my goal (I don't sure if it exists) for now I am using method for each dynamic component for hope that someone will advise me with more elegant solution. check it: React / JSX Dynamic Component Name
newExampleComponent() {
return <ExampleComponent />;
}
newComponent(type) {
return this["new" + type + "Component"]();
}
let Xxx = null;
if( this.props.onHex ){
const XxxName = this.props.onHex;
Xxx = <XxxName />;
}
Check this jsfiddle for example
UPDATE:
According to React official docs
You cannot use a general expression as the React element type. If you
do want to use a general expression to indicate the type of the
element, just assign it to a capitalized variable first. This often
comes up when you want to render a different component based on a
prop:
So you need to assign this.props.onHex to a CAPITALIZED variable first then you should be able to use it.
UPDATE again
Seems you want to pass a string, not a reference to the component. There is a dirty way to do that
const xxx = this.props.onHex || "";
const XxxComp = eval(xxx);
...
return (<XxxComp />);
I created this codepen for testing

Categories