How to control element with hover (display and hide) in dynamic way - javascript

I create a box/card with a checkbox hidden. So, if I hover this card, the checkbox displays. If the checkbox is checked it must be always visible. If checked is false, it turn back to default behavior.
I'm using a hover propriety to display my checkbox:
CSS using material ui:
hideCheckbox: {
display: 'none',
},
showCheckbox: {
display: 'initial',
},
My main class:
export class Item extends Component {
state = {
isHovering: true,
checkboxChecked: false,
}
handleGetCardSelected = (id, checked) => {
//Here I set isHovering to display my checkbox
//checkboxChecked is a control variable to always display the checkbox if it's checked
if(checked){
this.setState({
isHovering: !this.state.isHovering,
checkboxChecked: true,
})
} else {
this.setState({
checkboxChecked: false,
})
}
}
handleMouseHover = () => {
if(!this.state.checkboxChecked){
this.setState(this.toggleHoverState);
}
}
toggleHoverState = (state) => {
return {
isHovering: !state.isHovering,
};
}
return(
<div
onMouseEnter={this.handleMouseHover}
onMouseLeave={this.handleMouseHover}
>
<div className={`
${this.state.isHovering && classes.hideCheckbox }
${this.state.checkboxChecked && classes.showCheckbox}
`}>
<CheckBoxCard handleGetCardSelected={this.handleGetCardSelected}/>
</div>
</div>
<div
onMouseEnter={this.handleMouseHover}
onMouseLeave={this.handleMouseHover}
>
<div className={`
${this.state.isHovering && classes.hideCheckbox }
${this.state.checkboxChecked && classes.showCheckbox}
`}>
<CheckBoxCard handleGetCardSelected={this.handleGetCardSelected}/>
</div>
</div>
<div
onMouseEnter={this.handleMouseHover}
onMouseLeave={this.handleMouseHover}
>
<div className={`
${this.state.isHovering && classes.hideCheckbox }
${this.state.checkboxChecked && classes.showCheckbox}
`}>
<CheckBoxCard handleGetCardSelected={this.handleGetCardSelected}/>
</div>
</div>
)
}
My CheckboxCard:
import React from 'react';
import { makeStyles, withStyles } from '#material-ui/core/styles';
import { Checkbox } from '#material-ui/core';
const GreenCheckbox = withStyles({
root: {
color: '#43a04754',
'&$checked': {
color: '#43a047',
},
'&:hover': {
color: '#43a047',
backgroundColor: 'initial',
},
},
checked: {},
})(props => <Checkbox color="default" {...props} />);
export default function CheckBoxCard(props){
const [state, setState] = React.useState({
idItem: false,
});
const handleCheckbox = name => event => {
setState({ ...state, [name]: event.target.checked });
let id = name
let checked = event.target.checked
props.handleGetCardSelected(id, checked)
};
return(
<GreenCheckbox
checked={state.idItem}
onChange={handleCheckbox('idItem')}
value="idItem"
inputProps={{
'aria-label': 'primary checkbox',
}}
/>
);
}
By default, my checkbox is hidden because my state isHovering is true, so the css variable hideCheckbox ('display: none') is set.
If I hover the element, is called handleMouseHover and the checkbox is displayed!
If I checked my checkbox, it must be displayed always! I create a checkboxChecked for control it. If true i'm always displaying my checkbox! But isn't works perfectly, because checkboxChecked must be dynamic per element, not unique.
So, how it must be works?
-> All checkbox hidden (ok)
-> Hover card and checkbox appears (ok)
-> If checkbox is checked it must ignore hover and always be displayed
I uploaded my code at sandbox, pls click here to see how it works actually
How can I do that?

Give CSS to box display none as default. maintain 2 state isHovered and isChecked for any of this state is true you can give display block class to it. It is better to split card to a sperate component so that an easily maintain the state easily.
working code is uploaded to sandbox: Please accept this answer if this solution resolved your issue

Related

