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'));
Related
I have an example of a toggle that hide/display content.
I used https://www.npmjs.com/package/react-animated-css and it worked perfectly when displaying the content, meaning that after showing the content the animation is playing.
Now that I press the toggle button, the content instantly vanishes without animation.
I checked in the console and the class for the animationOut is working, but it seems that the content closes before the animation have the time to play, so it's hidden.
How to fix this issue ?
The working code : https://stackblitz.com/edit/react-sorgz5?file=src/App.js
import React, { Component } from "react";
import ContentComponent from "./content.js";
import { Animated } from "react-animated-css";
export default class toggleComponent extends React.Component {
constructor() {
super();
this.state = {
isShowBody: false
};
}
handleClick = event => {
this.setState({ isShowBody: !this.state.isShowBody });
};
checkbox = () => {
return (
<div>
<span className="switch switch-sm">
<label>
<input
type="checkbox"
name="select"
onClick={this.handleClick.bind(this)}
/>
<span />
</label>
</span>
</div>
);
};
render() {
return (
<div>
{this.checkbox()}
<Animated
animationIn="bounceInLeft"
animationOut="fadeOut"
isVisible={this.state.isShowBody}
>
<div>{this.state.isShowBody && <ContentComponent />}</div>
</Animated>
</div>
);
}
}
Solved. I needed to remove this.state.isShowBody in <div>so the condition of the visibility is controller by isVisible.
<Animated
animationIn="bounceInLeft"
animationOut="fadeOut"
isVisible={this.state.isShowBody}
>
<div>{<ContentComponent />}</div>
</Animated>
I have three buttons that when clicking show and individual div but this is done in reactjs
import React, { Component } from 'react';
export class ModeExtended extends Component {
constructor() {
super();
this.busButton = this.busButton.bind(this);
this.trainButton = this.trainButton.bind(this);
this.tramButton = this.tramButton.bind(this);
this.state = {
isHidden: false,
}
}
busButton(){
console.log('Bus Button Was Pressed');
this.setState((prevState) => {
return{
isHidden: !prevState.isHidden
};
});
}
trainButton(){
console.log('Train Button Was Pressed');
this.setState((prevState) => {
return{
isHidden: !prevState.isHidden
};
});
}
tramButton(){
console.log('Tram Button Was Pressed');
this.setState((prevState) => {
return{
isHidden: !prevState.isHidden
};
});
}
render() {
return (
<div>
<h5>Mode Extended</h5>
<button onClick={this.busButton}>Bus</button>
<button onClick={this.trainButton}>Train</button>
<button onClick={this.tramButton}>Tram</button>
{this.state.isHidden && (
<div>
<h6>You can show Bus Data Now....</h6>
</div>
)}
{this.state.isHidden && (
<div>
<h6>You can show Train Data Now....</h6>
</div>
)}
{this.state.isHidden && (
<div>
<h6>You can show Tram Data Now....</h6>
</div>
)}
</div>
)
}
}
export default ModeExtended
When I click any of the buttons it shows all bus, tram and train data - how do I get them to just show one thing at a time and making sure that the other states are closed. I am really missing something here and need a pointer or two or three…
How can I add an ID to make each button open separate from each other and when one is clicked how can I close the rest of the divs - or open state, I am so lost here. Please help me out.
Cheers as always!
Here is a REPL of my code:
You need to have 3 different isHidden properties to control your divs. You can do it like this:
this.state = {
isHiddenBus: false,
isHiddenTrain: false,
isHiddenTram: false,
}
and then in your render like this:
{this.state.isHiddenBus && (
<div>
<h6>You can show Bus Data Now....</h6>
</div>
)}
{this.state.isHiddenTrain && (
<div>
<h6>You can show Train Data Now....</h6>
</div>
)}
{this.state.isHiddenTram && (
<div>
<h6>You can show Tram Data Now....</h6>
</div>
)}
also your buttons have to change to state accordingly to this.
busButton(){
console.log('Bus Button Was Pressed');
this.setState((prevState) => {
return{
isHiddenBus: !prevState.isHiddenBus
isHiddenTram: false
isHiddenTrain: false
};
});
}
trainButton(){
console.log('Train Button Was Pressed');
this.setState((prevState) => {
return{
isHiddenTrain: !prevState.isHiddenTrain
isHiddenBus: false
isHiddenTram: false
};
});
}
tramButton(){
console.log('Tram Button Was Pressed');
this.setState((prevState) => {
return{
isHiddenTram: !prevState.isHiddenTram
isHiddenTrain: false
isHiddenBus: false
};
});
}
you can do somthing like this:
import React, { Component } from 'react';
export class ModeExtended extends Component {
constructor() {
super();
this.state = {
curDivIndex:0,//currently visible div index
// isHidden: false,
}
}
renderDiv=()=>{
switch(this.state.curDivIndex){
case 1:return <div> <h6>You can show Bus Data Now....</h6> </div>
case 2:return <div> <h6>You can show Train Data Now....</h6> </div>
case 3:return <div> <h6>You can show Tram Data Now....</h6> </div>
}
return null
}
setVisibleDiv=(index)=>{
this.setState({curDivIndex:index})
}
render() {
return (
<div>
<h5>Mode Extended</h5>
<button onClick={()=>{this.setVisibleDiv(1)} }>Bus</button>
<button onClick={()=>{this.setVisibleDiv(2)}}>Train</button>
<button onClick={()=>{this.setVisibleDiv(3)}}>Tram</button>
{this.renderDiv()}
</div>
)
}
}
export default ModeExtended
EDIT
you want to have three different buttons, on click of each certain div
needs to be visible.
you can achieve this by maintaining the index of currently visible div.
when user clicks any button you have to set the index of div to be visible
which in the above code is achieved by using setVisibleDiv(index) call.
and you can at rendering time use curDivIndex to decide visible div.
Or you can achieve this by declaring state properties for all case:
this.state = {
hiddenBus: false,
hiddenTrain: false,
hiddenTram: false,
}
providing a name attribute to your buttons like so:
<button name="hiddenBus" onClick={toggleDisplay}>Bus</button>
<button name="hiddenTrain" onClick={toggleDisplay}>Train</button>
<button name="hiddenBus" onClick={toggleDisplay}>Tram</button>
then by defining the toggleDisplay function to toggle their display:
toggleDisplay = (event) => {
event.preventDefault(); // default behavior of a clicked button is to send a form so let's prevent this
const { name } = event.target; // find the clicked button name value
this.setState((prevState => ({
[name]: !prevState[name],
}));
}
Setting[name] enables us to target the state prop via the nameattribute value and update it based on the previous state.
Try this
import React, { Component } from "react";
export default class Create extends Component {
constructor(props) {
super(props);
this.state = {
currentBtn: null
};
}
clickedButton = e => {
this.setState({ currentBtn: e.target.id });
};
showDivElem = () => {
const { currentBtn } = this.state;
switch (currentBtn) {
case "A":
return <div>A</div>;
break;
case "B":
return <div>B</div>;
break;
case "C":
return <div>C</div>;
break;
default:
return <div>ABC</div>;
break;
}
};
render() {
console.log(this.state.currentBtn);
return (
<div>
<button id="A" onClick={e => this.clickedButton(e)}>
A
</button>
<button id="B" onClick={e => this.clickedButton(e)}>
B
</button>
<button id="C" onClick={e => this.clickedButton(e)}>
C
</button>
{this.showDivElem()}
</div>
);
}
}
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
},
})
So I have this big messy component, I will try to slim it down, however keep most of it since I am unsure at this point what could be cause.
The issue is, that the game works as expected. When it is time for the modal to render, it appears at the bottom left of the page, with no styling floating left. The functionality however works as expected, the buttons work and it displays the raw content.
import { Modal } from 'antd';
//rest of imports
const initialState = {
visible: false,
streak: 0,
score: 0,
turn: 0,
previousPicks: [],
result: { result: "", player: "", computer: "" }
};
class Game extends React.Component {
constructor(props) {
super(props);
this.turnLimit = 10;
this.state = initialState;
}
componentWillUnmount() {
this.setState(initialState)
}
updateScore = () => {
//handles score
}
updatePreviousPicks = () => {
//update game data
}
onClickHandler = async (choice) => {
//fetching data from backend
self.showModal();
}
getAIResult = () => {
//
}
showModal = () => {
if (this.state.turn === 10) {
this.setState({
visible: true,
});
}
}
handleOk = () => {
this.setState(initialState)
}
handleCancel = () => {
this.setState(initialState)
}
render() {
return (
<div>
<div>
<Modal
title="Basic Modal"
centered={true}
visible={this.state.visible}
onOk={this.handleOk}
onCancel={this.handleCancel}></Modal>
</div>
<div className="container">
<div id="rockDiv" className={`choice`} onClick={() => this.onClickHandler("rock")}>
<Choices choice="rock"></Choices>
</div>
<div id="paperDiv" className={`choice`} onClick={() => this.onClickHandler("paper")}>
<Choices choice="paper"></Choices>
</div>
<div id="scissorsDiv" className={`choice`} onClick={() => this.onClickHandler("scissors")}>
<Choices choice="scissors"></Choices>
</div>
<Score score={this.state.score} bonus={this.state.streak} turn={this.state.turn} />
<div id="PlayerResult" className={this.state.result.result} >
{this.state.turn >= 1 ? <p>You</p> : <p></p>}
<Answer choice={`${this.state.result.player}`} />
</div>
<div id="AIResult" className={this.getAIResult()} >
{this.state.turn >= 1 ? <p>AI</p> : <p></p>}
<Answer choice={`${this.state.result.computer}`} />
</div>
</div>
</div>
)
}
}
export default Game
I have tried removing all CSS from the component, and still the modal does not show with the default antd design?
As I understand that current style you have doesn't like example of Antd.
Missing is you didn't import styles of Antd like this.
import { Modal, Button } from "antd";
import "antd/dist/antd.css";
Just need import style you will have what you need.
You can check my example here https://codesandbox.io/embed/8lr93mw8yj
<Modal
title="Basic Modal"
centered="true"
visible={this.state.visible}
onOk={this.handleOk}
onCancel={this.handleCancel}></Modal>
You do not need to wrap the "true" in brackets here as you are not calling a variable.
I'm having a bit of a head ache trying to figure out the React way of implementing this.
I have a Searches component which houses SearchItems, when an item is clicked among other things I need to set it's state to active to that it gets the correct CSS, I managed to get this working fine but how would I go about removing the active state from the others?
I was thinking that I could pass down a function from the top level component that would take the ID of the search, when clicked it'd zip through SearchItems and change their state to either true/false depending on which ID it was?
Code below!
Top level component:
import React from "react";
import {Link} from "react-router";
import Search from "./Search";
export default class Searches extends React.Component {
constructor(){
super();
this.state = {
searches : [
{
id : "2178348216",
searchName: "searchName1",
matches: "5"
},
{
id : "10293840132",
searchName: "searchName2",
matches: "20"
}
]
};
}
render() {
const { searches } = this.state;
const SearchItems = searches.map((search) => {
return <Search key={search.id} {...search}/>
})
return (
<div> {SearchItems} </div>
);
}
}
Search items component
export default class Search extends React.Component {
constructor() {
super();
// Set the default panel style
this.state = {
panelStyle: { height: '90px', marginBottom: '6px', boxShadow: '' },
selected: false
}
}
isActive(){
return 'row panel panel-success ' + (this.state.selected ? 'active' : 'default');
}
viewNotifications(e){
this.setState({selected: true});
}
render() {
const { id, searchName, matches } = this.props;
const buttonStyle = {
height: '100%',
width: '93px',
backgroundColor: '#FFC600'
}
return (
<div style={this.state.panelStyle} className={this.isActive()}>
<div class="col-xs-10">
<div class="col-xs-7">
Search Name: {searchName}
</div>
<div class="col-xs-7">
Must Have: PHP, MySQL
</div>
<div class="col-xs-7">
Could Have: AngularJS
</div>
</div>
<button type="button" onClick={this.viewNotifications.bind(this)} style={buttonStyle} class="btn btn-default btn-lg"> {matches} </button>
</div>
);
}
}
I think you don't need the state in the child component at all. In fact is a good idea to avoid having state in most components so they are easy to reason and reuse.
I would leave all the state only on the parent component in this case.
TOP Component:
import React from "react";
import Search from "./search";
export default class Searches extends React.Component {
constructor(){
super();
this.state = {
searches : [
{
id : "2178348216",
searchName: "searchName1",
matches: "5"
},
{
id : "10293840132",
searchName: "searchName2",
matches: "20"
}
],
activeElement : null
};
}
_onSearchSelect(searchId) {
this.setState({'activeElement': searchId})
}
render() {
const { searches, activeSearchId } = this.state;
const SearchItems = searches.map((search) => {
return <Search key={search.id} {...search}
isActive={search.id === activeElement}
onSelect={this._onSearchSelect.bind(this)} />
})
return (
<div> {SearchItems} </div>
);
}
}
CHILD Component:
import React from "react";
export default class Search extends React.Component {
_getPanelClassNames() {
const { isActive } = this.props
return 'row panel panel-success ' + (isActive ? 'active' : 'default')
}
_onSelect() {
const { id, onSelect } = this.props;
onSelect(id)
}
render() {
const { searchName, matches } = this.props;
const panelStyle = { height: '90px', marginBottom: '6px', boxShadow: '' }
const buttonStyle = {
height: '100%',
width: '93px',
backgroundColor: '#FFC600'
}
return (
<div style={panelStyle} className={this._getPanelClassNames()}>
<div className="col-xs-4">
Search Name: {searchName}
</div>
<div className="col-xs-3">
Must Have: PHP, MySQL
</div>
<div className="col-xs-3">
Could Have: AngularJS
</div>
<div className="col-xs-2">
<button type="button" onClick={this._onSelect.bind(this)}
style={buttonStyle} className="btn btn-default btn-lg"
>
{matches}
</button>
</div>
</div>
);
}
}
You can also see it running in Plunker: https://plnkr.co/edit/sdWzFedsdFx4MpbOuPJD?p=preview
Ok it turns out this is simpler than I thought and is simply a case of understanding how react works(and not getting confused) .
When you have a top level component you pass it's state via props to children, when you update the state in the top level component it'll pass that down to the children and you can use componentWillReceiveProps to take action.
I added a function to my top level component called updateActiveSearch which simply sets the state of the TOP level component I then passed the activeElement state as a prop to the child Elements along with the function. When a child element calls this function to set itself as active all of them will fire componentWillReceiveProps, they simply just need to check their own ID against the one they've received, if it matches they're active, if it doesn't they're not!
So my top level component now looks like this:
export default class Searches extends React.Component {
constructor(){
super();
this.state = {
searches : [
{
id : "2178348216",
searchName: "searchName1",
matches: "5"
},
{
id : "10293840132",
searchName: "searchName2",
matches: "20"
}
],
activeElement : 0
};
}
// This function gets passed via a prop below
updateActiveSearch(id){
//console.log(id);
this.setState({activeElement : id});
}
render() {
const SearchItems = this.state.searches.map((search) => {
return <Search activeElement={this.state.activeElement} goFunction={this.updateActiveSearch.bind(this)} key={search.id} {...search}/>
})
return (
<div> {SearchItems} </div>
);
}
}
CHILD COMPONENTS
export default class Search extends React.Component {
constructor() {
super();
// Set the default panel style
this.state = {
panelStyle: { height: '90px', marginBottom: '6px', boxShadow: '' },
selected: false
}
}
// This happens right before the props get updated!
componentWillReceiveProps(incomingProps){
if(incomingProps.activeElement == this.props.id){
this.setState({selected: true});
} else {
this.setState({selected: false});
}
}
isActive(){
return 'row panel panel-success ' + (this.state.selected ? 'active' : 'default');
}
viewNotifications(e){
//this.state.panelStyle.boxShadow = '-2px 3px 20px 5px rgba(255,198,0,1)';
this.setState({selected: true});
this.props.goFunction(this.props.id);
}
render() {
const { id, searchName, matches } = this.props;
const buttonStyle = {
height: '100%',
width: '93px',
backgroundColor: '#FFC600'
}
return (
<div style={this.state.panelStyle} className={this.isActive()}>
<div class="col-xs-10">
<div class="col-xs-7">
Search Name: {searchName}
</div>
<div class="col-xs-7">
Must Have: PHP, MySQL
</div>
<div class="col-xs-7">
Could Have: AngularJS
</div>
</div>
<button type="button" onClick={this.viewNotifications.bind(this)} style={buttonStyle} class="btn btn-default btn-lg"> {matches} </button>
</div>
);
}
}