ReactJS onClick not working on first element in menu - javascript

I'm making a custom dropdown list in reactjs. When I click on any element in the dropdown list I get it's value and put it inside an input and it works just fine. The problem is that the first element returns nothing and I can't get it's value. Also I developed the dropdown list to disappear when I choose any element inside of it. But like I said it works just fine on all elements except the first one.
I solved the problem by setTimeOut and hide the dropdown list in 50 milliseconds. But I don't think this's a right solution.
//Select Component
class Select extends Component {
showList = (e) => {
e.target.closest('.select').classList.add('active');
}
hideList = (e) => {
setTimeout(() => {
e.target.closest('.select').classList.remove('active');
}, 100);
}
selectValue = (e) => {
let value = e.target.getElementsByTagName('span')[0].innerHTML;
this.setState({ selectValue: value })
}
render() {
return (
<input
{...this.props}
type="text"
placeholder={this.props['placeholder']}
onFocus={this.showList}
onBlur={this.hideList}
value={this.state.selectValue}
onChange={this.changeSelectValue}
required
/>
<div className="select">
<div className="select-options menu full-width">
{
this.props.list.map(element => {
return (
<MenuItem text={element} onClick={(e) => this.selectValue(e)} />
)
})
}
</div>
</div>
);
}
}
==================
class MenuItem extends Component {
constructor(props) {
super(props);
this.state = {}
}
render() {
return (
<p className={"menu-item " + this.props['type'] } {...this.props}>
{this.props['icon'] ? <i className={this.props['icon']} ></i> : null}
<span>{this.props['text']}</span>
</p>
);
}
}

1.use key prop for every MenuItem Component when you are using the list to create a list of MenuItems.
2.Instead of getting value from target in selectValue function directly pass the value from onClick handler.
selectValue = (e , element) => {
this.setState({ selectValue: element })
}
<MenuItem text={element} key={element} onClick={(e) => this.selectValue(e , element)} />
Editted:-
Remove the onBlur handler and put the functionality of hideList inside selectValue function after setState,you can use setState with callback if normal setState doesn't work
selectValue = (e) => {
let value = e.target.getElementsByTagName('span')[0].innerHTML;
this.setState({ selectValue: value })
e.target.closest('.select').classList.remove('active');
}

Related

How to make my checkbox to be clicked only once and other are unchecked in React.js

I followed a tutorial and created a form with as many checkboxes to be clicked. But, in another case, I need only one box to be checked. The values of checkboxes are dynamic and you never know, how many checkboxes will be created. But, only one can be clicked. Can you please help me in finding the solution thankyou.
import React, { Component } from 'react';
import Checkbox from "./Checkbox.component";
class PatientSelectTiming extends Component {
state = {
options: [...this.props.props],
checkboxes: [...this.props.props].reduce(
(options, option) => ({
...options,
[option]: false
}),
{}
),
appointmentSlots: null
};
handleCheckboxChange = e => {
const { name } = e.target;
this.setState(prevState => ({
checkboxes: {
...prevState.checkboxes,
[name]: !prevState.checkboxes[name]
}
}))
}
handleFormSubmit = formSubmitEvent => {
formSubmitEvent.preventDefault();
Object.keys(this.state.checkboxes)
.filter(checkbox => this.state.checkboxes[checkbox])
.forEach(checkbox => {
let appointmentSlot = [];
appointmentSlot.push(checkbox);
console.log(appointmentSlot);
this.setState({appointmentSlots: appointmentSlot})
localStorage.setItem('appointmentSlots', JSON.stringify(appointmentSlot))
});
};
createCheckbox = option => (
<Checkbox
label={option}
isSelected={this.state.checkboxes[option]}
onCheckboxChange={this.handleCheckboxChange}
key={option}
/>
);
createCheckboxes = () => this.state.options.map(this.createCheckbox);
render() {
return (
<div>
<p>Only select one item and only first date clicked will be your time</p>
<form onSubmit={this.handleFormSubmit}>
{this.createCheckboxes()}
<button type="submit">
Save
</button>
</form>
{this.state.appointmentSlots === null ? <p>Click on any slot to get your time.</p> : <p>Your time is {JSON.parse(localStorage.getItem("appointmentSlots"))}</p>}
</div>
)
}
}
export default PatientSelectTiming;
You can use a radio button
https://www.w3schools.com/tags/att_input_type_radio.asp
Radio button is the same as checkbox but only allows users to check only 1 option.