Unfocusing dropdown menu sets its state as undefined

I'm making a dropdown menu that allows user to set a state and then see the page corresponding to chosen values.
I isolated my code to fully reproduce the issue both in text and in this [CodeSandbox]
Desired baheviour - Open menu, set state using its componets, close menu and keep the state.
Current behaviour - Open menu, set state using its components, close menu and state is set to undefined.
I track the changes to the state in the console and can clearly see that adding items to filter is seen in the updated state every time. However when I close the menu the state changes to undefined and the state is unsuable for my needs.
How do I change the code so the state persists when the menu is closed?
Thanks in advance for your time!
import React from "react";
import { default as ReactSelect } from "react-select";
import { components } from "react-select";
export default function BettingDeck(props) {
const sportsOptions = [
{ value: "soccer", label: "Soccer" },
{ value: "dota", label: "Dota 2" },
{ value: "tennis", label: "Tennis" },
{ value: "csgo", label: "CS:GO" }
];
const Option = (props) => {
return (
<div>
<components.Option {...props}>
<input
type="checkbox"
checked={props.isSelected}
onChange={() => null}
/>{" "}
<label>{props.label}</label>
</components.Option>
</div>
);
};
const [sportsSelectorState, setSportsSelectorState] = React.useState({
optionSelected: [],
isFocused: true
});
function handleChange(selected) {
setSportsSelectorState(() => {
return { optionSelected: selected };
});
}
console.log(sportsSelectorState.optionSelected);
return (
<>
<div className="betting-deck-container">
<div className="betting-deck-head-container">
<div className="betting-deck-title">Betting Deck</div>
{/* <SportsSelector /> */}
<span
class="d-inline-block"
data-toggle="popover"
data-trigger="focus"
data-content="Please selecet account(s)"
onBlur={() => {
setSportsSelectorState({ isFocused: false });
}}
onFocus={() => {
setSportsSelectorState({ isFocused: true });
}}
style={
sportsSelectorState.isFocused ? { zIndex: 1 } : { zIndex: 0 }
}
>
<ReactSelect
options={sportsOptions}
isMulti
closeMenuOnSelect={false}
hideSelectedOptions={false}
components={{
Option
}}
onChange={handleChange}
allowSelectAll={true}
value={sportsSelectorState.optionSelected}
placeholder="Select sports to filter"
menuPortalTarget={document.body}
classNamePrefix="mySelect"
/>
</span>
</div>
</div>
</>);}
Every time you set the state, you overwrite it with a new object.
So this:
setSportsSelectorState(() => {
return { optionSelected: selected };
});
practically removes isFocused from the object.
And this removes optionSelected:
setSportsSelectorState({ isFocused: true });
So to always preserve the entire object, spread the previous state (object) into the new and only overwrite the relevant property:
// The parameter in the callback function (prev)
// always holds the previous state, or should
// I say the state as it currently is
// before you change it.
setSportsSelectorState((prev) => {
return { ...prev, isFocused: true };
});
// or
setSportsSelectorState((prev) => {
return { ...prev, optionSelected: selected };
});

Focus on div and his children react

