I want to keep open my sidemenu but i don't know how to do.
const Dep2Handler = (e) => {
const classOnTarget = e.target.parentElement;
const onRemoveTarget = classOnTarget.parentElement.children;
if (classOnTarget.classList.contains("on")) {
classOnTarget.classList.remove("on");
}
else {
for (let i = 0; i < onRemoveTarget.length; i++) {
onRemoveTarget[i].classList.remove("on");
}
classOnTarget.classList.add("on");
}
};
/* useLayoutEffect(()=>{
},[sideNaviPos]) */
const Dep3Handler = (e,props) => {
const classOnTarget = e.target;
const onRemoveTarget = classOnTarget.parentElement.children;
classOnTarget.classList.add("on");
};
--------------------------------------------------------------------
<ol>
<li>
<p onClick={Dep2Handler} sidebar={sidebar}></p>
<ol className="has_dep3">
<li onClick={Dep3Handler} value="space"><button className="btn" onClick={(e)=>history("/info")}></button></li>
<li onClick={Dep3Handler} value="equip"><button className="btn" onClick={(e)=>history("/info/info/equipinfo")}></button></li>
<li onClick={Dep3Handler} value="work"><button className="btn" onClick={(e)=>history("/info/info/workerinfo")}></button></li>
</ol>
</li>
<li>
<p onClick={Dep2Handler}></p>
<ol className="has_dep3">
<li onClick={Dep3Handler}><button onClick={(e)=>history("/admin/info/greetings")}></button></li>
<li onClick={Dep3Handler}><button onClick={(e)=>history("/admin/info/vision")}></button></li>
<li onClick={Dep3Handler}><button onClick={(e)=>history("/admin/info/organization")}></button></li>
<li onClick={Dep3Handler}><button onClick={(e)=>history("/admin/info/partner")}></button></li>
</ol>
</li>
<li>
<p onClick={(e)=>{Dep2Handler(e);history("/info/way")}}></p>
</li>
<li>
<p onClick={(e)=>{Dep2Handler(e);history("/info/faq")}}></p>
</li>
</ol>
if i click children of first li menu, keeping open then click another one the previous will be close and clicked menu open. Is it possible without use redux?
There is quite a bit to unpack here so I apologize in advance if this post ends up longer than you desired.
First off, and to address your question about Redux
Is it possible without use redux?
Most definitely. Lots of people used React before redux was a thing and for some apps even today can get away without redux (using the Context API is an option).
Before just showing you to code let me comment on some of your existing code
classOnTarget.classList.add("on");
by doing this you are effectively using the DOM's classList to hold your state rather than React. While there are cases that something like that might be needed (e.g. you are introducing some react components into an existing codebase) I don't think that's the case here and we should change that.
Let's start small, what if you want to have a single menu open or close. One approach could be to have something like
const [isOpen, setIsOpen] = useState(false);
...
return <div className={isOpen ? 'on' : ''}>...</div>
The, maybe obvious, next step is what if I have two menus?
const [isFirstOpen, setIsFirstOpen] = useState(false);
const [isSecondOpen, setIsSecondOpen] = useState(false);
...
return (
<div className={isFirstOpen || isSecondOpen ? 'on' : ''}>
<div className={isFirstOpen ? 'active' : ''}>First</div>
<div className={isSecondOpen ? 'active' : ''}>Second</div>
</div>
);
but the above code is neither pretty nor scalable (what if I have 10 items in the menu?). So instead of representing the state of each possible menu you might want to keep track of which one is open/active
const [activeRoute, setActiveRoute] = useState();
...
return (
<div className={activeRoute ? 'on' : ''}>
<div className={activeRoute === 'first' ? 'active' : ''}>First</div>
<div className={activeRoute === 'second' ? 'active' : ''}>Second</div>
</div>
);
Now, we want the active route to change based on what is being clicked
const [activeRoute, setActiveRoute] = useState();
const navigateTo = (newRoute) => {
history(newRoute);
setActiveRoute(newRoute);
};
...
return (
<div className={activeRoute ? 'on' : ''}>
<div className={activeRoute === '/first' ? 'active' : ''}
onClick={() => navigateTo('/first')}>First</div>
<div className={activeRoute === '/second' ? 'active' : ''}
onClick={() => navigateTo('/second')}>Second</div>
</div>
);
Now you might ask, well that's all nice and dandy but how do I deal with a nested menu? like in your case.
You might notice in your existing code that all the onClick handlers happen in the deepest (the most nested) li / button, not on the first level of the menu.
So lets say that on your first level you just want to add a class so it looks like that's the active menu.
const MENU_MAP = {
'/first': 'menuA',
'/second': 'menuA',
'/third': 'menuB',
'/fourth': 'menuB',
};
const [activeRoute, setActiveRoute] = useState();
const navigateTo = (newRoute) => {
history(newRoute);
setActiveRoute(newRoute);
};
const activeMenu = activeRoute ? MENU_MAP[activeRoute] : null;
...
return (
<div>
<div className={activeMenu === 'menuA' ? 'open' : ''}>
<div className={activeRoute ? 'on' : ''}>
<div className={activeRoute === '/first' ? 'active' : ''}
onClick={() => navigateTo('/first')}>First</div>
<div className={activeRoute === '/second' ? 'active' : ''}
onClick={() => navigateTo('/second')}>Second</div>
</div>
</div>
<div className={activeMenu === 'menuB' ? 'open' : ''}>
<div className={activeRoute ? 'on' : ''}>
<div className={activeRoute === '/third' ? 'active' : ''}
onClick={() => navigateTo('/third')}>Third</div>
<div className={activeRoute === '/fourth' ? 'active' : ''}
onClick={() => navigateTo('/fourth')}>Fourth</div>
</div>
</div>
</div>
);
I hope you can see a pattern in the above and apply that to your specific HTML requirements of using ol, li, button. Hope this helps
--
For anyone other than the OP reading this and thinking "this could be cleaner" or "we could use useContext" or "we could add an abstraction here and here", save it. The verbosity on my answer to hopefully help the OP see the pattern so they can figure out a solution; this is not about showing off you know redux or mobX or how to use hooks.
Is there a possibility to set singe value on React rc-slider Range component? At present to do that I need to drag the second handle in front of the first one or the first one drag after the second one and this behavior is allowing user to drag the handle behind the rail if we are on the edge values
I would like to set this up like on the third image it would show from eg. 39 to 39. Here is my code
import React, { useState } from "react";
import Slider, { Range } from "rc-slider";
import "rc-slider/assets/index.css";
export const RangeSlider = ({
min,
max,
error,
displayUnit,
stereo,
onChange
}) => {
const [minVal, setMinVal] = useState(min);
const [maxVal, setMaxVal] = useState(max);
const props = {
onChange: value => {
if (stereo) {
setMinVal(value[0]);
setMaxVal(value[1]);
} else {
setMinVal(value);
setMaxVal(value);
}
onChange(value);
},
min: min,
max: max,
defaultValue: stereo ? [0, 100] : 0
};
return (
<>
{error && (
<span className="position-outside">
<i className="fas fa-exclamation-circle fa text-danger mt-2"></i>
</span>
)}
<div className="text-primary h6 mb-3">
<strong>
{stereo &&
`Od ${minVal}${
displayUnit ? " " + displayUnit : ""
} do ${maxVal}${displayUnit ? " " + displayUnit : ""}`}
{!stereo &&
`${minVal}${displayUnit ? " " + displayUnit : ""}`}
</strong>
</div>
{stereo ? <Range {...props} /> : <Slider {...props} />}
</>
);
};
I am aware that single range slider is for single values, although the client has requested this feature.
Thanks
Firstly my apologies for making a confusion with the title and a question, the title should be different, I have found the bug I thought it was rc-slider, however it was me translating the second rail to be contained within the track, I have explained everything in detail in this post styling rc-slider track to contain handles within itself therefore I am closing this one. Thanks
I'm trying to create an answer sheet, sometimes referred to as a "Bubble sheet", where the user clicks on "A,B,C, or D" and bubble changes from "light mode to dark mode" as when physically happens penciling in the answer. (Using Tailwind.css)
I'm having a problem with 1) getting only one answer to change state (they all change) and 2) only being able to select one of the answers per question (A,B,C, OR D). Any help would be appreciated as I'm close but stumped before the finish line (so to speak).
I'm passing in props.start from the parent component and repeating component to build answer sheets in increments of 25. You could easily not pass any props and replace "props.start" with 1 to test this out.
import React, { useState } from 'react';
const Bubbles = (props) => {
const [cName, setCname] = useState(
'rounded-full px-2 mx-1 border border-red-600 hover:bg-gray-700 hover:text-white'
);
const handleClick = (event) => {
event.preventDefault();
const answer = event.target.id;
setCname(
'rounded-full px-2 mx-1 border border-red-600 bg-gray-700 text-white'
);
console.log(answer);
};
const NumberedArray = Array.from(Array(25), (_, i) => i + props.start);
return (
<div className='flex flex-wrap flex-col mx-3 overflow-hidden rounded'>
{NumberedArray.map((number, index) => (
<div
className='flex h-10 w-full items-center justify-center bg-gray-100'
key={index}
>
{number}
<div id='A' className={cName} onClick={handleClick}>
A
</div>
<div id='B' className={cName} onClick={handleClick}>
B
</div>
<div id='C' className={cName} onClick={handleClick}>
C
</div>
<div id='D' className={cName} onClick={handleClick}>
D
</div>
</div>
))}
</div>
);
};
export default Bubbles;
<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>
You have to implement this.
set the default state const [visibleOption,setVisible] = useState('') , const [selctedIndex,setSelctedIndex] = useState('') and In handleClick you have to set the value that you passed and In return apply the ClassName as per Condition wise className={${cName} ${visibleOtion === 'A' && visibleClass}}.
const [visibleOption,setVisible] = useState('')
const [selctedIndex,setSelctedIndex] = useState('')
const visibleClass = 'rounded-full px-2 mx-1 border border-red-600 bg-gray-700 text-white'
const handleClick = (event,value,index) => {
event.preventDefault();
const answer = event.target.id;
setVisible(value)
setSelctedIndex(index.toString())
console.log(answer);
};
//In return written like this
<div id='A' className={`${cName} ${visibleOtion === 'A' && selctedIndex === index.toString() && visibleClass}`} onClick={(e) => handleClick(e, 'A',index)}>
A
</div>
I hope it will work for you.
Break this into 2 separate components. 1) ABCD and 2) Bubbles. In ABCD, map over the answers array. In bubbles, map over the 25 lines of answers. That way you are selecting answers in the ABCD component and don't have the same issues I had.
Pretty new at coding so sorry if my question looks ridiculous...
I am building a menu on my website which is divided in several categories.
In this example, we have Theory and Video categories ( Video only 1 level and Theory is going deeper on 2 levels).
The code below is working for only 1 category at time (thats why the comments).
I would like to ask you how to build a more generic function that can run whatever the array is (for the map function) and avoid this: "Cannot read property 'map' of undefined".
render() {
const theories = this.props.menuTheory;
const videos = this.props.menuVideos;
// const menuTheory = theories.map((theory, i) => (
//
// <div className="nav__category" key={i} onClick={() => this.onSelect(theory.category)}>
//
// <div className={this.state.isSelected === theory.category
// ? "nav__category__dropdown nav__category__dropdown--isSelected"
// : "nav__category__dropdown"}>
// <span className="nav__category__text">{theory.category}</span>
// <span className="checked"><img src={'../static/icons/nav__check.svg'}/></span>
// </div>
// <ul className={this.state.isExpanded === theory.category
// ? "nav__chapterBox"
// : "nav__chapterBox nav__chapterBox--isNotExpanded"}>
// {theory.chapters && theory.chapters.map((chapter, i) => <NavChapter key={i} id={chapter.objectId} title={chapter.name} onClick={() => this.onSelect1(chapter.objectId)}/>)}
// </ul>
// </div>
// ))
const menuVideo = videos.map((video, i) => (
<div className="nav__category" key={i} onClick={() => this.onSelect(video.category)}>
<div className={this.state.isSelected === video.category
? "nav__category__dropdown nav__category__dropdown--isSelected"
: "nav__category__dropdown"}>
<span className="nav__category__text">{video.category}</span>
<span className="checked"><img src={'../static/icons/nav__check.svg'}/></span>
</div>
</div>
))
return (
<nav className="nav__categoryBox">
{/* {menuTheory} */}
{menuVideo}
</nav>
)
}
Thanks.
Okay, so it sounds like you're looking for a way to do conditional rendering in React. What you need to do is add some state to the component. I've added showVideos as a bool (true|false). Use showVideos in the render method to determine which menu to show. You still create the menus using map, but in the return block, check this.state.showVideos to determine which content to return. To get this fully working, you'll also need to add a button that calls toggleMenu onClick that will update your state and switch which menu is being shown.
toggleMenu(){
this.setState({
showVideos: !this.state.showVideos
});
}
render() {
const theories = this.props.menuTheory;
const videos = this.props.menuVideos;
const menuTheory = theories.map((theory, i) => (
<div className="nav__category" key={i} onClick={() => this.onSelect(theory.category)}>
<div className={this.state.isSelected === theory.category ? "nav__category__dropdown nav__category__dropdown--isSelected" : "nav__category__dropdown"}>
<span className="nav__category__text">{theory.category}</span>
<span className="checked"><img src={'../static/icons/nav__check.svg'}/></span>
</div>
<ul className={this.state.isExpanded === theory.category ? "nav__chapterBox" : "nav__chapterBox nav__chapterBox--isNotExpanded"}>
{theory.chapters && theory.chapters.map((chapter, i) => <NavChapter key={i} id={chapter.objectId} title={chapter.name} onClick={() => this.onSelect1(chapter.objectId)}/>)}
</ul>
</div>
))
const menuVideo = videos.map((video, i) => (
<div className="nav__category" key={i} onClick={() => this.onSelect(video.category)}>
<div className={this.state.isSelected === video.category ? "nav__category__dropdown nav__category__dropdown--isSelected" : "nav__category__dropdown"}>
<span className="nav__category__text">{video.category}</span>
<span className="checked"><img src={'../static/icons/nav__check.svg'}/></span>
</div>
</div>
))
return (
<nav className="nav__categoryBox">
<button onClick={this.toggleMenu}>Toggle Menu</button>
{this.state.showVideos ? menuVideo : menuTheory}
</nav>
)
}
I want to conditionally show and hide this button group depending on what is passed in from the parent component which looks like this:
<TopicNav showBulkActions={this.__hasMultipleSelected} />
__hasMultipleSelected: function() {
return false; //return true or false depending on data
}
var TopicNav = React.createClass({
render: function() {
return (
<div className="row">
<div className="col-lg-6">
<div className="btn-group pull-right {this.props.showBulkActions ? 'show' : 'hidden'}">
<button type="button" className="btn btn-default dropdown-toggle" data-toggle="dropdown" aria-expanded="false">
Bulk Actions <span className="caret"></span>
</button>
<ul className="dropdown-menu" role="menu">
<li>Merge into New Session</li>
<li>Add to Existing Session</li>
<li className="divider"></li>
<li>Delete</li>
</ul>
</div>
</div>
</div>
);
}
});
Nothing is happening however, with the {this.props.showBulkActions ? 'show' : 'hidden'}. Am I doing anything wrong here?
The curly braces are inside the string, so it is being evaluated as string. They need to be outside, so this should work:
<div className={"btn-group pull-right " + (this.props.showBulkActions ? 'show' : 'hidden')}>
Note the space after "pull-right". You don't want to accidentally provide the class "pull-rightshow" instead of "pull-right show". Also the parentheses needs to be there.
As others have commented, classnames utility is the currently recommended approach to handle conditional CSS class names in ReactJs.
In your case, the solution will look like:
var btnGroupClasses = classNames(
'btn-group',
'pull-right',
{
'show': this.props.showBulkActions,
'hidden': !this.props.showBulkActions
}
);
...
<div className={btnGroupClasses}>...</div>
As a side note, I would suggest you to try to avoid using both show and hidden classes, so the code could be simpler. Most likely, you don't need to set a class for something to be shown by default.
2021 addendum: for performance improvement, you can look into clsx as an alternative.
If you are using a transpiler (such as Babel or Traceur) you can use the new ES6 "template strings".
Here is the answer of #spitfire109, modified accordingly:
<div className={`btn-group pull-right ${this.props.showBulkActions ? 'shown' : 'hidden'}`}>
This approach allows you to do neat things like that, rendering either s-is-shown or s-is-hidden:
<div className={`s-${this.props.showBulkActions ? 'is-shown' : 'is-hidden'}`}>
you can simply do the following for example.
let classNameDependsOnCondtion = i18n.language == 'en' ? "classname" : "";
className={`flex flex-col lg:flex-row list-none ${classNameDependsOnCondtion }`}
OR
className={`flex flex-col lg:flex-row list-none ${i18n.language == 'en' ? "classname" : ""}`}
You can use here String literals
const Angle = ({show}) => {
const angle = `fa ${show ? 'fa-angle-down' : 'fa-angle-right'}`;
return <i className={angle} />
}
In case you will need only one optional class name:
<div className={"btn-group pull-right " + (this.props.showBulkActions ? "show" : "")}>
Replace:
<div className="btn-group pull-right {this.props.showBulkActions ? 'show' : 'hidden'}">`
with:
<div className={`btn-group pull-right ${this.props.showBulkActions ? 'show' : 'hidden'}`}
Or use npm classnames. It is very easy and useful especially for constructing the list of classes
Expending on #spitfire109's fine answer, one could do something like this:
rootClassNames() {
let names = ['my-default-class'];
if (this.props.disabled) names.push('text-muted', 'other-class');
return names.join(' ');
}
and then within the render function:
<div className={this.rootClassNames()}></div>
keeps the jsx short
2019:
React is lake a lot of utilities. But you don't need any npm package for that. just create somewhere the function classnames and call it when you need it;
function classnames(obj){
return Object.entries(obj).filter( e => e[1] ).map( e=>e[0] ).join(' ');
}
or
function classnames(obj){
return Object.entries(obj).map( ([cls,enb]) => enb? cls: '' ).join(' ');
}
example
stateClass= {
foo:true,
bar:false,
pony:2
}
classnames(stateClass) // return 'foo pony'
<div className="foo bar {classnames(stateClass)}"> some content </div>
Just For Inspiration
declaring helper DOM element and using it native toggle method:
(DOMTokenList)classList.toggle(class,condition)
example:
const classes = document.createElement('span').classList;
function classstate(obj){
for( let n in obj) classes.toggle(n,obj[n]);
return classes;
}
You can use ES6 arrays instead of classnames.
The answer is based on Dr. Axel Rauschmayer article: Conditionally adding entries inside Array and object literals.
<div className={[
"classAlwaysPresent",
...Array.from(condition && ["classIfTrue"])
].join(" ")} />
More elegant solution, which is better for maintenance and readability:
const classNames = ['js-btn-connect'];
if (isSelected) { classNames.push('is-selected'); }
<Element className={classNames.join(' ')}/>
simply use this approach--
<div className={`${this.props.showActions ? 'shown' : 'hidden'}`}>
this is much more neat and clean.
<div className={['foo', condition && 'bar'].filter(Boolean).join(' ')} />
.filter(Boolean) removes "falsey" values from the array. Since class names must be strings, anything other than that would not be included in the new filtered array.
console.log( ['foo', true && 'bar'].filter(Boolean).join(' ') )
console.log( ['foo', false && 'bar'].filter(Boolean).join(' ') )
Above written as a function:
const cx = (...list) => list.filter(Boolean).join(' ')
// usage:
<div className={cx('foo', condition && 'bar')} />
var cx = (...list) => list.filter(Boolean).join(' ')
console.log( cx('foo', 1 && 'bar', 1 && 'baz') )
console.log( cx('foo', 0 && 'bar', 1 && 'baz') )
console.log( cx('foo', 0 && 'bar', 0 && 'baz') )
you can use this:
<div className={"btn-group pull-right" + (this.props.showBulkActions ? ' show' : ' hidden')}>
This is useful when you have more than one class to append. You can join all classes in array with a space.
const visibility = this.props.showBulkActions ? "show" : ""
<div className={["btn-group pull-right", visibility].join(' ')}>
This would work for you
var TopicNav = React.createClass({
render: function() {
let _myClasses = `btn-group pull-right {this.props.showBulkActions?'show':'hidden'}`;
return (
...
<div className={_myClasses}>
...
</div>
);
}
});
Reference to #split fire answer, we can update it with template literals, which is more readable,For reference Checkout javascript template literal
<div className={`btn-group pull-right ${this.props.showBulkActions ? 'show' : 'hidden'}`}>
I have tried to tailored my answer to include all the best possible solution in the post.
There are many different ways of getting this done.
1. Inline inside the class
<div className={`... ${this.props.showBulkActions ? 'show' : 'hidden'}`}>
...
</div>
2. Using the values
var btnClass = classNames(
...
{
'show': this.props.showBulkActions,
'hidden': !this.props.showBulkActions
}
);
3. Using a variable
let dependentClass = this.props.showBulkActions ? 'show' : 'hidden';
className={`... ${dependentClass }`}
4. Using clsx
<div className={clsx('...',`${this.props.showBulkActions ? 'show' : 'hidden'}`)}>
...
</div>
You can use this npm package. It handles everything and has options for static and dynamic classes based on a variable or a function.
// Support for string arguments
getClassNames('class1', 'class2');
// support for Object
getClassNames({class1: true, class2 : false});
// support for all type of data
getClassNames('class1', 'class2', null, undefined, 3, ['class3', 'class4'], {
class5 : function() { return false; },
class6 : function() { return true; }
});
<div className={getClassNames('show', {class1: true, class2 : false})} /> // "show class1"
Based on the value of this.props.showBulkActions you can switch classes dynamically as follows.
<div ...{...this.props.showBulkActions
? { className: 'btn-group pull-right show' }
: { className: 'btn-group pull-right hidden' }}>
I would like to add that you can also use a variable content as a part of the class
<img src={src} alt="Avatar" className={"img-" + messages[key].sender} />
The context is a chat between a bot and a user, and the styles change depending of the sender, this is the browser result:
<img src="http://imageurl" alt="Avatar" class="img-bot">
A function to return the correct class based on a param (if present)
getClass(param){
let podClass = 'classA'
switch(param.toLowerCase()){
case 'B':
podClass = 'classB'
break;
case 'C':
podClass = 'classC'
break;
}
return podClass
}
Now just invoke this function from the div where the corresponding class is to be applied.
<div className={anyOtherClass + this.getClass(param)}
I successfully used this logic to apply the correct color to my bootstrap table rows.
<div className={"h-3 w-3 rounded-full my-auto " + (index.endDate ==="present"? "bg-green-500":"bg-red-500")}></div>
Don't Forget to add an extra space after the static class names.