Is there a better way to use conditionals inside jsx? - reactjs

I've been learning React for the past few weeks and one thing that I don't like is that I have to use ternary operator for if else inside the render function.
Something like:
function render() {
return (
{x==="Let's go" ? <Go /> : <Hold on />}
)
}
Is there a way I can use a traditional if-else or switch for that matter with jsx syntax in React?

I utilize a few approaches to clean up the render method of more complex components.
1) Use a variable. Before you are actually in the JSX portion (the return) you can use the flexibility of raw JS to build variables. To take your example...
function render() {
let body;
if (x === "Let's go") {
body = <Go />
} else {
body = <Hold on />;
}
return (
<div>
{body}
</div>
);
}
Note that the top level needs to be wrapped, just put an extra div in there.
2) Use a function. This example is probably a little too simplistic but you'll get the idea..
renderBody() {
let body;
if (x === "Let's go") {
body = <Go />
} else {
body = <Hold on />;
}
return (
{body}
)
}
render() {
return (
<div>
{renderBody()}
</div>
)
}
3) Use an array (really a subset of #1) but oftentimes I find scenarios where sometimes I need to return 1 element but other times I need to return a variable number of elements. I will create an array at the top of the function and then push/unshift elements onto the array. Note that any time that you are building an array you must provide a key for each element so that React can update it properly.
let els = [
<div key="always">Always here</div>
];
if (foo) {
els.push(<div key="ifFoo">Conditionally Here</div>)
}
And then you just use the {els} variable in your main JSX.
4) Return null when you don't want anything to render

I prefer this syntax
function render() {
if (x==="Let's go") return <Go />;
return <Hold on />;
}

Related

Add custom CSS class via variable to returned jsx

