react - render dynamic component insider render function of other component - javascript

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

Related

i18next <Trans> avoid retranslation of nested components

I am using i18next with React for my project. For most of the translations I use the t() function with normal text. However, for some cases I would prefer to use the <Trans /> component.
I prepared some example below:
function Example() {
const { t } = useTranslation();
const reusableComponent = <p>{t('TranslateMeOnlyOnce')}</p>;
return (
<>
{reusableComponent}
<Trans i18nKey={'OnlyOnceWrapper'}>
The reusable component says {reusableComponent}
</Trans>
</>
)
}
Which expect would my ranslation file to look something like this:
...
TranslateMeOnlyOnce: 'Translate me only once',
OnlyOnceWrapper: 'The reusable component says <1>Translate me only once</1>',
...
However, as you can see I would be required to translate the text of the nested component twice. I would really like to just translate the The reusable component says {reusableComponent} in the second translation string and not all of the first translation string for another time.
For those wondering why I would want to do this, lets say const reusableComponent is a Create Button with some function to create a new entry placed at the top of the Screen. When however, there is no entry displayed in my list of entries I want to display a message saying something like: 'Unfortunately there is no entry yet. Click on [CreateButtonIsDisplayedHere] to create a new entry'.
Let's say I experience that users find a translation 'New' instead of 'Create' more useful or the other way round, I would want to change only the translation of the button and not every other place containing this button.
I also found myself a solution to this issue already, however, in my experience this is super ugly to maintain as I need to pass the content as string and not as React child Element which pretty much take all advantages of using the <Trans /> Component and I could better use the t() function using sprintf.
function UglySolution() {
const { t } = useTranslation();
const reusableComponent = <p>{t('Translate me only Once')}</p>;
return (
<>
{reusableComponent}
<Trans
i18nKey={'OnlyOnceWrapper'}
components={[reusableComponent]}
variables={{ v: t('Translate me only Once') }}
defaults='The reusable component says <1>{{v}}</1>'
/>
</>
)
}
Which would expect my translation file to look something like this:
...
TranslateMeOnlyOnce: 'Translate me only once',
OnlyOnceWrapper: 'The reusable component says <1>{{v}}</1>',
...
So my question is: Can I make my translation file to look something like the second example I created without using the ugly <Trans /> component from the second example and more something like the component in the first example I created?
Resusing translations
There is a way using the <Trans /> component as in the first example with reusing previous translations. Using $t(KeyToAnotherTranslation) inside the translation string.
This does not fully solve the issue as it's also not too handy to maintain but would be a lot prettier than the second example shown.
function Example() {
const { t } = useTranslation();
const reusableComponent = <p>{t('TranslateMeOnlyOnce')}</p>;
return (
<>
{reusableComponent}
<Trans i18nKey={'OnlyOnceWrapper'}>
The reusable component says {reusableComponent}
</Trans>
</>
)
}
(Taken 1:1 from the code from the question)
With a translation file looking something like this:
...
TranslateMeOnlyOnce: 'Translate me only once',
OnlyOnceWrapper: 'The reusable component says <1>$t(TranslateMeOnlyOnce)</1>',
...
More information about variable usage in i18next.

React Hooks -- Uncaught Invariant Violation: Objects are not valid as a React child

I'm working on the freeCodeCamp drum machine app. In my app with function arrow components, I set state of display with the useState hook in the parent component and pass it as a prop to the child component. In the parent component, I try to render the display state in a div. However, when the method is triggered (on click of the "drum pad" div), the app crashes. In the console I get an error that says "Uncaught Invariant Violation: Objects are not valid as a React child (found: object with keys {display}). If you meant to render a collection of children, use an array instead."
I've been following along a YouTube tutorial for this project but using arrow function components and Hooks instead of regular classes as used in the tutorial--in the tutorial (around 1:55 of this video) the person successfully does what I'm trying to do, so I think the issue is something to do with using Hooks or arrow function components.
// APP COMPONENT (PARENT)
const sounds = [
{ id: 'snare', letter: 'Q', src: 'https://www.myinstants.com/media/sounds/snare.mp3' },
// etc.
];
const App = () => {
const [display, setDisplay] = useState(''); // <----
const handleDisplay = display => { // <----
setDisplay({ display });
}
return (
<div className="App">
<div className="drum-machine">
<div className="display">
<p>{display}</p> // <---- Related to error in console
</div>
<div className="drum-pads">
{sounds.map(sound => (
<DrumPad
id={sound.id}
letter={sound.letter}
src={sound.src}
handleDisplay={handleDisplay} // <----
/>
))}
</div>
</div>
</div>
);
}
// DRUMPAD COMPONENT (CHILD)
const DrumPad = ({ id, letter, src, handleDisplay }) => {
let audio = React.createRef();
const handleClick = () => {
audio.current.play();
audio.current.currentTime = 0;
handleDisplay(id); // <----
}
return (
<div
className="drum-pad"
id={id}
onClick={handleClick}
>
<p className="letter">{letter}</p>
<audio
ref={audio}
id={letter}
src={src}
>
</audio>
</div>
);
}
You're setting the state as an object instead of a string. Remove the curly brackets around it.
const handleDisplay = display => {
setDisplay(display);
}
This was already answered, but since you are following a tutorial, I am assuming you are learning React and wanted to point a couple of things to help you :)
The incorrect use of state was pointed out, but just for clarification (and the reason I think you were using an object): in the "old" way, with Class components, the state used to be an object, and you needed to update it like an object. This example here shows that. With Hooks, you don't need to set the whole State object, only that specific state property. More info here.
Another point is, in your CodePen example at least, you were missing the import for useState. You either need to import it like this import { useState } from React or use it like this React.useState, since this is a separate module, not imported by default when you import React.
The last point is, when creating components using a loop (like your <DrumPad> with the map) you need to provide a "key" attribute. that will help React keep track of things that needs to be updated or rerendered.
O updated your code with those changes in this link, if you wanna see it working:
https://codesandbox.io/s/reverent-browser-zkum2
Good luck and hope you are enjoying React Hooks :)

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.