Edit an Item in a list

I am creating a to-do app using react js and semantic-ui-library.
Although Add and delete functions are working perfectly, I am stuck at moving on with edit function. Following are code snippets.
App.js
class App extends Component{
constructor(props){
super(props);
this.state={
item :'',
listItems:[
],
}
this.handleChangeItem = this.handleChangeItem.bind(this);
}
updateItem=(key,item)=>{
const newlistItems = [...this.state.listItems]
newlistItems.map(list=>{
if(list.key===key){
list.item = item;
}
}
)
this.setState({
listItems:newlistItems
})
}
ListView.js
<List.Content>
{
this.props.listItems.map((item, index) => <List.Header key={index} >
<span>
<input
size="50%"
id={index}
value={item}
onChange={(event)=>{this.props.updateItem(event.target.value,index)}}
/>
</List.Content>
I am implementing the edit function in parent component and calling it for onChange method in input field of the List View Component. I am unable to edit the value of the input field in the view component.
Can anybody help me to sort this out?
The problem is with your updateItem function. You can use setState with a callback to access the previous state of your state.
Try the following:
updateItem = (key, item) => {
this.setState(prevState => {
...prevState,
listItems: prevState.listItems.map(list => {
if(list.key === key){
list.item = item;
}
return list;
})
})
}
And maybe on your onChange event you want to pass item instead of index:
onChange={ event => { this.props.updateItem(event.target.value, item) } }
I hope this helps!

How to show tooltips for react-select?

I need to show tooltips for react-select container (not for a separate options) using react-tooltip library.
I made my own SelectContainer component based on the original one and added there data-tip and data-for HTML attributes. Tooltip shows up but when I change selected value it disappears and is not displayed any more.
Here is my code:
const getSelectContainer = options => props => {
return (
<components.SelectContainer
{...props}
innerProps={{
...props.innerProps, ...{
'data-tip': options.tooltipText,
'data-for': options.tooltipId,
}
}}
/>
)
}
const CustomSelect = (props) => {
const tooltipId='tooltip_id'
const tooltipText='tooltip'
const selectedOption = colourOptions.filter(option => option.value === props.value)
return (
<div>
<ReactTooltip effect="solid" html={true} place="bottom" id={tooltipId} />
<Select
defaultValue={colourOptions[4]}
value={selectedOption}
options={colourOptions}
classNamePrefix="react-select"
onChange={item => props.onChange(item.value)}
className="my-select"
components={{
SelectContainer: getSelectContainer({
tooltipText:tooltipText,
tooltipId:tooltipId
})
}}
/>
</div>
)
}
class Page extends Component {
constructor (props) {
super(props)
this.state = {
selectValue: 'red'
}
}
render () {
const onChange = (value)=> {
this.setState({selectValue: value})
//alert(value)
}
return (
<CustomSelect
value={this.state.selectValue}
onChange={onChange}>
</CustomSelect>
)
}
}
See full example here:
If I wrap Select with another <div> and assign tooltip HTML attributes to it everything works correctly but I don't want to add one more DOM element just for that.
What can I do to show tooltips after changing selection?
Try rebuilding the react-tooltip when the state changes.
useEffect(() => {
ReactTooltip.rebuild();
}, [state]);

when I click add button multiple times it should show I am here div with delete button with multiple times

I am trying to build a simple ui so that I can learn react.
right now when I click an add button it will show I am here div and delete button
when I click add button multiple times it should show I am here div with delete button with multiple times.
so I research and found this example https://www.skptricks.com/2018/06/append-or-prepend-html-using-reactjs.html
using this example I implemented the appendData method but still its not adding the div multiple times.
in my console I am able to see how many times divs are added console.log("this.displayData---->", this.displayData);
can you tell me how to fix it.
so that in future I will fix it myself
https://stackblitz.com/edit/react-b2d3rb?file=demo.js
onClick = () => {
this.setState({ showResults: true });
this.setState({ showOrHideSearch: true });
this.displayData.push(
<div style={{ display: this.state.showOrHideSearch ? "" : "none" }}>
{" "}
I am here
<input
ref="rbc-toolbar-label"
type="submit"
value="Delete"
onClick={this.onDelete}
/>
</div>
);
console.log("this.displayData---->", this.displayData);
this.setState({ showdata: this.displayData });
};
First thing is you should not use this.setState multiple times, instead you should do them in one line. And instead of pushing data into class variables, you should set that data into your state variable and the same variable you should use in your render function. It will be good if you can share your complete code..
import React, { Component } from 'react';
import Calendar from 'rc-calendar';
import DatePicker from 'rc-calendar/lib/Picker';
import 'rc-calendar/assets/index.css';
import moment from 'moment';
class CalendarPage extends Component {
constructor(props) {
super(props);
console.log("AsyncValidationForm this.props---->", this.props);
this.state = {
displayData: []
};
}
onClick = () => {
let displayData = [...this.state.displayData];
displayData.push( { text: 'I am here' });
this.setState({ displayData: displayData });
};
onDelete = index => {
let displayData = [...this.state.displayData];
if(index > -1){
displayData.splice(index, 1);
}
this.setState({ displayData: displayData });
};
handleChange = name => event => {
const value = event.target.value;
this.setState(
{
[name]: value,
pristine: false
},
() => {
this.props.handleChange(name, value); //setState username, password of VerticalLinearStepper.js
}
);
};
onSubmit(event) {
event.preventDefault();
var newItemValue = this.refs.itemName.value;
if(newItemValue) {
this.props.addItem({newItemValue});
this.refs.form.reset();
}
}
render() {
const { handleSubmit, pristine, reset, submitting } = this.props;
let { displayData} = this.state;
return (
<form onSubmit={handleSubmit}>
<div>
<input type="submit" value="add" onClick={this.onClick} />
{displayData.length > 0 ? displayData.map(function(data, index) {
return (
<div key={index}>
{data.text} - For testing added index on screen {index}
<input
ref="rbc-toolbar-label"
type="submit"
value="Delete"
onClick={() => this.onDelete(index)}
/>
</div>
)}, this) : null}
</div>
</form>
);
}
}
export default CalendarPage;

Function keeps on rendering when I try to add to a value to the sum. create-react-app

I am trying to create a function in which when a user clicks the add button className = "addButton" ; they will add the price value which is obtained from the .json that was fetched in the parent class this.props.products.
My problem is > When I try to render the ProductSquare component which contains the add button and a few other things related to the product, it will render in a infinite loop.
I am also selectivly targeting products that have the unique product._id
import React, { Component } from "react";
//css classes
import "./cssForComponents/ProductSquare.css";
import AddButton from "./addButton.js";
class ProductSquare extends Component {
constructor(props) {
super(props);
this.state = {
isLoaded: true,
sum: 0
};
this.onClickHandlerForAdd = this.onClickHandlerForAdd.bind(this);
this.getData = this.getData.bind(this);
}
onClickHandlerForAdd(price) {
this.setState({ sum: price });
}
getData() {
return this.props.products.map(
(product) =>
product._id === "5b77587c570ee12e768704da" ? (
<div className="ProductSquare" key={product.index}>
<button
className="AddButton"
onClick={this.onClickHandlerForAdd(product.price)}
>
{product.price}
</button>
<button className="InfoButton">{product.about}</button>
</div>
) : (
console.log("Results were not loaded")
)
);
}
render() {
console.log(this.state.sum);
return this.getData();
}
}
export default ProductSquare;
Your onClick handler is actually being called when you are doing the render; hence the state is changing, hence another render etc. It should be:
onClick={() => this.onClickHandlerForAdd(product.price)}
That way the onClick handler is registering a function to be called when clicked
You should avoid using arrow functions in the render method. To have a good understanding why, you can refer this article.
Why arrow functions are problematic inside render?
One way to to do this is to use a closure for the clickHandlers. You can modify your existing functions as below.
this.onClickHandlerForAdd = this.onClickHandlerForAdd.bind(this);
this.getData = this.getData.bind(this);
onClickHandlerForAdd = price => () => {
this.setState({ sum: price });
}
and
getData = () => {
return this.props.products.map(
(product) =>
product._id === "5b77587c570ee12e768704da" ? (
<div className="ProductSquare" key={product.index}>
<button
className="AddButton"
onClick={this.onClickHandlerForAdd(product.price)}
>
{product.price}
</button>
<button className="InfoButton">{product.about}</button>
</div>
) : (
console.log("Results were not loaded")
)
);
}

Categories

Resources