How to close the drop-down after selecting a value in react? - javascript

I have used a drop-down in my React project. When I select a value I want to close it. But it doesn't automatically close. How can I do it ?
Dropdown.js
import React from 'react';
import { PropTypes } from 'prop-types';
import { DropdownToggle, DropdownMenu, UncontrolledDropdown } from 'reactstrap';
import * as Icon from 'react-bootstrap-icons';
import './DropDown.scss';
/**
* This is a reusable dropdown
* onClick function and dropdown items come as props
*/
class DropDown extends React.Component {
render() {
const { dropDownItemArray, text, onClick } = this.props;
const dropdownItems = dropDownItemArray.map((item,key) => {
return (
<div className="dropdown-items" onClick={onClick} >
{item}
</div>
);
});
return (
<div>
<UncontrolledDropdown className="multi-select-wrapper text p4">
<DropdownToggle className="select-dropdown">
<div className="select-text text p4">{text}</div>
<Icon.CaretDownFill />
</DropdownToggle>
<DropdownMenu name='test'>{dropdownItems}</DropdownMenu>
</UncontrolledDropdown>
</div>
);
}
}
DropDown.propTypes = {
text: PropTypes.string,
onClick: PropTypes.func,
menuItemArray: PropTypes.array
};
export default DropDown;
This handles all the input values from the input fields and selected values from dropdowns.
handleChangeInputs = (event) => {
if (event[0] === '<') {
this.setState({
editorHtml: event
});
} else {
if (event.type === 'change') {
this.setState({
[event.target.name]: event.target.value
});
} else {
if (event.target.parentNode.innerText[0] === 'C') {
console.log(event);
this.setState({
ticketType: event.currentTarget.textContent
});
} else {
console.log("test");
this.setState({
ticketPriority: event.currentTarget.textContent
});
}
}
}
};
This part is related to drop-down handling
if (event.target.parentNode.innerText[0] === 'C') {
console.log(event);
this.setState({
ticketType: event.currentTarget.textContent
});
} else {
console.log("test");
this.setState({
ticketPriority: event.currentTarget.textContent
});
}