I've created a custom component which essentially returns html markup in order to display content based on the values passed to the component. Here's a simplified version for brevity:
interface ContainerProps {
position?: string;
content?: string;
class?: string;
}
const CardContainer: React.FC<ContainerProps> = ({ position = "right", content = "n/a", class = "" }) => {
if ( position.trim().toLowerCase() === "right" ) {
return <><div className="ion-float-right" >{content}</div><div className="clear-right"></div></>
} else if ( position.trim().toLowerCase() === "left" ) {
return <div className="ion-float-left">{content}</div>
} else {
return null
}
};
export default CardContainer;
This works great, but I now need to be able to pass a css class name to the component. However, I can't work out how to add the "class" prop to the returned html/jsx.
I tried various code such as below. However, in all cases the code was output as actual html rather than the value of the prop:
return <div className="ion-float-left" + {class}>{content}</div>
return <div className="ion-float-left {class}" >{content}</div>
return <div className="ion-float-left class">{content}</div>
I also tried a few other random things in desperation and these typically cause a compilation error. What is the best way to achieve the intended result eg:
return <div className="ion-float-left myClassNameHere">{content}</div>
its like inserting a string inside another or adding them together. You can use classname={"yourclasse" + theDynamicClass} or className={yourClass ${dynamicClass}} (inbetween ``)
return <div className=` ion-float-left ${class}``>{content}

How to resolve "serializes to the same string" message with Jest?

In my React app, I've built a function that accepts a string full of regular text and any number of URLs. It then converts these into a <span> in React with every URL inside of an <a href tag. The code works really well but I can't seem to write a Jest test for it.
Here's what I've tried so far:
expect(convertHyperlinks('http://stackoverflow.com'))
.toStrictEqual(<span><a href='http://stackoverflow.com' target='_blank'>stackoverflow.com</a></span>);
And:
expect(convertHyperlinks('http://stackoverflow.com'))
.toMatchInlineSnapshot(<span><a href='http://stackoverflow.com' target='_blank'>stackoverflow.com</a></span>);
In the former case I'm getting the "serializes to the same string" message.
In the latter case, it's showing me this:
Expected properties: <span>stackoverflow.com</span>
Received value: <span>stackoverflow.com</span>
Might anyone know how to build a passing test for this?
Robert
Update: Here's the code for the function in question:
export const convertHyperlinks = (text: string): React.Node => {
// Find all http instances
const regex = /http\S*/g;
const hyperlinkInstances = text.match(regex);
if (!hyperlinkInstances) {
return <span>{text}</span>;
}
// Break up `text` into its logical chunks of strings and hyperlinks
let items = [];
let idx1 = 0;
let idx2 = -1;
hyperlinkInstances.forEach((hyperlink) => {
idx2 = text.indexOf(hyperlink, idx1);
if (idx2 === idx1) {
items.push(hyperlink);
idx1 += hyperlink.length;
} else {
items.push(text.substring(idx1, idx2));
items.push(hyperlink);
idx1 = idx2 + hyperlink.length;
}
});
if (idx1 < text.length) {
items.push(text.substring(idx1, text.length));
}
return (
<span>
{items.map((item) => {
if (item.includes('http://')) {
const plainLink = item.replace('http://', '');
return (
<a href={item.toLowerCase()} target='_blank' key={plainLink}>
{plainLink}
</a>
);
} else {
return item;
}
})}
</span>
);
};
You are returning a ReactNode from the method, which is an object. But you are trying to assert as just a string. It would'nt work.
This is what you may be getting back from the method,
And so, you must assert against the object you got, and not the way you are doing it right now,
const result = convertHyperlinks('http://stackoverflow.com')
expect(result.props[0].key).equals('stackoverflow.com');
// similar kind of assertions.
Additionally, I would suggest you go the component route and just render the component in the test method and assert for presence of elements as opposed to diving into react objects.
A representation of the same is as follows,
Here is your component,
const ConvertToHyperlinks = ({text}: {text: string}) => {
// your logic and then returning DOM elements.
return <></>;
}
Then you use it anywhere as,
<div>
<ConvertToHyperlinks text={'https://www.test.com/'} />
</div>
In your unit test you can then,
const renderedComponent = render(<ConvertToHyperlinks text={''https://www.anytyhing.com}/>);
expect(renderdComponent.getByText('anytyhing.com')).ToBeInTheDocument();
Here I am using some Rect Testing Library method but the idea is same even if you use enzyme etc.

how to pass jsx as a string inside the return call

Is it possible to do something like:
const data={
star: "<h1>STAR</h1>",
moon: "<h3>moon</h3>"
}
const App = () => {
return(
<div>{data.start}</div>
);
}
what i get is the actual string of <h1>STAR</h1> not just STAR
I don't think you can. You can return an html string and possibly get it to display, but JSX isn't a string, it gets compiled into javscript code that creates those elements. it works when your app is built, I don't think you can use dynamic strings with it at run-time. You could do something like this:
const getData = (which) => {
if (which === 'star') {
return (<h1>STAR</h1>);
}
if (which === 'moon') {
return (<h3>moon</h3>);
}
return null; // nothing will display
}
const App = () => {
return (
<div>{getData('star')}</div>
);
};
Strings can be converted to JSX with third-party libraries such as h2x or react-render-html. It may be unsafe to do this with user input because of possible vulnerabilities and security problems that may exist libraries that parse DOM.
It's impossible to use components this way because component names aren't associated with functions that implement them during conversion.

Render some text if array.filter returns 0 in React?

I have a filter on an array in the render function in a React component:
someArray.filter(item => {
if (item.name.includes(searchText))return true
}).map(item=>{
return <h1>{item.name}</h1>
});
How can I elegantly display some text along the lines of "No search results" when no items are being returned by the map function?
There are a few ways you can do this. You can use a ternary operator (and also shorten your callbacks):
const filtered = someArray.filter(item =>
item.name.includes(searchText)
);
//Then, in your JSX:
{
filtered.length > 0 ?
filtered.map((item, key) =>
<h1 key={key}>{item.name}</h1>
)
:
<h1>No search results</h1>
}
This checks if there are any filtered results. If so, it will map them to h1s that have the name of the item. If not, then it will simply render a single h1 with the text 'No search results'.
One possible way is, instead of putting this code directly inside JSX render method, put it inside a method and call that method from render.
Like this:
_filterItem(){
const arr = someArray.filter(item => item.name.includes(searchText))
if(!arr.length) return <div>No data found</div>;
return arr.map(item => <h1 key={/*some unique value*/}>{item.name}</h1>)
}
render(){
return(
<div>{this._filterItem()}</div>
)
}
Suggestion:
With filter and map you can use concise body of arrow function instead of block body, for more details check MDN Doc.
Short and concise:
someArray.map(({name}) => name.includes(searchText) && <h1>{name}</h1>)

Simple for loop in react This code gives Errors at for loop

This gives Error at for loop
let contact=[{name:"Mithun"},{name:"Keerthana"},{name:"Jayendara"},{name:"Shivani"}]
for (i=0;i<list;i++)
{
<h1>{content[0].name}</h1>
}
You need to use contact.length rather than list in the for loop. You also need to use contact[i] rather than content[0].
for (i = 0; i < contact.length; i++) {
<h1>{contact[i].name}</h1>
}
If you are using TSX (TypeScript + React), you can use the map function to make this easier.
return contact.map(c => <h1>{c.name}</h1>);
Suggest you a few things
In your question you are looping over list rather than that you should be looping over contacts
As I understand you wish to craete a JSX element from the contact objects. So you need to push it into an array and then render it like
Code:
let contact=[{name:"Mithun"},{name:"Keerthana"},{name:"Jayendara"},{name:"Shivani"}]
var content = [];
for (i=0;i<contact;i++)
{
content.push(<h1>{contact[i].name}</h1>);
}
and when you want to render this in your render function you will do something like
return (
<div>{content}</div>
)
However since you are using react you should be using map function which are more convient and easy to use
Your code will look like
render() {
return(
<div>
{contacts.map(function(item) {
return (<h1>{item.name}</h1>)
})}
</div>
)
}

Resources