Could you please explain to me in a simple way what is the purpose of using Classnames utility in React code? I've just read Classnames docs, but I still can't grasp what is the main reason to use it in code like so:
import classnames from 'classnames';
[...]
render() {
const { className } = this.props
return (
<div className={classnames('search', className)}>
<div className="search-bar-wrapper">
<form className="search-form" onSubmit={this.onSearch.bind(this)}>
<div className="search-bar">
<label className="screen-reader-only" htmlFor="header-search-form">Search</label> [...]
Full version of this code (jsx):
https://jsfiddle.net/John_Taylor/j8d42m2f/2/
I don't understand what is going on this line of code:
<div className={classnames('search', className)}>
I've also read that (
how to use classnames library for React.js ) answer, but I still have problems with understanding my code snippet.
classnames library lets you join different classes based on different conditions in a simpler way.
Suppose you have 2 classes of which one is going to get used every time but the second one gets used based on some condition. So without classnames library you would something like this
render() {
const classnames = 'firstClass';
if (someCondition) {
classnames += ' secondClass'
}
return(
<input className={classnames} .. />
);
}
But with classnames library you would do that in this way
render() {
const classnames = {'firstClass', {'secondClass': someCondition}}
return(
<input className={classnames} .. />
);
}
In your case, <div className={classnames('search', className)}>is equivalent to <div className={`search ${className}`}>.
classnamesis mainly useful when you have to deal with conditional classes.
Classnames make it easy to apply class names to react component conditionally.
For example: Let create a state and apply a class to the button component when the button is clicked
const [isActive, setActive] = setState(false);
import classnames from "classnames"
var buttonClasses = classnames({
"btn": true,
"btn__active": isActive;
)}
<button className={buttonClasses} onClick={() => setActive(!isActive)}>Make me active</button>
This code will apply the "isActive" class to the button when it is clicked.
I hope this help answer your question
If used the way you use it in your script classnames simply joins the strings you give it with spaces.
const className = 'x';
const result = classnames('search', className);
// result == 'search x'
I think you should attempt reading the docs one more time, they are very clear. Specifically, this part:
So where you may have the following code to generate a className prop for a in React:
var Button = React.createClass({
// ...
render () {
var btnClass = 'btn';
if (this.state.isPressed) btnClass += ' btn-pressed';
else if (this.state.isHovered) btnClass += ' btn-over';
return <button className={btnClass}>{this.props.label}</button>;
}
});
You can express the conditional classes more simply as an object:
var classNames = require('classnames');
var Button = React.createClass({
// ...
render () {
var btnClass = classNames({
btn: true,
'btn-pressed': this.state.isPressed,
'btn-over': !this.state.isPressed && this.state.isHovered
});
return <button className={btnClass}>{this.props.label}</button>;
}
});
The sole purpose of the library here is to remove the need of writing ugly code to dynamically add classes to something, and just use a consistent syntax instead.
Another example:
var Button = React.createClass({
render () {
return <button className={this.props.foo ? 'bar' : 'baz' + this.props.bar ? 'baz' : 'foo'}>{this.props.label}</button>;
}
});
That is really hard to read. There is a lot of code out there that looks similar to that.
Here is a better way, using classnames:
var classes = classNames({
bar: this.props.foo,
baz: !this.props.foo,
active: this.props.bar,
hover: !this.props.bar
});
var Button = React.createClass({
render () {
return <button className={classes}>{this.props.label}</button>;
}
});
It's very clear there what's happening. If the values in the object are true, the key will be appended to the class. So in that example, the final class will be bar active, given this.props.foo is truthy and this.props.bar is truthy.
Your code snippet
<div className={classnames('search', className)}>
is equivalent to:
<div className={`search ${className ? className : ""}`}>
so in this particular case it's just encapsulates ternary operator from inside template string. Which is small but improvement - harder to produce bug in.
Probably it is simplest case of using classnames, however when you'll have more and more conditions around your class name manipulations, it is going to be more and more useful
Related
i want to improve my code, with several buttons that has custom class names (attr), when clicked should add to body tag (toggle), now is adding the first button only because for ("button")[0] but should work for each button
import React, { useState, useEffect } from "react"
function Test() {
const [isClass, setIsClass] = useState(false)
useEffect(() => {
const x = document.getElementsByTagName("button")[0].getAttribute("custom-class")
document.body.classList.toggle(x, isClass)
}, [isClass])
return (
<>
<button custom-class='test1' onClick={() => setIsClass(!isClass)}>
Setting test1 className
</button>
<button custom-class='test2' onClick={() => setIsClass(!isClass)}>
Setting test2 className
</button>
</>
)
}
export default Test
Thanks
Please use this code.
let oldStyle = "";
const handleClick = (index) => {
const x = [...document.getElementsByTagName("button")].map(value => value.getAttribute("custom-class"));
document.body.classList.contains(x[index]) ? document.body.classList.remove(x[index]) : document.body.classList.add(x[index]);
if(document.body.classList.length > 1) document.body.classList.replace(oldStyle, x[index]);
oldStyle = x[index];
}
return (
<>
<button custom-class='test1' onClick={() => handleClick(0)}>
Setting test1 className
</button>
<button custom-class='test2' onClick={() => handleClick(1)}>
Setting test2 className
</button>
</>
)
It is better not to use DOM querying and manipulation directly with elements that are created and controlled by react. In your particular example it is ok to use document.body, but not ok to search for buttons, especially when you try to find them by tag name. To actually toggle a class in classList you don't need second parameter in most cases, so additional state is also not needed.
React way to get reference to element renderend by React would be to use Ref. However, in your particular case side effect can be launched inside event handler, so you don't need useEffect or useRef.
Your onClick handler can accept event object that is Synthetic Event. It holds property target that holds reference to your button.
So, the easiest way would be simply to write like this:
function Test() {
function clickHandler(event) {
let classToToggle = event.target.getAttribute("custom-class");
document.body.classList.toggle(classToToggle);
}
return (
<>
<button key="test1" custom-class="test1" onClick={clickHandler}>
Setting test1 className
</button>
<button key="test2" custom-class="test2" onClick={clickHandler}>
Setting test2 className
</button>
</>
);
}
export default Test;
If you need to have only single className from the list, you can decide which class to enable or disable with a bit of a state. Since anything can add classes on body it might be useful to operate only on some set of classes and not remove everything.
Also, not mentioned before, but consider using data attribute as its purpose is to keep some additional data.
function Test() {
// this can come from props or be hardcoded depending on your requirements
// If you intend to change it in runtime, consider adding side effect to cleanup previous classes on body
let [classesList] = React.useState(["test1", "test2"]);
let [activeClass, setActiveClass] = React.useState("");
// You can switch actual classes in effect, if you want to
function clickHandler(event) {
let classToToggle = event.target.dataset.customClass;
// we remove all classes from body that are in our list
document.body.classList.remove(...classesList);
if (activeClass === classToToggle) {
setActiveClass("");
} else {
// if class not active - set new one
document.body.classList.add(classToToggle);
setActiveClass(classToToggle);
}
}
return (
<>
{classesList.map((cn) => (
<button key="cn" data-custom-class={cn} onClick={clickHandler}>
Setting {cn} className
</button>
))}
</>
);
}
I want to declare data- attributes on my react Elements and i want to know which type of referencing is better?
Doing ref and useRefs ?
`` declaring Data-{dataname} ``` ?
is any other way ?
when i used data- way i got undefiend data in mapping ...
const stuffsList = stuffs.map((stuff) => (
<div
id="stuffsList"
key={stuff.id}
accessKey={stuff.label}
data-energies={stuff.nutrients.energy}
>
{stuff.label} {stuff.cat.child}
</div>
));
I mapped this code , and it just render first data, not the data deponds on KEY
This is vhat i tried :
const stuffsDiv = document.getElementById("stuffsList");
console.log(stuffsDiv.dataset);
const newStuff = {
label: e.target.accessKey,
energy: stuffsDiv.dataset.energy,
};
You could test codes below :
// Example class component
class Thingy extends React.Component {
constructor(props) {
super(props)
this.state = {
objList:[
{
id:"1",
label:"A",
type:"APlus"
},
{
id:"2",
label:"B",
type:"BPlus"
}
]
}
this.refArray =[];
}
createDomElements=()=>{
let keyCount = 0
let output = this.state.objList.map((obj)=>(
<div className="demoDiv" key={obj.id} id={obj.id} domLabel={obj.lable} data-type={obj.type} ref={domRef=>(this.refArray[obj.id]=domRef)} >{obj.id}</div>
)
)
return output
}
getDataset=()=>{
//let stuffsDiv = document.getElementById("1");
let stuffsDiv = this.refArray[2]
stuffsDiv.dataset.setAnotherProps = "This is another prop"
console.log(stuffsDiv.dataset.setAnotherProps)
console.log(stuffsDiv.dataset.type)
console.log(stuffsDiv.dataset)
}
render() {
return (
<div>
{this.createDomElements()}
<button type="button" onClick={this.getDataset}>get data-set</button>
</div>
)
}
}
// Render it
ReactDOM.render(
<Thingy title="I'm the thingy" />,
document.getElementById("react")
);
.demoDiv {
width:100px;
height:20px;
background-color:red;
margin:10px
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<div id="react"></div>
Update
Your questions:
which way to delcare dataset attribute in Dom element is better in React ?
It's no difference between this way: domElement.dataset.custome and this way: domElement.getAttribute('data-custome')
How to access custom attributes from event object in React?
which type of referencing is better?
I suggest React.createref() is better way to ref DOM element.
reactjs this.refs vs document.getElementById
In your case, because you want to use ref in mapping, you could declare ref as an array at first, like this.refArray=[] (You could see my code)
Problem that you got undefined when use dataset data-
The problem is in your mapping code, all elements got the same ID "stuffsList"
const stuffsList = stuffs.map((stuff) => (
<div
// id="stuffsList" //This line is wrong
id={stuff.id}
key={stuff.id}
accessKey={stuff.label}
data-energies={stuff.nutrients.energy}
>
{stuff.label} {stuff.cat.child}
</div>
));
it just render first data,not the data deponds on KEY
I am not sure what you mean actually, because in my code, two mapping div elements render well.
I suggest what you want to say is:
When you log console.log(stuffsDiv.dataset), always log the data- attributes of first element. The reason is that in your code, every element you map has the same ID, and when multiple ID are, getElementById alway returns the first element.
Can multiple different HTML elements have the same ID if they're different elements?
I'm trying to rewrite one of my JS plugins to react, as a way of learning.
I have a panel that when hidden/shown needs to be updated with several classnames as well as some that need to wait for a css animation to complete (why the timer).
How should I do this in a react way? Using querySelector to change classnames seem very wrong..?
Detailed explanation
When showPanel is triggered the following need to happen
the body/html element need updated css (hence me adding classes)
an existing overlay fades in (adding a class to that)
the modal div is displayed (adding a class for that)
the modal div is told to be active AFTER the animation has been run (hence the timer and class "am-animation-done")
What I preferably would like to have/learn is best practice to do this in reactjs. I'm thinking a toggle state that when triggered sets the state to visible/hidden and if set to "visible" the class changes below happens. My biggest issue is the timer thing.
showPanel = () => {
document.querySelector('body').classList.add('am-modal-locked');
document.querySelector('html').classList.add('am-modal-locked');
document.querySelector('.am-overlay').classList.add('fadein');
document.querySelector('.am-modal').classList.add('am-show');
const timer = setTimeout(() => {
document.querySelector('.am-modal').classList.add('am-animation-done');
}, 500);
return () => clearTimeout(timer);
};
hidePanel = () => {
document.querySelector('.am-modal').classList.remove('am-show');
document.querySelector('.am-modal').classList.remove('am-animation-done');
document.querySelector('.am-overlay').classList.add('fadeout');
const timer = setTimeout(() => {
document.querySelector('.am-overlay').classList.remove('fadein');
document.querySelector('.am-overlay').classList.remove('fadeout');
document.querySelector('body').classList.remove('am-modal-locked');
document.querySelector('html').classList.remove('am-modal-locked');
}, 500);
return () => clearTimeout(timer);
};
Source code updated for clarifaction
This is a lot simpler in React, here's an example with hooks
function Panel() {
const [hidden, setHidden] = useState(false);
const toggleCallback = useCallback(() => setHidden(hidden => !hidden), []);
const cls = hidden ? 'hide' : 'show';
return (
<div className={cls}>
<button onClick={toggleCallback}>Toggle</>
</div>
)
}
You can use the state to dinamically change classnames inside your component
className={this.state.isPanelVisible}
And maybe instead of setting it as boolean you can set your variable to the class you need at the moment.
React working with virtual DOM so you should play with state and change class of that particular element like below example:
constructor(props) {
super(props);
this.state = {'active': false, 'class': 'album'};
}
handleClick(id) {
if(this.state.active){
this.setState({'active': false,'class': 'album'})
}else{
this.setState({'active': true,'class': 'active'})
}
}
<div className={this.state.class} onClick={this.handleClick.bind(this.data.id}>
<p>Data</p>
</div>
In very basic use cases you can write the logic inside of the class itself.
<div className={active ? "active" : "disabled"} />
In more advanced cases I would suggest to use something like classnames package.
https://www.npmjs.com/package/classnames
<div className={classNames({ foo: true, bar: true, boo: false })} />
Which would result in div having class foo and bar
This is mainly regarding one component, but if you really have to affect class of something so far away as body would be, than you are most likely gonna need useQuerySelector or put the state somewhere high and then base the logic on it.
Yes that's not a very good way to do it. Instead you should use state variables to toggle your classes as well. There is no need to manually manipulate DOM. The you can set up your timeout inside the callback of your first setState to change state again.
Maybe something like this:
class Todo extends Component {
constructor(props) {
super(props);
this.state = {
class1: 'on',
class2: 'off'
}
}
toggle = () => {
this.setState({class1: 'off'}, () => {
setTimeout(() => {
this.setState({class2: 'on'})
}, 2000)
})
}
render() {
const {class1, class2} = this.state;
return (
<div>
<h1 className={`${class1} ${class2}`} onClick={this.toggle}>Class toggle</h1>
</div>
)
}
}
use this approach for change styling on state change
<div className={`rest_of_classes ${isClassChange ? 'change_class_name': ''}`} />
I'm displaying text that was stored in the database. The data is coming from firebase as a string (with newline breaks included). To make it display as HTML, I originally did the following:
<p className="term-definition"
dangerouslySetInnerHTML={{__html: (definition.definition) ? definition.definition.replace(/(?:\r\n|\r|\n)/g, '<br />') : ''}}></p>
This worked great. However there's one additional feature. Users can type [word] and that word will become linked. In order to accomplish this, I created the following function:
parseDefinitionText(text){
text = text.replace(/(?:\r\n|\r|\n)/g, '<br />');
text = text.replace(/\[([A-Za-z0-9'\-\s]+)\]/, function(match, word){
// Convert it to a permalink
return (<Link to={'/terms/' + this.permalink(word) + '/1'}>{word}</Link>);
}.bind(this));
return text;
},
I left out the this.permalink method as it's not relevant. As you can see, I'm attempting to return a <Link> component that was imported from react-router.However since it's raw HTML, dangerouslySetInnerHTML no longer works properly.
So I'm kind of stuck at this point. What can I do to both format the inner text and also create a link?
You could split the text into an array of Links + strings like so:
import {Link} from 'react-router';
const paragraphWithLinks = ({markdown}) => {
const linkRegex = /\[([\w\s-']+)\]/g;
const children = _.chain(
markdown.split(linkRegex) // get the text between links
).zip(
markdown.match(linkRegex).map( // get the links
word => <Link to={`/terms/${permalink(word)}/1`}>{word}</Link> // and convert them
)
).flatten().thru( // merge them
v => v.slice(0, -1) // remove the last element (undefined b/c arrays are different sizes)
).value();
return <p className='term-definition'>{children}</p>;
};
The best thing about this approach is removing the need to use dangerouslySetInnerHTML. Using it is generally an extremely bad idea as you're potentially creating an XSS vulnerability. That may enable hackers to, for example, steal login credentials from your users.
In most cases you do not need to use dangerouslySetHTML. The obvious exception is for integration w/ a 3rd party library, which should still be considered carefully.
I ran into a similar situation, however the accepted solution wasn't a viable option for me.
I got this working with react-dom in a fairly crude way. I set the component up to listen for click events and if the click had the class of react-router-link. When this happened, if the item has a data-url property set it uses browserHistory.push. I'm currently using an isomorphic app, and these click events don't make sense for the server generation, so I only set these events conditionally.
Here's the code I used:
import React from 'react';
import _ from 'lodash';
import { browserHistory } from 'react-router'
export default class PostBody extends React.Component {
componentDidMount() {
if(! global.__SERVER__) {
this.listener = this.handleClick.bind(this);
window.addEventListener('click', this.listener);
}
}
componentDidUnmount() {
if(! global.__SERVER__) {
window.removeEventListener("scroll", this.listener);
}
}
handleClick(e) {
if(_.includes(e.target.classList, "react-router-link")) {
window.removeEventListener("click", this.listener);
browserHistory.push(e.target.getAttribute("data-url"));
}
}
render() {
function createMarkup(html) { return {__html: html}; };
return (
<div className="col-xs-10 col-xs-offset-1 col-md-6 col-md-offset-3 col-lg-8 col-lg-offset-2 post-body">
<div dangerouslySetInnerHTML={createMarkup(this.props.postBody)} />
</div>
);
}
}
Hope this helps out!
I have a set of buttons created from an array, however I'm unsure how to set individual classNames for them. Is there an easy way to this?
var ButtonContainer = React.createClass({
render: function(){
var answerList = this.props.answerList.map(function(input, i){
return <SingleButton key={'button'+i} singleAnswer={input}/>
}.bind(this));
return <div> {answerList} </div>
}
})
var SingleButton = React.createClass({
render: function(){
return (
<div>
<button className='quiz-button'>{this.props.singleAnswer}</button>
</div>
)
}
});
I've tried className={this.props.key} but that doesn't seem to work. Thanks for any help!
Since React v0.12 key and ref are removed from props:
You can no longer access this.props.ref and this.props.key from inside
the Component instance itself. So you need to use a different name for
those props.
That is why setting className={this.props.key} wont work. But you can try this:
return <SingleButton key={'button'+i} className={'button'+i} singleAnswer={input}/>
and then
<button className={this.props.className}>{this.props.singleAnswer}</button>
Related question: This.key in React.js 0.12