I am working at a project, in React, class components, which is like this:
A navbar with a dropdown and <section> with more cards. When the dropdown it is open the <section> has a background color which is on top of all other elements. To close the dropdown, you click outside of it.
onMouseEnter each card, more data is displayed(display:none to display:flex), and onMouseLeave each card, the data is hidden (display:flex to dispaly:none).
The problem is that, if the mouse pointer it is on a card, after the dropdown closes, the extra data stays hidden.
This is the <div> which is making troubles:
<div
className={
bla.type === "blla"
? "flex"
: bla.type !== "blla" &&
this.props.showOtherAttr === false
? "hidden"
: "flex"
}>
And this is a fragment of the parrent component:
class Article extends Component {
state = {
showOtherAttr: false,
isFocused: false
};
render() {
const dispalyOtherAttr = () => {
if (this.state.showOtherAttr === false) {
this.setState({
showOtherAttr: true
});
} else if (this.state.isFocused === true){
this.setState({
showOtherAttr: true
})
}
};
const hideOtherAttr = () => {
if (this.state.showOtherAttr === true) {
this.setState({
showOtherAttr: false,
});
}
}
const handleFocus = () => {
this.setState({
isFocused: true
})
}
return (
<div
className="card"
onMouseEnter={(e) => {
dispalyOtherAttr();
this.props.reduxAction
}}
onFocus = {()=>handleFocus()}
onMouseLeave={()=>{hideOtherAttr(); this.props.resetStateReduxAction()}
>
<ComponentWithDiv
showOtherAttr={this.state.showOtherAttr}>
</div>
);
}
}
export default Article
I tried to use onFocus event on card's outer <div> but nothing.
I tried to override CSS class .hidden{display: none} like this:
.card:focus div.hidden {
display: flex
}
and like this:
.card:focus:nth-child(3){
display: flex;
}
I searched in react synthetic events, but there is nothing like "mouseAlreadyHere".
How to make it to display extra data and to call this.props.reduxAction, after I close the dropdown and the mouse it is inside the card?
Thank you very much!

How can I display dynamic state in react

I create a state dynamically, but I need use this state in my render.
See my issue:
handleChange = (name, checked) => {
this.setState({
[name]: checked,
})
}
So, in my render how can I display my [name] state? (I don't know his name, since it's dynamic)
render(){
console.log(this.state....?)
return(
<p>Hello</p>
)
}
My function handleChange is called when I checked my checbkox. So, if I have 5, 10, 20 checkboxes how can I display my [name] state?
----> UPDATE - FULL CODE <----
I'm using a hover propriety to display my checkbox:
CSS using material ui:
hideCheckbox: {
display: 'none',
},
showCheckbox: {
display: 'initial',
},
My main class:
export class Item extends Component {
state = {
isHovering: true,
checkboxChecked: false,
}
handleGetCardSelected = (id, checked) => {
//Here I set isHovering to display my checkbox
//checkboxChecked is a control variable to always display the checkbox if it's checked
if(checked){
this.setState({
isHovering: !this.state.isHovering,
checkboxChecked: true,
})
} else {
this.setState({
checkboxChecked: false,
})
}
}
handleMouseHover = () => {
if(!this.state.checkboxChecked){
this.setState(this.toggleHoverState);
}
}
toggleHoverState = (state) => {
return {
isHovering: !state.isHovering,
};
}
return(
<div
onMouseEnter={this.handleMouseHover}
onMouseLeave={this.handleMouseHover}
>
<div className={`
${this.state.isHovering && classes.hideCheckbox }
${this.state.checkboxChecked && classes.showCheckbox}
`}>
<CheckBoxCard handleGetCardSelected={this.handleGetCardSelected}/>
</div>
</div>
<div
onMouseEnter={this.handleMouseHover}
onMouseLeave={this.handleMouseHover}
>
<div className={`
${this.state.isHovering && classes.hideCheckbox }
${this.state.checkboxChecked && classes.showCheckbox}
`}>
<CheckBoxCard handleGetCardSelected={this.handleGetCardSelected}/>
</div>
</div>
<div
onMouseEnter={this.handleMouseHover}
onMouseLeave={this.handleMouseHover}
>
<div className={`
${this.state.isHovering && classes.hideCheckbox }
${this.state.checkboxChecked && classes.showCheckbox}
`}>
<CheckBoxCard handleGetCardSelected={this.handleGetCardSelected}/>
</div>
</div>
)
}
My CheckboxCard:
import React from 'react';
import { makeStyles, withStyles } from '#material-ui/core/styles';
import { Checkbox } from '#material-ui/core';
const GreenCheckbox = withStyles({
root: {
color: '#43a04754',
'&$checked': {
color: '#43a047',
},
'&:hover': {
color: '#43a047',
backgroundColor: 'initial',
},
},
checked: {},
})(props => <Checkbox color="default" {...props} />);
export default function CheckBoxCard(props){
const [state, setState] = React.useState({
idItem: false,
});
const handleCheckbox = name => event => {
setState({ ...state, [name]: event.target.checked });
let id = name
let checked = event.target.checked
props.handleGetCardSelected(id, checked)
};
return(
<GreenCheckbox
checked={state.idItem}
onChange={handleCheckbox('idItem')}
value="idItem"
inputProps={{
'aria-label': 'primary checkbox',
}}
/>
);
}
By default, my checkbox is hidden because my state isHovering is true, so the css variable hideCheckbox ('display: none') is set.
If I hover the element, is called handleMouseHover and the checkbox is displayed!
If I checked my checkbox, is set checkboxChecked for true and now I'm always displaying my checkbox! But, if I've two or more elements, all checkbox is displayed, because checkboxChecked is an unique element!
So, my checkboxChecked must be dynamic and per item! In this way, if is checked, only this checkbox will be displayed. The others no!
For first element: ${this.state.checkboxCheckedITEM1 && classes.showCheckbox}
For second element: ${this.state.checkboxCheckedITEM2 && classes.showCheckbox}
For second element: ${this.state.checkboxCheckedITEM3 && classes.showCheckbox}
I update my code in sandbox: https://codesandbox.io/embed/material-demo-16t91?fontsize=14
How can I do that?
So, if I have 5, 10, 20 checkboxes how can I display my [name] state?
I think the smart way to do this is to nest components. Have an abstract parent component, that only renders the child component once the name is available. By using this pattern your problem can be solved.
Check out this helpful article
You can filter your object to get only checked names.
Object.keys(this.state).filter(name => this.state[name]);
If you have a finite list of checkbox component
render() {
return (
<div>
<Checkbox
handleChange={() => this.handleChange('abc', !this.state[abc])}
className={this.state['abc'] ? 'checked-class' : '' }
/>
</div>
)
}
In another situation whereby you render a list of dynamic checkbox.
componentDidMount() {
//Calling API to return dynamic list of checkboxes, and dump them into a state
this.setState({ listOfCheckboxes });
}
Then in your render method, you will be using .map function
render() {
return (
<div>
{
this.state.listOfCheckboxes.map(x => (
<Checkbox
handleChange={() => this.handleChange(x.name, !this.state[x.name]) }
className={this.state[x.name] ? 'checked-class' : '' }
/>))
}
</div>
)
}
By doing so :
this.state[name]
You can look at Object.keys(), but if in future you would need to handle more than one state, iteration does not seem to solve the problem.
You could also give it a prefix [name_${name}]: checked, but would not recommend that at all.
If it is not a problem, use an object and you will have full controll over it.
this.setState({
something: {
name,
checked
},
})

check function is called in child component

I am trying to make a custom dropdown but with custom children component. Within the children custom component, there's an onChange event.
The problem now is whenever I trigger the onChange which is for the checkbox, the dropdown is closed.
https://codesandbox.io/s/lr677jv7l7
Partial code
render() {
const { className, onOpen, children } = this.props
const { openItems, selectedItem } = this.state
return (
<div className={classnames('customDropdown', className)}>
<div tabIndex="1"
onBlur={() => { this.setState({ openItems: false }) }}
onFocus={() => { this.setState({ openItems: true }); onOpen && onOpen() }}>
<button className="btn">
{selectedItem}
</button>
<div className={classnames('items', { 'show': openItems === true, 'hide': openItems === false })}>
{children && children}
</div>
</div>
</div>
)
}
You need to get rid of following line:
onBlur={() => { this.setState({ openItems: false }) }}
It basically says that when your div wrapping the button loses focus (eg when you click the checkbox) it should set the state.openItems variable to false and therefore it closes the dropdown.
Edit:
Check out working example here: https://codesandbox.io/s/jnq2rqwr53.
Basically use onClick instead of blur and then you add click event to your document, so anytime user clicks anywhere on the document it calls your hide method and closes the modal. This way the selected checkbox gets checked, but if you want to dropdown to stay open after the selection you'll need to somehow tell the hide function not to execute if user clicked on the checkbox. I did it using ids and simple condition guard at the beginning of the hide method.
Code looks like this:
Hello.js
import React, { Component } from 'react';
import classnames from 'classnames'
export default class CustomDropdown extends Component {
constructor() {
super()
this.state = {
openItems: false,
selectedItem: 'Please select'
}
this.show = this.show.bind(this);
this.hide = this.hide.bind(this);
}
show() {
this.setState({openItems: true});
document.addEventListener("click", this.hide);
}
hide(e) {
if (e.target.id === "1" || e.target.id === "2") {
return false;
}
this.setState({openItems: false});
document.removeEventListener("click", this.hide);
}
render() {
const { className, onOpen, children } = this.props
const { openItems, selectedItem } = this.state
return (
<div className={classnames('customDropdown', className)}>
<div tabIndex="1">
<button className="btn" onClick={this.show}>
{selectedItem}
</button>
<div className={classnames('items', { 'show': openItems === true, 'hide': openItems === false })}>
{children && children}
</div>
</div>
</div>
)
}
}
index.js
import React, { Component } from 'react';
import { render } from 'react-dom';
import Hello from './Hello';
import './styles.css';
const styles = {
fontFamily: 'sans-serif',
textAlign: 'center'
};
class App extends Component {
constructor() {
super()
}
changeCheckbox = () => {
console.log('something')
}
render(){
return(
<div style={ styles }>
<Hello>
<div>
my checkbox 1
<input type="checkbox" onChange={this.changeCheckbox} id="1" />
</div>
<div>
my checkbox 2
<input type="checkbox" onChange={this.changeCheckbox} id="2" />
</div>
</Hello>
</div>
)
}
}
render(<App />, document.getElementById('root'));

Hide element onClick React based on Id

I have a list of 3 filters that will show based on their id, when clicked it will show the filters matching the id but I would like to hide it if clicked again. So if Filter 1 is clicked it should show and then if clicked again it should hide
https://www.webpackbin.com/bins/-KpFE0uZN94N_RY2lavn
import React, { Component } from 'react'
export default class Catalogue extends Component {
constructor(props) {
super(props)
this.state = {
filterListShow: false,
active: false
}
this.handleShowFilterList = this.handleShowFilterList.bind(this)
}
// Show Filter checklist onClick
handleShowFilterList(id) {
this.setState({
filterListShow: id,
active: false })
}
render() {
const { filterListShow } = this.state
let test = ''
if (filterListShow === 1) {
test = (<div>show 1</div>)
}
else if (filterListShow === 2) {
test = (<div>show 2{console.log(2)}</div>)
}
else if (filterListShow === 3) {
test = (<div>show 3{console.log(3)}</div>)
}
return (
<div >
<div onClick={()=> this.handleShowFilterList(1)}>
Show Filter 1
</div>
<div onClick={()=> this.handleShowFilterList(2)}>
Show Filter 2
</div>
<div onClick={()=> this.handleShowFilterList(3)}>
Show Filter 3
</div>
{test}
</div>
)
}
}
Just add another check in the onClick handler to check whether the current state is the same as the id of the element clicked,
// Show Filter checklist onClick
handleShowFilterList(id) {
if(this.state.filterListShow !== id) {
this.setState({
filterListShow: id,
active: false })
} else {
this.setState({filterListShow: false})
}
}
DEMO
Simply put the condition in handleShowFilterList function, if same item has been clicked again then reset the state value of filterListShow variable.
Like this:
handleShowFilterList(id) {
this.setState(prevState => ({
//if same then reset otherwise assign new id
filterListShow: prevState.filterListShow == id ? false : id,
active: false
}))
}
Working Code.

Categories

Resources