How to add multiple classNames to nextjs elements - javascript

I have an unordered list element that looks like this:
<ul className={styles["projects-pd-subdetails-list"]}>
{detail.subdetails.map((sub) => (
<li
className={styles["projects-pd-text projects-pd-subdetail"]}
>
{sub}
</li>
))}
</ul>
With a normal React element, I would be able to apply the multiple classes for the li element like this:
<li className="projects-pd-text projects-pd-subdetail">{sub}</li>
However, having a space like I do in nextjs means the styles are just getting ignored. How can I fix this problem and properly account for two classNames for my li element here?

You can use multiple className like this
<li className={`${styles.projects-pd-text} ${styles.projects-pd-subdetail}`}>
{sub}
</li>
But there is a problem. It may throws an error(I guess, not sure). You may use camelCase in your css className.
<li className={`${styles.projectsPdText} ${styles.projectsPdSubdetail}`}>
{sub}
</li>
or, if you don't want to camelCase
<li className={`${styles["projects-pd-text"]} ${styles["projects-pd-subdetail"]}`}>
{sub}
</li>
Let me know if it works.

A simple array join should suffice.
["class1", "class2", "class3"].join(" ")
result: "class1 class2 class3"
<li className={[styles.projects_pd_text, styles.projects_pd_subdetail].join(" ")}>
{sub}
</li>
Or save it as a variable for multiple uses
const listClasses = [styles.projects_pd_text, styles.projects_pd_subdetail].join(" ")

As stated in my original comment I have not worked with Next.js.
It appears as though styles is a map of some kind i.e.:
const styles = {
"projects-pd-subdetails-list": "Class Name A",
"projects-pd-text": "Class Name B",
"projects-pd-subdetail": "Class Name C"
}
This means that by using a line similar to styles["projects-pd-text projects-pd-subdetail"] you are attempting to retrieve the value for the key "projects-pd-text projects-pd-subdetail" which does not exist.
I would suggest retrieving the values individually from the map and then joining them together with your choice of string concatenation.
className={styles["projects-pd-subdetail"] + " " + styles["projects-pd-text"]}
// OR
className={`${styles["projects-pd-subdetail"]} ${styles["projects-pd-text"]}`}

clsx is generally used to conditionally apply a given className.
https://www.npmjs.com/package/clsx

Because it might be tedious to always write styles.className for every class you need to add to an element, you can create a utility function that makes things look neater.
For example, in my case, I created this function:
export const classes = (styles: any, classes: string) => {
const list = classes.split(' ');
classes = '';
for (const className of list) {
classes += `${styles[className] }`
}
return classes;
}
in a util file.
And on elements, I can do this
<div className={classes( styles, 'hero-container text-success text-bold italicize another-class yet-another-class ')}>
Lorem ipsum dolor
</div>
A PLUS:
One other issue around classnames I encountered getting started with NextJS on VSCode was getting emmet tag generation to list the classnames the NextJS way when I type css class selector.
"Complete NextJS Classname Emmet": {
"scope": "javascript,typescript,typescriptreact,javascriptreact",
"prefix": ["."],
"body": [
"<div className={classes( styles, '$1')}>",
"\t$2",
"</div>"
],
"description": "Lean autocomplete emmet in nextjs css modules classname"
}
I added this to my VSCode > Preferences > User Snippets > typescriptreact.json
These made working with classnames in NextJS easier for me - especially on VSCode.

It worked for me.
<div className={styles.side +" "+ styles.side2}>side2</div>
Thanks to CodenNerd

If you console log your css or scss module class (ex. ImportedStyleModule.someClassName) you'll see it's just a string that has an auto generated UID concatenated.
Ergo, it's just a string so you can use a number of ways to join them like so:
//ts
const mergeStyles = (styleArray: string[]) => (styleArray.map((style: string) => `${style}`).join(" "));
//js
const mergeStyles = (styleArray) => (styleArray.map((style) => `${style}`).join(" "));
//example
return (
<span
onClick={() => setIsClicked(!isClicked)}
className={`${!!isClicked
? mergeStyles([NavbarStyle.navButton, NavbarStyle.navButtonOpen])
: NavbarStyle.navButton}`}
>
<Image src='/image-logo.svg' alt='logo' width='80px' height='40px' />
</span>
);

<div className={`${GloStyles.flexRow} ${MyStyles.gap_10rem}`}> Hello </div>
Explanation
className accept 'String' only
use 'String template' solved,
it is just like a normally react app className, nothing fancy
enter image description here
enter image description here

