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

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.)

Related

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.

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: []
};

React 16 forces component re-render while returning array

Assume I have the next piece of code inside the React component
removeItem = (item) => {
this.items.remove(item) // this.items -> mobx array
}
renderItem = (item, index) => {
var _item = undefined
switch (item.type) {
case "header":
_item = <Header key={item.id} onRemove={() => this.removeItem(item)} />
// a few more cases
// note that item.id is unique and static
}
// return _item -> works fine
return [
_item,
this.state.suggested
? <Placeholder key={-item.id} />
: null
]
}
render() {
return (
<div>
{this.items.map((item, i) => renderItem(item))}
</div>
)
}
Also assume that inside each of item I have a button that triggers onRemove handler with click. And each component has textarea where user can enter his text.
Obviously, when user enters text inside item's textarea, it should be saved until item will be removed.
The problem is when I remove some item, each item that goes after the removed one is being remounted (edited for Vlad Zhukov). It happens only when I return an array from renderItem(...) (I mean, when I return only item, this problem doesn't happen).
My question: is this a bug, or it's a feature? And how can I avoid it (desirable without wrapping item and Placeholder with another React child)?
UPDATED
I tried rewrite renderItem(...) the next way:
renderItem = (item, index) => {
var Item = undefined
switch (item.type) {
case "header":
Item = Header
// a few more cases
// note that item.id is unique and static
}
// return _item -> works fine
return [
<Item key={item.id} onRemove={() => this.removeItem(item)} />,
this.state.suggested
? <Placeholder key={-item.id} />
: null
]
}
And it still causes the problem.
Rerendering is absolutely fine in React and can be considered the main feature. What happens in your case is components remount when you make changes to an array of elements when these elements have no key props.
Have a look at this simple example. As you can see rerendering components has no difference but removing the first element will clear values of inputs below.
You've got 2 options:
Use a component instead of an array and set key to it (see an example). There is really no reason not to.
Remove all keys. The reason why it works is because React internally already uses keys for elements. However I wouldn't suggest this as it doesn't look reliable enough to me, I'd prefer to control it explicitly.

Changing a html variable in react

I have the following variable in React
let Example = (
<div>
Hello World
</div>
);
I would like to change its content (text, tags, etc.)
Is that possiable to do?
What is the type of this variable?
Thanks
You can just overwrite your variable in another statement and this is a jsx Element.
https://reactjs.org/docs/introducing-jsx.html
Maybe you are looking for react components and props?
https://reactjs.org/docs/components-and-props
The type of your variable would be a JavaScript function and you can use react props to modify your content.
Use functions!
const myContent = () => {
// Some js
return 'Hello!';
}
const Example = () => (
<div>
{myContent()}
</div>
);
If you want to change the content of a JSX snippet that's a crystal clear hint that what you need is a component.
In your example, you can try something like this.
const SayHello = ({ name }) => <div>Hello {name}</div>
Then you can use it as a regular component.
<SayHello name="world" />
You can bound the value to a variable, and then the component will be updated with any update of that variable.
let name = this.aMethodToGetAValue();
return <SayHello name={name} />

Reactjs how to convert string to [object Object]

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'} ... />;

Categories