You could add a toggleDropdown function along with a property in your state dropdownOpen which will cause the dropdown to be open or closed:
toggleDropdown = () => {
const dropdownOpen = this.state.dropdownOpen ? false : true;
this.setState({ dropdownOpen: dropdownOpen})
};
Pass toggleDropdown and dropdownOpen in the props and reference them in your code:
const { toggleDropdown, dropdownOpen } = this.props;
[...]
<UncontrolledDropdown
isOpen={dropdownOpen || false}
toggle={toggleDropdown}
>
Your code references an onClick function but you've named your function handleChangeInputs. Here's an onClick function that would work.
onClick = (event) => {
this.setState({ ticketType: event.currentTarget.textContent }, () => this.toggleDropdown()}
Calling toggleDropdown inside of onClick only seems to work if it's in the callback of this.setState.

Related

Disable Semantic UI dropdown option being selected when tabbing to next element

I am working with ReactJS and using Semantic UI.
I have a breadcrumb style navigation menu on my page built using the 'Breadcrumb' and 'Dropdown' components from Semantic UI.
I am trying to make the behaviour of the menu accessible by allowing the user to use the keyboard (tab, arrows and enter) to navigate the menu, the dropdown options and to make a selection from the dropdown options.
The issue I am trying to solve is when using the keyboard, the user should only be able to select an option when it is focused on (using the arrow keys) followed by pressing the 'enter' key. At the moment when the user focuses on an option, it gets selected when they 'tab' to the next element.
Is it possible to change this behaviour so an option is only selected when the enter key is pressed?
I tried implementing "selectOnNavigation={false}" on the Dropdown component however this still results in the option being selected when 'tabbing' away from the element.
I also tried manipulating the event handler onBlur() but it still selected the option when tabbing.
Code for my component:
import React from 'react';
import { connect } from 'react-redux';
import { Breadcrumb, Dropdown } from 'semantic-ui-react';
import PropTypes from 'prop-types';
import {
saveBreadcrumbOptions,
changeBreadcrumbMarket,
changeBreadcrumbParentGroup,
changeBreadcrumbAgency
} from '../../actions/breadcrumbActions';
import MenuFiltersAPI from '../../api/MenuFiltersAPI';
import './OMGBreadcrumb.css';
export class OMGBreadcrumb extends React.Component {
constructor(props) {
super(props);
this.state = {
markets: [],
parentGroups: [],
agencies: []
};
this.getBreadcrumbOptions = this.getBreadcrumbOptions.bind(this);
this.handleMarketChange = this.handleMarketChange.bind(this);
this.handleParentGroupChange = this.handleParentGroupChange.bind(this);
this.handleAgencyChange = this.handleAgencyChange.bind(this);
this.handleParentGroupBlur = this.handleParentGroupBlur.bind(this);
}
componentDidMount() {
this.getBreadcrumbOptions();
}
async getBreadcrumbOptions() {
// get all options
const breadcrumb = this.props.breadcrumb.options.length
? this.props.breadcrumb.options
: await MenuFiltersAPI.getBreadcrumb();
// if a market is selected, use it
// otherwise use the first one
let selectedMarket = breadcrumb.find(m => (
m.id === this.props.breadcrumb.selectedMarket
));
selectedMarket = selectedMarket
? selectedMarket.id
: breadcrumb[0].id;
this.props.saveBreadcrumbOptions(breadcrumb);
this.setState({ markets: breadcrumb }, () => this.changeMarket(selectedMarket));
}
changeMarket(id) {
// get parent group options for given market
const parentGroups = this.state.markets.find(market => market.id === id).parent_groups;
// if a parent group is selected, use it
// otherwise use the first one
let selectedParentGroup = parentGroups.find(pg => (
pg.id === this.props.breadcrumb.selectedParentGroup
));
selectedParentGroup = selectedParentGroup
? selectedParentGroup.id
: parentGroups[0].id;
this.props.changeBreadcrumbMarket(id);
this.setState({ parentGroups }, () => this.changeParentGroup(selectedParentGroup));
}
changeParentGroup(id) {
// get agency options for dropdown menu
const agencies = this.state.parentGroups.find(parentGroup => parentGroup.id === id).agencies;
let selectedAgency = agencies.find(a => (
a.id === this.props.breadcrumb.selectedAgency
));
selectedAgency = selectedAgency
? selectedAgency.id
: agencies[0].id;
this.props.changeBreadcrumbParentGroup(id);
this.setState({ agencies }, () => this.changeAgency(selectedAgency));
}
changeAgency(id) {
// const selectedAgency = agencyOptions[0].value
this.props.changeBreadcrumbAgency(id);
}
handleMarketChange(e, { value }) {
console.log(value)
this.changeMarket(value);
}
handleParentGroupChange(e, { value }) {
console.log(value)
// if(!!value){
// return;
// }
this.changeParentGroup(value);
}
handleAgencyChange(e, { value }) {
console.log(value)
this.changeAgency(value);
}
handleParentGroupBlur(e, {value}) {
e.preventDefault();
console.log(e.key)
if(e.key !== 'Enter'){
console.log('key was not enter')
return;
}
}
render() {
return (
<div id="OMGBreadcrumb">
<b>Show information by: </b>
<Breadcrumb>
<Breadcrumb.Section>
<Dropdown
selectOnNavigation={false}
options={this.state.markets.reduce((acc, cur) => {
acc.push({ text: cur.name, value: cur.id });
return acc;
}, [])}
value={this.props.breadcrumb.selectedMarket}
onChange={this.handleMarketChange}
openOnFocus={false}
/>
</Breadcrumb.Section>
<Breadcrumb.Divider icon='right chevron' />
<Breadcrumb.Section>
<Dropdown
selectOnNavigation={false}
options={this.state.parentGroups.reduce((acc, cur) => {
acc.push({ text: cur.name, value: cur.id });
return acc;
}, [])}
value={this.props.breadcrumb.selectedParentGroup}
onChange={this.handleParentGroupChange}
openOnFocus={false}
onBlur={this.handleParentGroupBlur}
/>
</Breadcrumb.Section>
<Breadcrumb.Divider icon='right chevron' />
<Breadcrumb.Section>
<Dropdown
// selectOnNavigation={false}
options={this.state.agencies.reduce((acc, cur) => {
acc.push({ text: cur.name, value: cur.id });
return acc;
}, [])}
value={this.props.breadcrumb.selectedAgency}
onChange={this.handleAgencyChange}
openOnFocus={false}
/>
</Breadcrumb.Section>
</Breadcrumb>
</div>
);
}
}
OMGBreadcrumb.propTypes = {
saveBreadcrumbOptions: PropTypes.func.isRequired,
changeBreadcrumbMarket: PropTypes.func.isRequired,
changeBreadcrumbParentGroup: PropTypes.func.isRequired,
changeBreadcrumbAgency: PropTypes.func.isRequired,
breadcrumb: PropTypes.objectOf(
PropTypes.oneOfType([
PropTypes.number,
PropTypes.array
])
).isRequired
};
export default connect(
store => ({
breadcrumb: store.breadcrumb
}),
{
saveBreadcrumbOptions,
changeBreadcrumbMarket,
changeBreadcrumbParentGroup,
changeBreadcrumbAgency
}
)(OMGBreadcrumb);
<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>
Appreciate any guidance.
You can add selectOnBlur={false} to your dropdown component and it will no longer select when the dropdown is blurred.
<Dropdown
...
selectOnBlur={false}
/>
https://codesandbox.io/s/semantic-ui-example-7qgcu?module=%2Fexample.js

React : Updated values is not coming in React-popup

I am working on React-popup but I face some problem . I set condition if user select 3rd option then I am updating state value . State value is updated, When I console it in render then it showing updated value but when I want to use it in React-PopUp updated value is not working . I need to re-render a content on pop-up form if state is get updated . I am new please help me .
Note: PopUp form will be show on click
Code
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import MyCalendar from 'react-big-calendar';
import CustomToolbar from './toolbar';
import Popup from 'react-popup';
import Input from './input';
import axios from 'axios'
import moment from 'moment';
import { fetchEvents, createEvent, updateEvent, deleteEvent } from '../../actions/calendar';
// Setup the localizer by providing the moment (or globalize) Object to the correct localizer.
const localizer = MyCalendar.momentLocalizer(moment); // or globalizeLocalizer
class Calendar extends Component {
constructor(){
super();
this.state={
isCustomer:false,
}
}
onChangeHandler = ev => {
this.setState(
{isCustomer:ev.target.value === "viewing"},
() => { console.log(this.state.isCustomer)
})
}
componentDidMount() {
this.props.fetchEvents();
}
//RENDER SINGLE EVENT POPUP CONTENT
renderEventContent(slotInfo) {
const date = moment(slotInfo.start).format('MMMM D, YYYY');
return (
<div>
<p>Date: <strong>{date}</strong></p>
<p>Subject: {slotInfo.taskChage}</p>
<p>Time : {slotInfo.time}</p>
<p>Date : { slotInfo.date}</p>
<p>Notes : {slotInfo.notes}</p>
<p>User Id : {slotInfo.userId}</p>
</div>
);
}
//ON SELECT EVENT HANDLER FUNCTION
onSelectEventHandler = (slotInfo) => {
console.log("New Applicaiton ", this.state.isCustomer)
Popup.create({
title: slotInfo.title,
content: this.renderEventContent(slotInfo),
buttons: {
right: [{
text: 'Edit',
className: 'info',
action: function () {
Popup.close(); //CLOSE PREVIOUS POPUP
this.openPopupForm(slotInfo); //OPEN NEW EDIT POPUP
}.bind(this)
}, {
text: 'Delete',
className: 'danger',
action: function () {
//CALL EVENT DELETE ACTION
this.props.deleteEvent(slotInfo.id);
Popup.close();
}.bind(this)
}]
}
});
}
//HANDLE FUNCITON ON SELECT EVENT SLOT
onSelectEventSlotHandler = (slotInfo) => {
this.openPopupForm(slotInfo); //OPEN POPUP FOR CREATE/EDIT EVENT
}
//POPUP-FORM FUNCTION FOR CREATE AND EDIT EVENT
openPopupForm = (slotInfo) => {
let newEvent = false;
let popupTitle = "Update Event";
if(!slotInfo.hasOwnProperty('id')) {
slotInfo.subject = null;
slotInfo.taskType = null;
slotInfo.time=null;
slotInfo.date=null;
slotInfo.notes=null;
slotInfo.userId=null;
popupTitle = "Add task to Diary";
newEvent = true;
}
let titleChange = function (value) {
slotInfo.subject = value;
};
let taskChange = function (value) {
slotInfo.taskType = value;
};
let timeChange = function (value) {
slotInfo.time = value;
};
let dateChnage = function ( value){
slotInfo.date=value;
};
let notesChange = function ( value){
slotInfo.notes=value;
};
let userId = function ( value){
slotInfo.userId=value;
};
Popup.create({
title: popupTitle,
content: <div>
<Input fieldName="subject" onChange={titleChange} placeholder="Subject" />
<select name="taskType" onChange={this.onChangeHandler}>
<option vlaue="no">Task Type</option>
<option value="meeting">Meeting</option>
<option value="followup">Follow Up</option>
<option value="viewing">Viewing</option>
<option value="reminder">Reminder</option>
<option value="other">Other</option>
</select>
{this.state.isCustomer===true && <input defaultValue="Hi customer :)"></input>}
<Input fieldName="time" onChange={timeChange} placeholder="Time"/>
<Input fieldName="date" onChange={dateChnage} placeholder="Date"/>
<Input fieldName="notes" onChange={notesChange} placeholder="Notes"/>
<Input fieldName="userId" onChange={userId} placeholder="User Id"/>
</div>,
buttons: {
left: ['cancel'],
right: [{
text: 'Save',
className: 'success',
action: function () {
//CHECK THE ID PROPERTY FOR CREATE/UPDATE
if(newEvent) {
this.props.createEvent(slotInfo); //EVENT CREATE ACTION
} else {
this.props.updateEvent(slotInfo); //EVENT UPDATE ACTION
}
Popup.close();
}.bind(this)
}]
}
});
}
//EVENT STYLE GETTER FOR SLYLING AN EVENT ITEM
eventStyleGetter(event, start, end, isSelected) {
let current_time = moment().format('YYYY MM DD');
let event_time = moment(event.start).format('YYYY MM DD');
let background = (current_time>event_time) ? '#DE6987' : '#8CBD4C';
return {
style: {
backgroundColor: background
}
};
}
render() {
return (
<div className="calendar-container">
<MyCalendar
popup
selectable
localizer={localizer}
defaultView={MyCalendar.Views.MONTH}
components={{toolbar: CustomToolbar}}
views={['month']}
style={{height: 600}}
events={this.props.events}
eventPropGetter={(this.eventStyleGetter)}
onSelectEvent={(slotInfo) => this.onSelectEventHandler(slotInfo)}
onSelectSlot={(slotInfo) => this.onSelectEventSlotHandler(slotInfo)}
/>
<Popup />
</div>
);
}
}
function mapStateToProps(state) {
return {
events: state.events
};
}
function mapDispatchToProps(dispatch) {
return bindActionCreators({
fetchEvents,
createEvent,
updateEvent,
deleteEvent
}, dispatch);
}
export default connect(mapStateToProps, mapDispatchToProps)(Calendar);
Try to use react inbuild method componentDidUpdate(). The componentDidUpdate is particularly useful when an operation needs to happen after the DOM is updated and the update queue is emptied
import React, { Component } from "react";
import ReactDom from "react-dom";
import { Modal, Button, Row, Col, Form } from "react-bootstrap";
class PopupModal extends React.Component {
popupShown = true;
componentWillMount() {
}
componentDidUpdate() { //called when state update in parent component
this.setState({ modalData: null });
this.setState({ modalData: this.props.modalData});
this.popupShown = true;
}
state = {
modalData: this.props.modalData //getting data for the modal from parent
};
onHidePopup = () => {
this.setState({ modalData: null });
this.popupShown = false;
};
render() {
if (this.popupShown == true) {
return (
<Modal
show={this.popupShown}
onHide={this.onHidePopup}
>
<Modal.Header closeButton>
<Modal.Title id="modalTitle">Model header</Modal.Title>
</Modal.Header>
<Modal.Body>
Model body comes here
</Modal.Body>
<Modal.Footer>
<p>Hello I am Footer</p>
</Modal.Footer>
</Modal>
);
} else {
return <div />;
}
}
}
export default PopupModal;

Console.log() after setState() doesn't return the updated state

I've created a simple todo list to learn react and i'm trying to add some additional features. At the moment i'm trying to add buttons that toggle the list of items, so it either shows all the tasks or just those that are completed.
I've written a function to change the state of my visabilityFilter so I can later use this to toggle the items in the list, but it isn't behaving how it should be.
I console log the visabilityFilter variable but it always shows the wrong state before changing to the correct state. e.g. the 'show all' button will console log 'show completed' then if you press it again it will console log 'show all'
App.js
import React, { Component } from 'react';
import './App.css';
import TodoList from './components/TodoList.js'
import VisabilityFilter from './components/VisabilityFilter.js'
export const SHOW_ALL = 'show_all'
export const SHOW_COMPLETED = 'show_completed'
class App extends Component {
constructor (props) {
super(props)
this.state = {
inputValues: {
'newTodo': ''
},
todos: [
{
task: 'My First Todo',
completed: false
}
],
visabilityFilter: SHOW_ALL
}
this.addTodo = this.addTodo.bind(this)
this.handleInputChange = this.handleInputChange.bind(this)
this.handleKeyUp = this.handleKeyUp.bind(this)
this.toggleCompleted = this.toggleCompleted.bind(this)
this.removeTodo = this.removeTodo.bind(this)
this.checkCompleted = this.checkCompleted.bind(this)
this.setVisabilityFilter = this.setVisabilityFilter.bind(this)
}
handleInputChange (e) {
const { inputValues } = this.state
const { id, value } = e.target
this.setState({
inputValues: {
...inputValues,
[id]: value
}
})
}
handleKeyUp (e) {
var code = e.key
if(code === 'Enter') {
this.addTodo(e);
}
}
toggleCompleted (e, index) {
const { todos } = this.state
todos[index].completed = !todos[index].completed
todos.sort((a, b) => b.completed - a.completed)
this.setState({ todos })
}
removeTodo (e, index) {
const { todos } = this.state
this.setState ({ todos: todos.filter((todo, i) => i !== index) })
}
addTodo (e) {
const { todos, inputValues } = this.state
const { dataset } = e.target
if (inputValues[dataset.for] === '') return
const newTodo = { task: inputValues[dataset.for], completed: false }
todos.push(newTodo)
this.setState({
todos,
inputValues: { ...inputValues, [dataset.for]: '' }
})
}
checkCompleted (e, index) {
const { todos } = this.state
return { todos } && todos[index].completed
}
setVisabilityFilter (e) {
const { visabilityFilter } = this.state
const { dataset } = e.target
this.setState({
visabilityFilter: dataset.for
})
console.log ({ visabilityFilter })
}
render() {
const { todos, inputValues, visabilityFilter } = this.state
return (
<div className="App">
<TodoList
todos={todos}
inputValues={inputValues}
addTodo={this.addTodo}
handleInputChange={this.handleInputChange}
removeTodo={this.removeTodo}
toggleCompleted={this.toggleCompleted}
handleKeyUp={this.handleKeyUp}
checkCompleted={this.checkCompleted}
/>
<VisabilityFilter setVisabilityFilter={this.setVisabilityFilter} />
</div>
);
}
}
export default App;
VisabilityFilter.js
import React from 'react'
import { func } from 'prop-types'
import { SHOW_ALL, SHOW_COMPLETED } from '../App'
const VisabilityFilter = props => {
return (
<div>
<button data-for={SHOW_COMPLETED} onClick={ props.setVisabilityFilter } >
Show Completed Tasks
</button>
<button data-for={SHOW_ALL} onClick={ props.setVisabilityFilter }>
Show All Tasks
</button>
</div>
)
}
VisabilityFilter.propTypes = {
setVisabilityFilter: func.isRequired
}
export default VisabilityFilter
setState() is async (React docs), so the state changes won't be applied immediately. If you want to log out the new state,setState() takes in a function as the second argument and performs that function when the state is updated. So:
this.setState({
abc: xyz
},
() => console.log(this.state.abc),
)
Or you can also use componentDidUpdate(), which is recommended
In the functional components, you can use useEffect to track changes in state.
useEffect(() => {
console.log(someState);
},[someState);

Handle Redux-form events

Not really sure how to do this, quite new in the Redux-Form world. I have a custom component for my inputs with an onFocus event that displays an unordered list and an onBlur event that hides it. I am also attaching an onClick event to the items because I would like to change the value of the input field once I click on a list item but the click event never fires because blur fires first.
Not sure if I am addressing my code properly.
Here is my custom component:
import React, { Component } from 'react'
import pageStyles from '../../../styles.scss'
class MyInput extends Component {
constructor(props) {
super(props);
this.state = {
dropdownIsVisible: false,
dropdownCssClass: 'dropdown'
}
this.handleFocus = this.handleFocus.bind(this);
this.handleBlur = this.handleBlur.bind(this);
this.handleClick = this.handleClick.bind(this);
}
handleFocus () {
this.setState({
dropdownIsVisible: true,
dropdownCssClass: ['dropdown', pageStyles.dropdown].join(' ')
})
}
handleBlur () {
this.setState({
dropdownIsVisible: false,
dropdownCssClass: 'dropdown'
})
}
handleClick () {
console.log('here I am')
}
render() {
const {
input: {
name,
value,
onFocus
},
label,
hasOptions,
options,
cssClass,
meta: {
touched,
error
}
} = this.props
if(options) {
var listItems = options.map((item) =>
<li key={ item.id } onClick={ this.handleClick }>{ item.name }</li>
)
}
return (
<div className={ cssClass }>
<label>{ label }</label>
<input name={ name } id={ name } type="text" onFocus={ this.handleFocus } onBlur={ this.handleBlur } autoComplete="off" />
{ hasOptions === true ? <ul className={ this.state.dropdownCssClass }>{ listItems }</ul> : null }
{ touched && error && <span className="error">{ error }</span> }
</div>
)
}
}
export default MyInput
Use lodash debounce function. This function adds delay before function calling, so you can cancel it in case of nested element click.
Add in constructor:
this.handleBlur = debounce(this.handleBlur, 100);
replace handleClick:
handleClick () {
if(this.handleBlur.cancel){
this.handleBlur.cancel()
}
console.log('here I am')
}

Prevent setState trigger in React

I have:
class SomeComponent extends Component {
state = {
outside: false,
inside: false,
}
onOutsideClick = () => this.setState({ outside: true })
onInsideClick = () => this.setState({ inside: true })
render() {
return (<div onClick={this.onOutsideClick}>Some text, <p onClick={this.onInsideClick}>some other text</p></div>)
}
}
When I click on some other text, the onOutsideClick handler will trigger as well and as such this.state.outside will change to true. If some other text is clicked, I don't want to trigger any other method.
I tried with e.stopPropagation and if (this.state.inside) return in onOutsideClick but none of those worked
You should use event.stopPropagation() to stop bubbling event. It's hard to say why stopPropagation() hasn't worked for you.
Working demo with stopPropagation(): https://codesandbox.io/s/wpPJ1LkXR
import React, { Component } from 'react';
import { render } from 'react-dom';
const INSIDE_CLICK = 'inside';
const OUTSIDE_CLICK = 'outside';
class App extends Component {
state = {
clicked: undefined,
};
onOutsideClick = () => {
this.setState({ clicked: OUTSIDE_CLICK });
};
onInsideClick = (event) => {
event.stopPropagation();
this.setState({ clicked: INSIDE_CLICK });
};
render() {
return (
<div onClick={this.onOutsideClick}>
Some text, <p onClick={this.onInsideClick}>some other text</p>
<div style={{ border: '2px solid red' }}>
Clicked: {this.state.clicked}
</div>
</div>
);
}
}
render(<App />, document.getElementById('root'));
Check some boolean before calling setState and put it to false again in the setState callback.

Categories

Resources