a Function would be much cleaner
const convertToNextClassName = (className) => className.split(' ').map(c => styles[c]).join(' ')
then in the jsx you can directly call it
<div className={convertToNextClassName("firstClass secondClass")}></div>
for conditional i suggest using classnames from npm

The array join() is the cleanest solution, as #rain mentioned here.
You can also use global css class names.
<span className={[styles.grid, "disabled"].join(" ")}>content</span>

This is how it works for me in my style component
<ul>
<li className={router.pathname == "/" && styles.active}><Link href="/">Home</Link></li>
<li className={router.pathname == "/about" && styles.active}><Link href="/about">About</Link></li>
<li className={router.pathname == "/contact" && styles.active}><Link href="/contact">Contact</Link></li>
<li><Link href="/404">404</Link></li>
</ul>

Related

JavaScript - Replace a string with anchor tag while fetching data from JSON Array

I have encountered this problem while doing loop over array of objects. Consider I have below array of objects.
General : [
{
question: 'Whats you suggestion?',
answer: "Before you go ahead and book one of our trusted developer, please read our FAQ.",
},
{
question: 'Do you have guaranteed solution?',
answer: 'Please let us know at hello#stackoverflow.com.',
}
]
I am using map in my JSX (react application) something like this.
General.map((faq, index) => (
<details>
<summary>{faq.question}</summary>
<div>
<Linkify>{faq.answer}</Linkify>
</div>
</details>
))}
Now I want the text hello#stackoverflow.com to render as html anchor tag. I have done this through React Linkify. But how we can achieve this with pure javascript ?
This will work for emails, but you'll need to work out the regex for urls and chain them. Hope it helps.
General.map((faq, index) => {
const emailRegex = /([a-zA-Z0-9._-]+#[a-zA-Z0-9._-]+\.[a-zA-Z0-9._-]+)/g;
const emailReplacer = "<a href='mailto:$1'>$1</a>";
return (
<details>
<summary>{faq.question}</summary>
<div>
<div>
{faq.answer.replace(emailRegex, emailReplacer)}
</div>
</div>
</details>
)
})

React - merging 2 strings to get URL

How do I join a link http://image.tmdb.org/t/p/w185/ and {item.backdrop_path}, which provides the rest of the link?
For example in the end it should look like: http://image.tmdb.org/t/p/w185/bcRFf5Qmw4XotFYAfj8fCS8PJy5.jpg
this is the code itself:
export default class MoviesSearch extends Component {
render() {
const {movieprop} = this.props;
return (
<ul className = "col-md-4 list-group">
{
movieprop && movieprop.slice(0,5).map((item ) =>
<li key={item.id}>
<h4>name:{item.title }</h4>
<p>release date: {item.release_date}</p>
<p>vote: {item.vote_average}</p>
<img src="http://image.tmdb.org/t/p/w185{item.backdrop_path}"/> //NOT WORKING
</li>
)
}
</ul>
)
}
}
Right now it works fine for title, release date and vote, but I can not get the url. The data is from themoviedb:
http://api.themoviedb.org/3/discover/movie?certification_country=US&certification=R&sort_by=vote_average.desc&api_key=79eb5f868743610d9bddd40d274eb15d
Please let me know if my explanation is horrible and I need to provide more information.
You are looking to concatenate a string in JSX? Here's how:
<img src={"http://image.tmdb.org/t/p/w185" + item.backdrop_path} />
Alternatively, in ES6 you can use Template Literals, like this:
<img src={`http://image.tmdb.org/t/p/w185${item.backdrop_path}`} />
A simple demo of the result, here. (Inspect the element to see the src url.)

React - Optionally wrap an item in a link

I have a React component that displays a title and some text. I want to optionally wrap the title in a link (the same component is used in more than one place), and would appreciate guidance for the best way to do it.
My component looks like this:
var FeedItem = React.createClass({
renderRawMarkup: function(text) { ... },
render: function() {
var item = this.props.item,
rawBody = this.renderRawMarkup(item.body);
return (
<article className="feed-item">
<h2 className="feed-item__title">{item.title{</h2>
<div className="feed-item__body" dangerouslySetInnerHTML={rawBody} >
</div>
</article>
);
});
Am I best to create a new component just for the title? Or can I use an if inside the return, e.g.:
<h2 className="feed-item__title">
{if (item.path) { <a href={item.path}> }}
{item.title}
{if (item.path) { </a> }}
</h2>
I'm a bit of a React novice so apologies if I'm approaching the problem from completely the wrong angle!
You can't use if statements inside jsx, but you can make use of ternary expressions. In your case, you can use:
<h2 className="feed-item__title">
{ item.path ? <a href={item.path}>{item.title}</a> : {item.title} }
</h2>
This is stated in the official documentation: React docs

Have both JSX and normal className together

I have a css style class that needs to be in JSX form as it depends on the state, but I also need to use style class the ordinary way for styling. How do I combine these together?
I've tried
<span className={statusColor} className="uppercase">
Which only takes into account the last class one
<span className={statusColor uppercase}>
Which obviously looks for a variable called uppercase and errors out
<span className="uppercase {statusColor}">
Neither of them work
Below is a dummied-down version of my code. What is the correct way to do this?
const Component = React.createClass({
return {
completed: false
}
},
render () {
let statusColor;
this.state.completed === true ? statusColor = "green" : statusColor = "red";
return (
<div className="status-banner">
<span className="kata-text-status">Status: <span className={statusColor} className="uppercase">{this.state.completed.toString()}</span></span>
</div>
)
}
})
Try this:
<span className={`uppercase ${statusColor}`}>
This will use an ES6 template string (`uppercase ${statusColor}`) and interpolate statusColor with ${...}.
<span className={'uppercase' + ' ' + statusColor}>
You can do it in several ways.
Firstly you can use + to concat more than one string variables and generate dynamic strings
<span className={"uppercase " + statusColor}>
Or you can use npm modules classnames.
The brackets {} translate to normal JS. So you can just use + to concatenate.
<span className={"uppercase " + statusColor}>

Rendering raw html with reactjs

So is this the only way to render raw html with reactjs?
// http://facebook.github.io/react/docs/tutorial.html
// tutorial7.js
var converter = new Showdown.converter();
var Comment = React.createClass({
render: function() {
var rawMarkup = converter.makeHtml(this.props.children.toString());
return (
<div className="comment">
<h2 className="commentAuthor">
{this.props.author}
</h2>
<span dangerouslySetInnerHTML={{__html: rawMarkup}} />
</div>
);
}
});
I know there are some cool ways to markup stuff with JSX, but I am mainly interested in being able to render raw html (with all the classes, inline styles, etc..). Something complicated like this:
<!-- http://getbootstrap.com/components/#dropdowns-example -->
<div class="dropdown">
<button class="btn btn-default dropdown-toggle" type="button" id="dropdownMenu1" data-toggle="dropdown" aria-expanded="true">
Dropdown
<span class="caret"></span>
</button>
<ul class="dropdown-menu" role="menu" aria-labelledby="dropdownMenu1">
<li role="presentation"><a role="menuitem" tabindex="-1" href="#">Action</a></li>
<li role="presentation"><a role="menuitem" tabindex="-1" href="#">Another action</a></li>
<li role="presentation"><a role="menuitem" tabindex="-1" href="#">Something else here</a></li>
<li role="presentation"><a role="menuitem" tabindex="-1" href="#">Separated link</a></li>
</ul>
</div>
I would not want to have to rewrite all of that in JSX.
Maybe I am thinking about this all wrong. Please correct me.
There are now safer methods to render HTML. I covered this in a previous answer here. You have 4 options, the last uses dangerouslySetInnerHTML.
Methods for rendering HTML
Easiest - Use Unicode, save the file as UTF-8 and set the charset to UTF-8.
<div>{'First · Second'}</div>
Safer - Use the Unicode number for the entity inside a Javascript string.
<div>{'First \u00b7 Second'}</div>
or
<div>{'First ' + String.fromCharCode(183) + ' Second'}</div>
Or a mixed array with strings and JSX elements.
<div>{['First ', <span>·</span>, ' Second']}</div>
Last Resort - Insert raw HTML using dangerouslySetInnerHTML.
<div dangerouslySetInnerHTML={{__html: 'First · Second'}} />
dangerouslySetInnerHTML is React’s replacement for using innerHTML in the browser DOM. In general, setting HTML from code is risky because it’s easy to inadvertently expose your users to a cross-site scripting (XSS) attack.
It is better/safer to sanitise your raw HTML (using e.g., DOMPurify) before injecting it into the DOM via dangerouslySetInnerHTML.
DOMPurify - a DOM-only, super-fast, uber-tolerant XSS sanitizer for HTML, MathML and SVG. DOMPurify works with a secure default, but offers a lot of configurability and hooks.
Example:
import React from 'react'
import createDOMPurify from 'dompurify'
import { JSDOM } from 'jsdom'
const window = (new JSDOM('')).window
const DOMPurify = createDOMPurify(window)
const rawHTML = `
<div class="dropdown">
<button class="btn btn-default dropdown-toggle" type="button" id="dropdownMenu1" data-toggle="dropdown" aria-expanded="true">
Dropdown
<span class="caret"></span>
</button>
<ul class="dropdown-menu" role="menu" aria-labelledby="dropdownMenu1">
<li role="presentation"><a role="menuitem" tabindex="-1" href="#">Action</a></li>
<li role="presentation"><a role="menuitem" tabindex="-1" href="#">Another action</a></li>
<li role="presentation"><a role="menuitem" tabindex="-1" href="#">Something else here</a></li>
<li role="presentation"><a role="menuitem" tabindex="-1" href="#">Separated link</a></li>
</ul>
</div>
`
const YourComponent = () => (
<div>
{ <div dangerouslySetInnerHTML={{ __html: DOMPurify.sanitize(rawHTML) }} /> }
</div>
)
export default YourComponent
You could leverage the html-to-react npm module.
Note: I'm the author of the module and just published it a few hours ago. Please feel free to report any bugs or usability issues.
I have used this in quick and dirty situations:
// react render method:
render() {
return (
<div>
{ this.props.textOrHtml.indexOf('</') !== -1
? (
<div dangerouslySetInnerHTML={{__html: this.props.textOrHtml.replace(/(<? *script)/gi, 'illegalscript')}} >
</div>
)
: this.props.textOrHtml
}
</div>
)
}
I have tried this pure component:
const RawHTML = ({children, className = ""}) =>
<div className={className}
dangerouslySetInnerHTML={{ __html: children.replace(/\n/g, '<br />')}} />
Features
Takes classNameprop (easier to style it)
Replaces \n to <br /> (you often want to do that)
Place content as children when using the component like:
<RawHTML>{myHTML}</RawHTML>
I have placed the component in a Gist at Github: RawHTML: ReactJS pure component to render HTML
export class ModalBody extends Component{
rawMarkup(){
var rawMarkup = this.props.content
return { __html: rawMarkup };
}
render(){
return(
<div className="modal-body">
<span dangerouslySetInnerHTML={this.rawMarkup()} />
</div>
)
}
}
I used this library called Parser. It worked for what I needed.
import React, { Component } from 'react';
import Parser from 'html-react-parser';
class MyComponent extends Component {
render() {
<div>{Parser(this.state.message)}</div>
}
};
dangerouslySetInnerHTML should not be used unless absolutely necessary. According to the docs, "This is mainly for cooperating with DOM string manipulation libraries". When you use it, you're giving up the benefit of React's DOM management.
In your case, it is pretty straightforward to convert to valid JSX syntax; just change class attributes to className. Or, as mentioned in the comments above, you can use the ReactBootstrap library which encapsulates Bootstrap elements into React components.
Here's a little less opinionated version of the RawHTML function posted before. It lets you:
configure the tag
optionally replace newlines to <br />'s
pass extra props that RawHTML will pass to the created element
supply an empty string (RawHTML></RawHTML>)
Here's the component:
const RawHTML = ({ children, tag = 'div', nl2br = true, ...rest }) =>
React.createElement(tag, {
dangerouslySetInnerHTML: {
__html: nl2br
? children && children.replace(/\n/g, '<br />')
: children,
},
...rest,
});
RawHTML.propTypes = {
children: PropTypes.string,
nl2br: PropTypes.bool,
tag: PropTypes.string,
};
Usage:
<RawHTML>{'First · Second'}</RawHTML>
<RawHTML tag="h2">{'First · Second'}</RawHTML>
<RawHTML tag="h2" className="test">{'First · Second'}</RawHTML>
<RawHTML>{'first line\nsecond line'}</RawHTML>
<RawHTML nl2br={false}>{'first line\nsecond line'}</RawHTML>
<RawHTML></RawHTML>
Output:
<div>First · Second</div>
<h2>First · Second</h2>
<h2 class="test">First · Second</h2>
<div>first line<br>second line</div>
<div>first line
second line</div>
<div></div>
It will break on:
<RawHTML><h1>First · Second</h1></RawHTML>
I needed to use a link with onLoad attribute in my head where div is not allowed so this caused me significant pain. My current workaround is to close the original script tag, do what I need to do, then open script tag (to be closed by the original). Hope this might help someone who has absolutely no other choice:
<script dangerouslySetInnerHTML={{ __html: `</script>
<link rel="preload" href="https://fonts.googleapis.com/css?family=Open+Sans" as="style" onLoad="this.onload=null;this.rel='stylesheet'" crossOrigin="anonymous"/>
<script>`,}}/>
Here is a solution the boils down to two steps:
Use built-in APIs to parse a raw HTML string into an HTML Element
Recursively transform an Element object (and its children) into ReactElement objects.
Note: this is a good example for learning. But consider the options described in the other answers, like the html-to-react library.
Characteristics of this solution:
It does not use dangerouslySetInnerHTML
It uses React.createElement
Runnable example repository.
Here is the .jsx code:
// RawHtmlToReactExample.jsx
import React from "react";
/**
* Turn a raw string representing HTML code into an HTML 'Element' object.
*
* This uses the technique described by this StackOverflow answer: https://stackoverflow.com/a/35385518
* Note: this only supports HTML that describes a single top-level element. See the linked post for more options.
*
* #param {String} rawHtml A raw string representing HTML code
* #return {Element} an HTML element
*/
function htmlStringToElement(rawHtml) {
const template = document.createElement('template');
rawHtml = rawHtml.trim();
template.innerHTML = rawHtml;
return template.content.firstChild;
}
/**
* Turn an HTML element into a React element.
*
* This uses a recursive algorithm. For illustrative purposes it logs to the console.
*
* #param {Element} el
* #return {ReactElement} (or a string in the case of text nodes?)
*/
function elementToReact(el) {
const tagName = el.tagName?.toLowerCase(); // Note: 'React.createElement' prefers lowercase tag names for HTML elements.
const descriptor = tagName ?? el.nodeName;
const childNodes = Array.from(el.childNodes);
if (childNodes.length > 0) {
console.log(`This element ('${descriptor}') has child nodes. Let's transform them now.`);
const childReactElements = childNodes.map(childNode => elementToReact(childNode)).filter(el => {
// In the edge case that we found an unsupported node type, we'll just filter it out.
return el !== null
});
return React.createElement(tagName, null, ...childReactElements);
} else {
// This is a "bottom out" point. The recursion stops here. The element is either a text node, a comment node,
// and maybe some other types. I'm not totally sure. Reference the docs to understand the different node
// types: https://developer.mozilla.org/en-US/docs/Web/API/Node/nodeType
console.log(`This element ('${descriptor}') has no child nodes.`);
// For simplicity, let's only support text nodes.
const nodeType = el.nodeType;
if (nodeType === Node.TEXT_NODE) {
return el.textContent;
} else {
console.warn(`Unsupported node type: ${nodeType}. Consider improving this function to support this type`);
return null;
}
}
}
export function RawHtmlToReactExample() {
const myRawHtml = `<p>This is <em>raw</em> HTML with some nested tags. Let's incorporate it into a React element.`;
const myElement = htmlStringToElement(myRawHtml);
const myReactElement = elementToReact(myElement);
return (<>
<h1>Incorporate Raw HTML into React</h1>
{/* Technique #1: Use React's 'dangerouslySetInnerHTML' attribute */}
<div dangerouslySetInnerHTML={{__html: myRawHtml}}></div>
{/* Technique #2: Use a recursive algorithm to turn an HTML element into a React element */}
{myReactElement}
</>)
}
This works for me:
render()
{
var buff = '';
for(var k=0; k<10; k++)
{
buff += "<span> " + k + " </span>";
}
return (
<div className='pagger'>
<div className='pleft'>
<div dangerouslySetInnerHTML={{__html: buff }} />
</div>
<div className='pright'>
<div className='records'>10</div>
<div className='records'>50</div>
<div className='records records_selected'>100</div>
<div className='records'>1000</div>
</div>
</div>
)
}
It is safer to sanitize the raw html with something like DOMPurify then use with dangerouslySetInnerHTML
npm i dompurify
For types
npm i --save-dev #types/dompurify
import React from 'react'
import * as DOMPurify from 'dompurify';
let dirty = '<b>hello there</b>';
let clean = DOMPurify.sanitize(dirty);
function MyComponent() {
return <div dangerouslySetInnerHTML={{ __html: clean) }} />;
}
If you have problems making it work in your specific setup, consider looking at the amazing isomorphic-dompurify project which solves lots of problems people might run into.
npm i isomorphic-dompurify
import React from 'react'
import DOMPurify from 'isomorphic-dompurify';
const dirty = '<p>hello</p>'
const clean = DOMPurify.sanitize(dirty);
function MyComponent() {
return <div dangerouslySetInnerHTML={{ __html: clean) }} />;
}
For Demo
https://cure53.de/purify
For More
https://github.com/cure53/DOMPurify
https://github.com/kkomelin/isomorphic-dompurify

Categories

Resources