button value passing as undefined - javascript

I apologize for the simple question, I'm new to react and I have yet to find a solution for this problem. When clicked, I'm trying to retrieve the value for each button. The value repeatedly comes back as undefined. What am I doing wrong? I greatly appreciate your help!
class BottomListFilter extends Component {
constructor(props) {
super(props);
this.handleButton = this.handleButton.bind(this);
}
handleButton = (evt) => {
console.log(evt.target.value);
};
render() {
const listFilter = this.props.initialAdvisorList.map((filter) =>
<li key={filter.id}>
{filter.location}
</li>
);
return(
<div>
<div>
<button
value='location'
onClick={this.handleButton}
>
<h2>Choose by location</h2>
</button>
<button
value='state'
onClick={this.handleButton}
>
<h2>Choose by state</h2>
</button>
<button
value='practice'
onClick={this.handleButton}
>
<h2>Choose by practice</h2>
</button>
<button
value='topic'
onClick={this.handleButton}
>
<h2>Choose by topic</h2>
</button>
</div>
<div>
<ul>
{listFilter}
</ul>
</div>
</div>
);
}
}

You can certainly do evt.target.parentNode.value and it will work in this case. It may be just me but I find that solution against the spirit of React as it's reminisce of traversing the DOM (JQuery days).
Passing the value to handleButton function is much cleaner and more flexible:
<button onClick={this.handleButton.bind(null, 'location')}>
<h2>Choose by location</h2>
</button>
Now your handleButton looks like this:
handleButton(value, event) {
console.log(value);
console.log(event);
}
In this case, you don't really need "event" anymore and you could easily do handleButton(value) but I have included it just for the sake of clarity.

you can simplify your handleButton situation. it doesn't need to be a method. (in the code below, i also stripped away the ul, just to isolate the issue.)
i don't think this is what was causing the problem, though. most likely, the problem was not a React issue but just a simple DOM issue. event.target means "the lowest-level node where the event took place". if you clicked on the h2, that node is the h2, which has no value property.
just don't use an h2. put the text directly inside the button. also shown in the code sample.
var handleButton = evt => {
console.log(evt.target.value)
}
class BottomListFilter extends Component {
constructor(props) {
super(props);
this.handleButton = this.handleButton.bind(this);
}
render() {
return(
<div>
<button
value='location'
onClick={handleButton}
>
Choose by location
</button>
<button
value='state'
onClick={handleButton}
>
Choose by state
</button>
<button
value='practice'
onClick={handleButton}
>
Choose by practice
</button>
<button
value='topic'
onClick={handleButton}
>
Choose by topic
</button>
</div>
);
}
}

Related

How do I make React onClick calls only affect a single item, instead of all?

