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
},
})
Related
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 };
});
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!
Fairly new with react. I'm creating a dropdown button for a Gatsby project. The button toggle works, but I'm having trouble getting the selected value to the parent where I need it.
-Tried lifting the state up, but this resulted in the button not appearing at all. I was a bit confused here so maybe I was doing something wrong.
-Also tried using refs although I wasn't sure if this was the right use case, it worked, however it seems the value is grabbed before it's updated in the child component and I'm not sure how to change or work around this. (the code is currently set up for this)
Are either of these options right? or could anybody steer me in the right direction, thanks.
Dropdown in parent:
this.dropdownRef1 = React.createRef();
componentDidUpdate(){
console.log("Color Option:" + this.dropdownRef1.current.state.ColorOption)
}
<DropdownBtn ref={this.dropdownRef1} mainText="Color" options={this.props.pageContext.colors || ['']} />
DropdownBtn:
export default class refineBtn extends React.Component {
constructor(props) {
super(props);
}
state = {
open: false,
[this.props.mainText + "Option"]: "all",
};
dropdownBtnToggle = () => {
this.setState((prevState)=> {
return{open: !prevState.open};
});
};
optionClickHandler = (option) => {
this.setState(() => {
console.log(this.props.mainText + " updated to " + option)
return {[this.props.mainText + "Option"] : option}
});
};
render(){
const options = this.props.options
console.log("open: " + this.state.open)
return(
<div>
<button onClick={this.dropdownBtnToggle} >
{this.props.mainText}:
</button>
<div className={this.state.open ? 'option open' : "option"} >
<p key={"all"} onClick={() => this.optionClickHandler("all")}> all</p>
{options.map(option => (
<p key={option} onClick={() => this.optionClickHandler(option)}>{option}</p>
))}
</div>
</div>
);
}
}
You can respond to selection by allowing your component to accept a callback.
class MyComponent extends React.Component {
constructor(props) {
super(props)
this.state = {open: false, value: ''}
}
render() {
return (
<div>
<div onClick={() => this.setState({open: true})}>{this.state.value}</div>
<div style={{display: this.state.open ? 'block' : 'none'}}>
{this.props.options.map((option) => {
const handleClick = () => {
this.setState({open: false, value: option})
this.props.onChange(option)
}
return (
<div key={option} onClick={handleClick} className={this.state.value === option ? 'active' : undefined}>{option}</div>
)
})}
</div>
</div>
)
}
}
<MyComponent onChange={console.log} options={...}/>
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
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'));