Reactjs how to convert string to [object Object] - javascript

I have a menuItem component and its props is a Icon component created by me and called FontIcon.
And in menuItem props you can pass a name of the icon as a string, for example: leftIcon="face" but you can also pass a code for the component like this: leftIcon='<FontIcon style={{color:"red"}} className="face" />'.
In the first case, all works perfectly, the props is passed to the variable where is code for the component:
leftIcon = <FontIcon size={_props.fontSize} className={_props.leftIcon} />;
But this second way is not working. When the user passes all code I need to add something to this(this size value like above), at this point this adding works:
leftIcon = _props.leftIcon.replace(/\/>$/, " size={_props.fontSize}/>");
Here I've got if to check what has the user passed:
if (_props.leftIcon.match(/<.+\/>/)) {
leftIcon = _props.leftIcon.replace(/\/>$/, " size={_props.fontSize}/>");
} else {
leftIcon = <FontIcon size={_props.fontSize} className={_props.leftIcon} />;
}
But in this second way, I'm getting a string and it doesn't work. There is no icon, but there is a string with code:
So I've consoled log this and it's what I got:
The typeof this first is object but this second one is string.
So what can do to make this second way works?

If this code is in the render portion this should work;
if (_props.leftIcon.match(/<.+\/>/)) {
leftIcon = _props.leftIcon.replace(/\/>$/, " size={_props.fontSize}/>");
} else {
leftIcon = (<FontIcon size={_props.fontSize} className={_props.leftIcon} />);
}
Note the parenthesis.

There's a way to create component from string you can take a look on this answer
But I would suggest you to pass styles and / or class names and other parameters to your component rather then a string and then you could just have:
return <FontIcon class={condition ? 'firstClass' : 'secondClass'} ... />;

Related

Is there a way to find out if a JS function return JSX or not?

I have a dynamic list. Developers can either provide a URL or a component for that list. Both of them are basically functions:
<List
{...otherProps}
create={params => `create_a_dynamic_url`}
/>
<List
{...otherProps}
create={Form}
/>
and in my List.jsx I have a create prop that is a function.
I should either directly render it using {create} or call it first and then navigate the user to that URL.
For both of them, the typeof operator returns function. Is there a way to find out if their return value is JSX or not?
It is not reliably possible to check if the function is a React component,
but it might help you to check for the React element (which is easy).
Check the React element
You can call the function yourself, assign the return value to a variable, and check if that is a React element.
function App(){
return <>
<List id={'A'} create={ props => <>(My React component)</> } />
<List id={'B'} create={ params => `create_a_dynamic_url` } />
</>;
}
export const List = props => {
const { create, id } = props;
// -- Check for the React components `.name`:
console.log('(List.js) create.name:', id, create?.name);
// --
// Create either a ReactElement from the ReactComponent,
// or the string from the string constructor function:
// --
const myElementOrString = create();
// -- Check for string or ReactElement:
const isString = typeof myElementOrString === 'string';
const isReactElement = !isString;
// --
return <>
<p>{ id }</p>
<div>
<b>The React element is:</b>
{ isReactElement ? myElementOrString : '(none)' }
</div>
<p>
<b>The string is:</b>
{ isString ? '"' + myElementOrString + '"': '(none)' }
</p>
</>;
};
Alternatively you could also check for myElementOrString.hasOwnProperty('$$typeof') or other React specific properties,
but I would be careful with that, because these (or some of them) are "implementation details" you should not rely on.
The .name property
Also note that the function received by your List component via the create property
will have a .name property, which will be different depending on some circumstances, e.g.:
an anonymous function will have the name "create" (the property name)
a named component will have the name of the component
a named function will have the name of the function
... ?
You might be able to check the .name property, but I think that approach would not
be reliable (e.g. this depends on that the developer knows about this, e.g. has to name the function in a specific way.)

Get text content from React element stored in a variable

Is there a way to get text content from a React element stored in a variable without ref?
There is a functional component, that receives title prop, which contains react element:
function component({ title }) {
const content = title.textContent() // Need Something like this
}
and this title prop might have react node like: <div>Some Title</div>. But I'd like to get only content of the node, in a variable before rendering it. Is it possible?
When I console.log title variable this is the output, The content I want is inside props.children array, so is there a method to get it without traversing through keys:
I've not found a better solution than indeed traversing the object to get the text. In TypeScript:
/**
* Traverse any props.children to get their combined text content.
*
* This does not add whitespace for readability: `<p>Hello <em>world</em>!</p>`
* yields `Hello world!` as expected, but `<p>Hello</p><p>world</p>` returns
* `Helloworld`, just like https://mdn.io/Node/textContent does.
*
* NOTE: This may be very dependent on the internals of React.
*/
function textContent(elem: React.ReactElement | string): string {
if (!elem) {
return '';
}
if (typeof elem === 'string') {
return elem;
}
// Debugging for basic content shows that props.children, if any, is either a
// ReactElement, or a string, or an Array with any combination. Like for
// `<p>Hello <em>world</em>!</p>`:
//
// $$typeof: Symbol(react.element)
// type: "p"
// props:
// children:
// - "Hello "
// - $$typeof: Symbol(react.element)
// type: "em"
// props:
// children: "world"
// - "!"
const children = elem.props && elem.props.children;
if (children instanceof Array) {
return children.map(textContent).join('');
}
return textContent(children);
}
I don't like it at all, and hope there's a better solution.
use https://github.com/fernandopasik/react-children-utilities
import Children from 'react-children-utilities'
const MyComponent = ({ children }) => Children.onlyText(children)
from https://github.com/facebook/react/issues/9255
Thanks #Arjan for the effort and solution, but I have changed something in the component, to get the title in string format.
Now I have added another props to the component: renderTitle which is a function to render custom react title.
So now I am passing title as string:
<Component
title="Some content"
renderTitle={(title) => <div>{title}</div> }
/>
and inside component:
<div>{renderTitle ? renderTitle(title) : title}</div>
With this implementation, I can use title as string to do what I want inside the component, while also supporting custom title render.