I have an array of objects, and for each one I .map it into a component called Card.js. Each card has an 'edit' button, and I have an edit form which I want to appear ONLY for the card on which I clicked the button.
At the moment, whatever I try to do to pass an id into the Editform.js component, it still makes the form appear for all of the card components.
Here's the current component I call which is meant to render just form for the clicked button. I pass in all of the cards in the 'cards' array, and what I believe is the id of the current .map object from the calling function:
function Editform({ cards, setCards, id }) {
const thisCard = cards.filter((card) => card.id === id)[0];
const editThisCard = thisCard.id === id; // trying to match id of passed card to correct card in 'cards' array.
console.log(editThisCard);
return (
<>
{editThisCard && ( // should only render if editThisCard is true.
<div className="form">
<p>Name of game:</p>
<input type="text" value={thisCard.gamename}></input>
<p>Max players: </p>
<input type="text" value={thisCard.maxplayers}></input>
<p>Free spaces: </p>
<input type="text" value={thisCard.freespaces}></input>
<p>Table #: </p>
<input type="text" value={thisCard.tablenum}></input>
<p></p>
<button type="button" className="playbutton">
Save changes
</button>
</div>
)}
</>
);
}
export default Editform;
edit: apologies, I forgot to paste in the other code. Here it is. Note that I'm just hardcoding in a couple of cards for now:
import React from "react";
import ReactFitText from "react-fittext";
import Editform from "./Editform";
function Displaycards({ lastid }) {
const [cards, setCards] = React.useState([
{
id: 1,
gamename: "El Dorado",
maxplayers: 4,
freespaces: 1,
tablenum: 5,
},
{
id: 2,
gamename: "Ticket to Ride",
maxplayers: 4,
freespaces: 2,
tablenum: 3,
},
]); // using the React state for the cards array
const [showForm, setShowForm] = React.useState((false);
return (
<div className="cardwrapper">
{cards.map(({ id, gamename, maxplayers, freespaces, tablenum }) => {
return (
<div key={id}>
<div>
<div className="card">
<ReactFitText compressor={0.8}>
<div className="gamename">{gamename}</div>
</ReactFitText>
<div className="details">
<p>Setup for: </p>
<p className="bignumbers">{maxplayers}</p>
</div>
<div className="details">
<p>Spaces free:</p>
<p className="bignumbers">{freespaces}</p>
</div>
<div className="details">
<p>Table #</p>
<p className="bignumbers">{tablenum}</p>
</div>
<button type="button" className="playbutton">
I want to play
</button>
<br />
</div>
<div className="editbuttons">
<button
type="button"
className="editbutton"
onClick={() => setShowForm(!showForm)}
>
Edit
</button>
<button type="button" className="delbutton">
X
</button>
</div>
{showForm && (
<div>
<Editform
cards={cards}
setCards={setCards}
id={id}
/>
</div>
)}
</div>
</div>
);
})}
</div>
);
}
export default Displaycards;
I feel like I'm missing something obvious, but I can't get my head around what it is. The current iteration of it is here - https://github.com/TSDAdam/lfp/tree/usestate-trial - and it was created with create-react-app .
It sounds like you have one state controlling all of the Cards. You haven't shown the Card component yet however. Have every Card control its own state, so when the edit button bound to the card is clicked, it only applies to that one card. If you show us more code we can narrow it down, but this is most likely the gist of your problem.
The problem is that the EditForm is inside the map function, so for every item in your cards array, a separate EditForm is rendered with the corresponding values, and all these EditForms get shown/hidden based on the same boolean in your state.
The solution is to move the EditForm outside the map function, and create a new state object that tracks an "active" card, from where the single EditForm could take its values.
This of course won't work if you want to render the EditForm in a position relative to the "active" card.
[Edit]
Okay, I ended my answer with a caveat, but I should add a solution for that as well, since it isn't very complicated.
If you want to render an EditForm below the selected card, for example, the approach would be to keep it inside the map function as it is now, and change the boolean state variable showForm into one that accepts a string/number (depending on what you use as the identifier for each card). And then use this state variable to determine which form shows at any given time.
const [showForm, setShowForm] = React.useState("");
{cards.map(({ id, gamename, maxplayers, freespaces, tablenum }) => {
return (
<div key={id}>
// Rest of the JSX
<div className="editbuttons">
<button
type="button"
className="editbutton"
onClick={() => setShowForm(id)}
>
Edit
</button>
<button type="button" className="delbutton">
X
</button>
</div>
{showForm == id && (
<div>
<Editform
cards={cards}
setCards={setCards}
id={id}
/>
</div>
)}
</div>
</div>
);
})}

Scroll to Top of Page onClick with React

I have a component that is rendered with a static ID called #mainView. I have a component that renders a button element lower in the page, and I want to scroll up to the #mainView element when the button is clicked. As far as I can tell, this is a very simple user experience that has been apart of the web standard for decades now and can be accomplished with a simple ... in vanilla HTML (and extended with scroll-behavior: smooth as an experimental feature).
I have spent the last hour trying to replicate this behavior in React, to no avail. Hashes in URLs are ignored, and Gatsby complains about an external link. I have tried #reach/router/navigate and Link. I have tried to use onClick={...}, and manually trying to override onclick does not work. None of these produce any behavior at all and the only approaches I can find on SO involve extending React.Component and getting inside render(), using another method, using refs, and all sorts of stuff that should 10,000% not be necessary for such simple UX.
Is there any way to get React to easily replicate what I want here? And, why might the React developers actively break traditional web functionality? All responses appreciated.
Probably not the beautiful way to do it, but you can try using element.scrollIntoView, in the example I'm using the id and document.getElementById but you can replace it with ref
const Element = ({id,text})=>(<div id={id} style={{width:"100%", height: "400px", border:"solid 1px black"}} >{text}</div>);
const Scroll = ({to})=><span onClick={()=>document.getElementById(to).scrollIntoView({behavior:"smooth"})} >Scroll</span>
const App = ()=><div>
<h1>MyApp</h1>
<Element id="el-1" text="Lorem Ipsum" />
<Element id="el-2" text="Lorem Ipsum" />
<Element id="el-3" text="Lorem Ipsum" />
<Scroll to="el-2"/>
</div>
ReactDOM.render(<App />, document.getElementById('app'));
<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>
<div id="app"></div>
And yes, if it's not to a element, but to the top window.scrollTo({top:0,behavior:'smooth'}) should be enough.
I was using Gatsby, and the solution was to import gatsby-plugin-smoothscroll package and add to plugins (docs), then navigate with:
<button onClick={() => scrollTo('#mainView')}>My link</button>
class Scroll extends React.Component {
constructor(props) {
super(props);
this.myRef1 = React.createRef();
this.myRef2 = React.createRef();
this.myRef3 = React.createRef();
this.myRef4 = React.createRef();
}
scrollSmooth(e, scroll) {
if (scroll === "sec1") {
this.myRef1.current.scrollIntoView({
behavior: "smooth",
block: "start"
});
} else if (scroll === "sec2") {
this.myRef2.current.scrollIntoView({
behavior: "smooth",
block: "start"
});
} else if (scroll === "sec3") {
this.myRef3.current.scrollIntoView({
behavior: "smooth",
block: "start"
});
} else if (scroll === "sec4") {
this.myRef4.current.scrollIntoView({
behavior: "smooth",
block: "start"
});
}
}
render() {
return (
<div className="container">
<ul className="list-group">
<li className="list" onClick={e => this.scrollSmooth(e, "sec1")}>
section1
</li>
<li className="list" onClick={e => this.scrollSmooth(e, "sec2")}>
section2
</li>
<li className="list" onClick={e => this.scrollSmooth(e, "sec3")}>
section3
</li>
<li className="list" onClick={e => this.scrollSmooth(e, "sec4")}>
section4
</li>
</ul>
<div className="row">
<div className="section1" ref={this.myRef1}>
<p>Section 1</p>
</div>
<div className="section2" ref={this.myRef2}>
<p>Section 2</p>
</div>
<div className="section3" ref={this.myRef3}>
<p>Section 3</p>
</div>
<div className="section4" ref={this.myRef4}>
<p>Section 4</p>
</div>
</div>
</div>
);
}
}
export default Scroll;
Hi refer this component.
Working link click here

Why a canvas update reload the page ? React

I would like to implement a signature system inside my form, I use "react-signature-canvas" for this.
Unfortunately, when I try to use it some problem appears.
My main problem is the reloading my page. Usually in React When you update something ,just the render changing without reloading. But here it does ... Is it linked to the canvas system ? I don't want the reload of my page... It's so ugly for a PWA.
For example when I click on "clear" or "trim" the page reload.
This is my code: I removed some parts of it to be clearer
const Consultation4 = () => {
const sigPad = {}
const [trimmedDataUrl,setTrimmedDataUrl] = useState(null);
const clear = () => sigPad.clear();
const trim = () => {
setTrimmedDataUrl(sigPad.getTrimmedCanvas().toDataURL('image/png'));
}
...
(In the render)
<FormGroup tag="fieldset" className="form-group">
<div className="container">
<div className="row">
<div className="col-4 margin-left--12px">
<legend>Please sign here:</legend>
</div>
<div className="col-4 margin-button-clear">
<button onClick={clear} className="btn btn-outline-secondary btn-sm">Clear</button>
</div>
<div className="col-4 margin-button-clear">
<button onClick={trim} className="btn btn-outline-secondary btn-sm">Trim</button>
</div>
</div>
</div>
<div className='border_grey'>
<SignaturePad ref={sigPad} />
</div>
</FormGroup>
{trimmedDataUrl ? <img src={trimmedDataUrl}/> : null}
}
Could you help me please ? Thanks in advance
PS: I have a second little problem, when I try to use sigPad.isEmpty() the compilator said "this method does not exist". But it exist in the doc of signature-pad-canvas (https://www.npmjs.com/package/react-signature-canvas).
The default type of button elements issubmit. Try button tags like
<button type="button" ....
within the form to stop submission from them. Text input elements may also need attention to stop pressing enter from submitting the form as well.

Mouse hover in React

I'm trying to display a div when the mouse is over another div element. I've managed to do so via onMouseEnter and onMouseLeave.
The issue here is that if you quickly move from one div to another (it's an array of divs that contain data about a product), the value of index[0] becomes true.
The way it works is that I have an array initialised to false when the mouse enters one of them, it becomes true and shows the div that I wanted. Once it leaves, it set it back to false.
this.state = {
isProductsHovering: new Array(this.props.currentProducts.length).fill(false)
};
handleMouseHover = (idx) => {
this.setState({
isProductsHovering: update(this.state.isProductsHovering, {
[idx]: { $set: !this.state.isProductsHovering[idx] }
})
})
}
render() {
return this.props.currentProducts.map((product, idx) => {
return <Fragment key={idx}>
<div className="product-grid-view col-6 col-md-4" >
<div
className=" product-holder"
onMouseEnter={this.handleMouseHover.bind(this, idx)}
onMouseLeave={this.handleMouseHover.bind(this, idx)}>
<div className="image-container" align="center">
<img src={"/img/product-3.jpg"} alt="" />
{
this.state.isProductsHovering[idx] &&
<div className="product-buttons">
<Link to={`products/${product.id}`} className="btn-detail" text="View Details" />
<Link to='#' className="btn-cart" icons={["icon-cart", "icon-plus"]} />
</div>
}
</div>
<div className="details-holder">
<span className="part-text">{product.desc}</span><br />
<span className="manufacturer-text">{product.manufacturer.name}</span>
<div className="product-review_slide">
<Stars values={product.averageRating} {...starsRating} />
<span className="product-review">{getLength(product.reviews)} review</span>
</div>
<span className="product-price">{product.salesPrice.toFixed(2)}</span>
<span className="product-currency">SR</span>
</div>
</div>
</div>
</Fragment>
})
}
Update
I've made a stackblitz project to reproduce the same issue as suggested:
https://stackblitz.com/edit/react-mouse-hover.
For everyone that wants to see what I mean. I've attached a photo of the issue. If you move the mouse over the two divs (up and down as quick as you can), this what happens:
mouse hover broken
For situation like this, I wouldn't rely on array and index to make it work. You are further complicating your handleMouseHover functions and the checking of isHovering.
A 'more React' way of dealing with this situation is simply make each Product a component itself. And this Product component will have its own state of isHovered and handleOnHover method, that way you create a more concise and reliable code without having to rely on array index at all:
App.js can be as simple as this:
class App extends Component {
render() {
return (
<div>
{
data.map(product =>
<Product product={product} />
)
}
</div>
)
}
}
A new Product.js:
import React from 'react'
import ReactHoverObserver from 'react-hover-observer';
export default class Product extends React.Component {
render() {
const { product } = this.props
return (
<ReactHoverObserver className="product-grid-view col-6 col-md-4">
{
({isHovering}) => (
<div className=" product-holder">
<div className="image-container" align="center">
<img src={"/img/product-3.jpg"} alt="" />
{
isHovering &&
<div className="product-buttons">
<button className="btn-detail">View Details</button>
</div>
}
</div>
<div className="details-holder">
<span className="part-text">{product.desc}</span><br />
<span className="manufacturer-text">{product.manufacturer.name}</span>
<div className="product-review_slide">
<span className="product-review">0 review</span>
</div>
<span className="product-price">{product.salesPrice.toFixed(2)}</span>
<span className="product-currency">Currency</span>
</div>
</div>
)
}
</ReactHoverObserver>
)
}
}
I have put the moficiation in Stackblitz: https://stackblitz.com/edit/react-mouse-hover-2cad4n
Liren's answer is good advice and will help simplify the code. One thing I also noticed is that occasionally the HoverObserver won't 'hear' an event, and since the hover enter and hover exit events are listening to the same event, then the display state for the button will become reversed (i.e., it will show when the mouse is NOT hovering and hide when the mouse hovers over the observer).
I would recommend getting rid of the ReactHoverObserver HOC and instead just listen for the onMouseOver for hover enter and onMouseLeave for hover exit. That way, even if the div doesn't register a hover enter or exit, it will easily reset because onMouseOver will toggle the display state to true and onMouseLeave will reliably set the button's display state to false.
See here for those events in the docs:
https://reactjs.org/docs/events.html#mouse-events
The way you trigger it (from array or from a component) is semantics , the real issue is that these events don't always fire.
I had the same issue , apparently the events of react are not that reliable.
In my case I could live in a situation where a tooltip does not close , but not in the situation where 2 tooltips are open. In order to solve my issue , I returned to good old dom manipulation - Every time a tooltip appeared , it made all the other ones invisible.
It looked something like this :
showTooltip() {
// Clear old tooltips, EventTooltip is the name of my tooltip class.
Array.from(document.getElementsByClassName('EventTooltip'))
.forEach(tooltip=>tooltip.style = 'display:none;')
this.setState({showTooltip: true})
}
hideTooltip() {
this.setState({showTooltip: false})
}

React Component "closing" when lost focus

I'm trying to create a select2 style component in React.
I have got 90% functionality down, the one bit I just can't fathom is hiding the result box when the user clicks away
The render method is:
render() {
let resultBlock;
if (this.state.showSearch) {
resultBlock = (
<div className="search-input-container" onBlur={this.onBlur}>
<div className="search-input-results">
<input
type="text"
name={this.props.name}
placeholder={this.props.placeholder}
className="form-control"
onChange={this.inputKeyUp}
autoComplete="false" />
<ul>
{this.state.items.map((item, i) => <li key={i} data-value={item.id} onClick={this.itemSelected} className={item.isSelected ? 'selected' : ''}>{item.text}</li>)}
</ul>
</div>
</div>
);
}
let displayBlock;
if (this.props.value.text) {
displayBlock = this.props.value.text;
} else {
displayBlock = <span className="placeholder">{this.props.placeholder}</span>;
}
return (
<div className="form-group">
<label htmlFor={this.props.name}>{this.props.label}:</label>
<div className="form-input">
<div className="searchable-dropdown" onClick={this.revealSearch}>
{displayBlock}
<div className="arrow"><i className="fa fa-chevron-down" aria-hidden="true" /></div>
</div>
{resultBlock}
</div>
</div>
);
}
I've tried moving onBlur={this.onBlur} around, but it only fires if the <input... had focus before one clicked away.
It can't be that complicated, the only approach I thought of, is attaching a global click handler to the page, and diff'ing clicks to understand if a user hasn't clicked on my component. But this seems over engineered.
How can this be achieved?
I achieved this functionality by:
Putting this in the constructor:
this.windowClick = this.windowClick.bind(this);
(From what dfsq said) Put this in componentDidMount:
if (window) {
window.addEventListener('click', this.windowClick, false);
}
This event handler:
windowClick(event) {
event.preventDefault();
if (event.target.classList.contains('searchable-marker')) {
return;
} else {
this.setState({
showSearch: false
});
}
}
Where searchable-marker is just a class I put on all the div's, ul's, li's and inputs to make sure that if I clicked one of these, it wouldn't close the the box.
Adding the unmount:
componentWillUnmount() {
window.removeEventListener('click', this.windowClick, false);
}
what you can try doing is onBlur you could change the value of this.state.showSearch = false and when this condition is satisfied add a className="hide" (hide{display: none}) by creating a custom function which returns a classname as a string.

Categories