Calling dynamic component in React JS

I am new in react JS. I am calling a react component from a HTML string which is coming from another javascript class. But the component is not rendering on the screen.
class Form extends Component {
render() {
var markup = '<Sections />';
return (<div className="content" dangerouslySetInnerHTML={{__html: markup}}></div>);
}
}
class Sections extends Component{
render(){
return (<div className='row'>Row1</div>);
}
}
Please help me where am i going wrong.
I think the reactjs is not recognizing the sections component as it is coming from the string. Is there any way, we can manually compile the jsx.
Remember that JSX isn't the same as HTML. When you write something like <Sections /> you are in fact writing React.createElement(Sections, null).
If you have "<Sections />" as a string and you want to resolve it to a call to React.createElement then you need to parse it as JSX. However, this happens at compile-time (not run-time) which would prevent you from using dynamic components.
If you want to work with a dynamic component, then your builder method must return a component class or a component instance.
function getDynamicComponent() {
return Sections;
}
function render() {
const Component = getDynamicComponent();
return <Component />;
}
// or
function getDynamicComponentInstance() {
return <Sections />;
}
function render() {
const component = getDynamicComponentInstance();
return component;
}
The only way to infer the correct component from "<Sections />" is to parse it manually and use eval to resolve the name to the appropriate variable. Parsing JSX is non-trivial and using eval is likely to open you up to a whole class of security vulnerabilities.
Unless your dynamic component name is coming from over the network, or from inside a separate worker, there's no reason why the function which returns your dynamic component can't be written like the example above.

What actually happens when React component returns?

I have noticed a difference between the data before returning and after a return of a component.
class AComponent extends Component {
render() {
const body = <BComponent crmStatus={...}/>
debugger // log body on the right
// ... render as static html to electron window
return false
}
}
class BComponent extends Component {
render() {
const resultRender = <article className='large'>...</article>
debugger // log resultRender on the left
return resultRender
}
}
My former question was going to be "How to read rendered component's className?", but I have split the questions as answering what is actually happening and why is it like that really started to bug me and might even give me hints to solve my problem.
So the question is:
What is actually happening to the component and why is it like that? I can have really complicated logic in my render() function, but I guess working with the components isn't that easy.
const headerContact = isContactInCRM ? <p>..</p> : <div>..</div>
const headerCallBtnsOrInfo = isSipEnabled && <div>..buttons..</div>
const callTimer = callDuration && <span>{callDuration}</span>
const footerNotes = <footer>..</footer>
const someImportedComponent = <MyComponent />
const resultRender = <section>
{headerContact}
{headerCallBtnsOrInfo}
{callTimer}
{footerNotes}
{someImportedComponent}
</section>
// there is a difference in data between headerContact and someImportedComponent
// when traversing the resultRender's tree in console
Before answering the question, it's worth to look at what is JSX. It just provides syntactic sugar for the React.createElement(component, props, ...children) function.
<div>
<MyComponent/>
</div>
As an example, above JSX snippet will be transformed to following JavaScript code in the compilation process.
React.createElement(
"div",
null,
React.createElement(MyComponent, null)
);
You can try out this using Babel online repl tool. So if we rewrite your example code using normal JavaScript (after compiling JSX), it will be something like this.
class AComponent extends Component {
render() {
const body = React.createElement(BComponent, { crmStatus: '...' });
debugger // log body on the right
// ... render as static html to electron window
return false
}
}
class BComponent extends Component {
render() {
const resultRender = React.createElement('article',{ className: 'large' }, '...' );
debugger // log resultRender on the left
return resultRender
}
}
By looking at above code, we can understand that <BComponent crmStatus={...}/> doesn't create a new object of BComponent class or call render method of BComponent. It just create a ReactElement with BComponent type and crmStatus prop. So what is a ReactElement? ReactElement is a pain JavaScript object with some properties. I recommend you to read this post from official React blog to get an in-depth understanding of React components, elements, and instances.
An element is a plain object describing a component instance or DOM node and its desired properties. It contains only information about
the component type (for example, a Button), its properties (for
example, its color), and any child elements inside it.
Basically, what you have printed in the console is two React elements in different types. The left one is describing DOM node with type 'article' and the right one is describing BComponent type React component instance. So simply you can't expect them to be the same.
Then where does React create an instance of BComponent? Actually, this happens internally in the React code. Usually, we don't have access to these instances or what return by their render methods in our application code.
However, React still provide an escape hatch called 'refs' which you can explicitly access instances of child components. You might be able to use that approach to solve your original problem.
Hope this helps!

Categories