StencilJs/Jsx: render HTMLElements in nested component

This is my component:
#Component({
tag: "my-alert-list",
styleUrl: "alert-list.scss",
shadow: true,
})
export class AlertList {
#State() alertList: object[] = [];
#Method()
async appendAlert(
type: string,
message: string,
htmlContent: object,
canClose: boolean = false,
closeDelay: number
) {
let alertBoxElement = (
<my-alert-box
alert-type={type}
message={message}
can-close={canClose}
close-delay={closeDelay}
opened
>
{htmlContent}
</my-alert-box>
);
this.alertList = [
...this.alertList,
alertBoxElement
]
}
render() {
return (
<Host>
{this.alertList}
</Host>
);
}
}
The method appendAlert aims to append a new my-alert-box element to the list of alerts.
In same case i don't want to pass a simple text to the my-alert-box but some HTML block.
(my-alert-box has a receiver slot element and i verified that it works).
I tried to achieve this with the htmlContent variable as you can see, but of course it doesn't work if i do:
$('#alertlist')[0].appendAlert(type='info',message='', htmlContent=document.createElement('div'))
I receive the error:
[STENCIL-DEV-MODE] vNode passed as children has unexpected type.
Make sure it's using the correct h() function.
Empty objects can also be the cause, look for JSX comments that became objects.
Any idea on how can i achieve this?
It's not possible like this because JSX works differently. You could pass the htmlContent as a string and use innerHTML on my-alert-box but it's dangerous (XSS).
Ionic's ion-alert has the same limitation with the message prop... see https://ionicframework.com/docs/api/alert#properties which has a link to https://ionicframework.com/docs/techniques/security, and there they explain how they do some basic DOM sanitization (#ionic/core is also built with Stencil).

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} />) );
}

Unable to render an array of values (React component)

I am building a test app to learn more about React and I have made an API call which gets a huge JSON object.
I was able to break this json into the parts that I need and now I have 10 arrays of 3 props each. I am able to send these 10 arrays in 3 props to another component, which needs to use these 3 props 10 times and render a div class Card each.
I can console.log(this.props) and it shows 10 different arrays with 3 props each,however, I cannot produce a same element 10 times.. I tried using map() but since my array is initially undefined, map() is not able to function properly either. Is there any thing in react like *ngFor in Angular ?
What is the best way to go about this?
*EDIT
Here's more code guys. Sorry still noobie here..
ERROR : this.props.map is not a function
return(
<div>
{this.props.map((data,i)=>{
return(
<li key={i}>{data.likes}</li>
);
*EDIT 2
Soo I tried running map function with an if condition but the code still breaks the very moment the condition gets true..
render() {
if(this.props.url !== undefined){
this.props.map((data,i) =>{
return <li key={i}>{data.likes}</li>
})
}
My state method is :
state = {
userId: undefined,
likes: undefined,
url: undefined
}
and im setting my values on each data stream as follows :
const pics = await fetch(`${base_url}?key=${api_key}&q=${query}
&img_type=photo&per_page=12`).then(response => {
return response.json();
})
pics.hits.map((data) =>{
return this.setState({
userId: data.user_id,
likes: data.likes,
url: data.webformatURL
})
})
this.props won't have map, it's not an array. It's an object with a property for each property passed to your component. For instance:
<YourComponent foo="bar"/>
...will have this.props.foo with the value "bar".
So if you're passing an array to your component, like this:
<YourComponent theArrayProperty={[{likes: 42},{likes:27}]} />
...then you need the name of that property:
return (
<div>
{this.props.theArrayProperty.map((data,i) => {
return (
<li key={i}>{data.likes}</li>
);
})}
</div>
);
Side note: You can use a concise arrow function for the map callback instead:
return (
<div>
{this.props.theArrayProperty.map((data,i) => <li key={i}>{data.likes}</li>)}
</div>
);
...and no need for the () if you put the opening tag on the line with return (you can't leave off the ( if it's on the next line, but you probably knew that):
return <div>
{this.props.theArrayProperty.map((data,i) => <li key={i}>{data.likes}</li>)}
</div>;
...but that's a matter of style.
With little information that you have provided, my guess is that code fails at map() when you try to use it with undefined value.
Try adding a conditional check to render
{props && props.map([RENDER CODE HERE])}
You can just make simple if statement to check if the array is not undefined, and then pass it to map function.
Another option is to set a defaultProps for an empty array.
MyComponent.defaultProps = {
arrProp: []
};

Categories