I am trying to get the index of a p tag so that when the user clicks on it the p tag will either change color or text size(maybe both). I am using reactjs, but I am trying to do it in jquery. I gave every p tag a data and name attribute. Here is my code. How do I make it so only the clicked element gets a css change so the user knows it is selected. The function running the p tags is in the HTML showChapters function.
$(onReady)
function onReady() {
$("#voteSearchInput").submit(function (e) {
e.preventDefault();
return false;
});//end of voteSearchInput
$('#root').on('click', '.bookChapters' ,() => {
console.log('clicked')
//I want to change the color here
});
}//end of onReady
import React from 'react';
import {connect} from 'react-redux';
import {bindActionCreators} from 'redux';
import {Link} from 'react-router-dom';
import Header from './Header.jsx';
import axios from 'axios';
class Book extends React.Component {
constructor(props) {
super(props);
this.state = {
book:Object,
user:Object,
chapters:[],
chapterCount:0,
ready:false,
};//end of state
}//end of constructor
start() {
console.log(this.state.book)
}
showChapters() {
let chapterCount = this.state.chapterCount;
let chapters = this.state.book.length;
let chatpersArr = this.state.chapters;
for(let i = 0; i < chapters; i++) {
chatpersArr.push([])
}
return (
chatpersArr.map((ch, id) => {
chapterCount++;
return (
<p key={id} className='bookChapters' data-index={id}>{this.state.book.book} Chapter:{chapterCount}</p>
)
})
)
}//end start
componentWillMount() {
//Always gettting the last book out of the array
//AUTHENTICATES THE USER
axios.get('/login').then(res => {
let userInfo = res.data;
if(userInfo === "Not Authenticated") {
window.location = "/?#/";
}
});//end of axios GET
//GETS THE CORRECT BOOK FROM THE REDUCER
if(this.props.book.editBookReducer.length - 1 === -1) {
window.location = "/?#/user";
}
else {
let lastBook = this.props.book.editBookReducer.length -1;
let book = this.props.book.editBookReducer[lastBook].data.book;
let user = this.props.book.editBookReducer[lastBook].data.user;
this.setState({book, user, ready:true});
}
}//end componentWillMount
render() {
if(!this.state.ready){
return false;
}
else {
return (
<div>
<Header/>
<button onClick={this.start.bind(this)} >start</button>
<div className="container">
<div className="row" style={{"textAlign": "center"}}>
<div className="col-md-12">
<h1>{this.state.book.book}</h1>
</div>
</div>
<div className="row" style={{"textAlign": "center"}}>
<div className="col-md-6">
{this.showChapters()}
</div>
<div className="col-md-6">
<textarea name="" id="" cols="50" rows="60"></textarea>
</div>
</div>
</div>
</div>
);
}
}
}//end class Book
function mapDispatchToProps(dispatch) {
return bindActionCreators({
},
dispatch)
}
function mapStateToProps(state) {
return {
book:state
}
}
export default connect(mapStateToProps, mapDispatchToProps)(Book);
React has event handling system built in that you can leverage. For more info see the docs.
Here I assign an onClick to the p element and use the data-index that you defined.
class Book extends React.Component {
handleChapterClicked = (event) => {
// you can get the index like this
let pIndex = event.target.getAttribute("data-index");
// your logic for the handler here
}
showChapters() {
let chapterCount = this.state.chapterCount;
let chapters = this.state.book.length;
let chatpersArr = this.state.chapters;
for(let i = 0; i < chapters; i++) {
chatpersArr.push([])
}
return (
chatpersArr.map((ch, id) => {
chapterCount++;
return (
<p key={id} className='bookChapters' onClick={this.handleChapterClicked} data-index={id}>{this.state.book.book} Chapter:{chapterCount}</p>
)
})
)
}//end start
}
You can get rid of the data-index attribute and bind the index to be the first argument that is passed to handleChapterClicked:
<p key={id} className='bookChapters' onClick={this.handleChapterClicked.bind(this, id)} data-index={id}>{this.state.book.book} Chapter:{chapterCount}</p>
And then the definition of the function would be changed to be:
handleChapterClicked = (index, event) => {
// your logic for the handler here
}
Related
I'm still in the process of learning React, I'm trying to implement an onClick function on a button element. The list of buttons are being rendered here:
<div className="buttons">
{ colours.map((colour, index) => (
<Button
key={ index }
onClick={() => this.checkChoice(colour)}
className="button"
>
{colour}
</Button>
))}
</div>
And here is the onClick function I have already defined.
checkChoice(col) {
const {correctIndex, colours} = this.state;
const newMessage = '';
if (col == colours[correctIndex]){
// correct colour chosen so update message
newMessage = 'Correct!'
} else {
newMessage = 'Wrong!'
}
console.log(newMessage);
}
I'm currently receiving no errors at all, however when I do click on a button, nothing is being printed to the console. Even when I try to call the function in the render method before returning, it still returns with nothing. I'm surely missing something silly, and would greatly appreciate any help.
Thanks!
Edit Here is the Component code:
const Button = ({ onClick, className = '', children }) =>
// {console.log(onClick)}
<button
onClick = { onClick }
className = { className }
type = "button"
>
{ children }
</button>
You should declare "newMessage" variable using let or var. You can't reassign value to a variable declared with const. I have assumed correctIndex state as 1.
import React from "react";
const Button = ({ onClick, className = "", children }) => (
// {console.log(onClick)}
{children}
);
class Car extends React.Component {
constructor() {
super();
this.state = {
correctIndex: 1,
colours: ["red", "green", "white"],
};
}
checkChoice(col) {
const { correctIndex, colours } = this.state;
let newMessage = "";
if (col == colours[correctIndex]) {
// correct colour chosen so update message
newMessage = "Correct!";
} else {
newMessage = "Wrong!";
}
console.log(newMessage);
}
render() {
const { colours } = this.state;
return (
{colours.map((colour, index) => (
this.checkChoice(colour)}
className="button"
>
{colour}
))}
);
}
}
export default Car;
I am working on trying to get this counter for pintsLeft to work. This is my first project with React and I feel that I am either not passing the property of the array correctly or my function code is not set correctly.
^^^^KegDetail.js^^^^
import React from "react";
import PropTypes from "prop-types";
function KegDetail(props){
const { keg, onClickingDelete} = props
return (
<React.Fragment>
<hr/>
<h2>{keg.name} Made By {keg.brewery}</h2>
<p>abv {keg.abv}</p>
<h3>price {keg.price}</h3>
<p>{keg.pintsLeft} total pints left</p> {/* Make this a percentage */}
<hr/>
<button onClick={ props.onClickingEdit }>Update Keg</button>
<button onClick={()=> onClickingDelete(keg.id) }>Delete Keg</button>
<button onClick={()=> this.onSellingPint()}>Sell A Pint!</button>
</React.Fragment>
);
}
KegDetail.propTypes = {
keg: PropTypes.object,
onClickingDelete: PropTypes.func,
onClickingEdit:PropTypes.func,
onSellingPint:PropTypes.func
}
export default KegDetail;
That was my KegDetail.js
import React, {useState} from "react";
import NewKegForm from "./NewKegForm";
import DraftList from "./DraftList";
import KegDetail from "./KegDetail";
import EditKegForm from "./EditKegForm";
class DraftControl extends React.Component {
constructor(props){
super(props);
this.state = {
kegFormVisibleOnPage: false,
fullDraftList: [],
selectedKeg: null,
editing: false,
pints: 127,
};
this.handleClick = this.handleClick.bind(this);
this.handleSellingPint = this.handleSellingPint.bind(this);
}
handleClick = () => {
if (this.state.selectedKeg != null){
this.setState({
kegFormVisibleOnPage: false,
selectedKeg: null,
editing: false
});
} else {
this.setState(prevState => ({
kegFormVisibleOnPage: !prevState.kegFormVisibleOnPage,
}));
}
}
handleSellingPint = () => {
this.setState({
pints:this.state.pints-1
})
};
render() {
let currentlyVisibleState = null;
let buttonText = null;
if (this.state.editing){
currentlyVisibleState = <EditKegForm keg = {this.state.selectedKeg} onEditKeg = {this.handleEditingKegInDraftList} />
buttonText = "Return to the Draft List"
}
else if (this.state.selectedKeg != null){
currentlyVisibleState = <KegDetail keg = {this.state.selectedKeg} onClickingDelete = {this.handleDeletingKeg}
onClickingEdit = {this.handleEditClick} onSellingPint = {this.handleSellingPint}/>
buttonText = "Return to the Keg List"
My DraftControl.js code
I don't know what I am doing wrong. I cant get the keg.pintsLeft to pass a number when I console.log, So I may be targeting it incorrectly.
Thanks again!
Try it like this:
handleSellingPint = () => {
this.setState(prevState => {
return {
pints: prevState.pints-1
}
})
};
edit
Also, you invoke the onSellingPint() in a wrong way.
It's not a class component, so React doesn't know what does this refer to.
The function itself is passed in as a prop, so you should reference it like this: <button onClick={() => props.onSellingPint() />
handleSellingPint = (id) => {
const clonedArray = [...this.state.fullDraftList]
for (let i = 0; i < this.state.fullDraftList.length; i++){
if (clonedArray[i].id === id){
clonedArray[i].pintsLeft -= 1
}
}
this.setState({
fullDraftList: clone
});
}
Is what I came up with.
Since you are alteriting a state within an array, you need to clone the array and work on that array, not the "real" one.
Thanks for all your help!
Edit: I have included the full code for better clarity
I am not sure if this is possible. I am trying to pass an onClick event via props but the click event does not work.
The parent component looks like this:
import React from 'react'
import { getProductsById } from '../api/get'
import Product from './Product'
import { instanceOf } from 'prop-types'
import { withCookies, Cookies } from 'react-cookie'
class Cart extends React.Component {
static propTypes = {
cookies: instanceOf(Cookies).isRequired
}
constructor(props) {
super(props)
const { cookies } = props;
this.state = {
prods: cookies.get('uircartprods') || '',
collapsed: true,
total: 0,
}
this.expand = this.expand.bind(this)
this.p = [];
}
componentDidMount() {
this.getCartProducts()
}
handleClick = (o) => {
this.p.push(o.id)
const { cookies } = this.props
cookies.set('uircartprods', this.p.toString(), { path: '/' , sameSite: true})
this.setState({prods: this.p })
console.log('click')
}
getCartProducts = async () => {
let products = []
if (this.state.prods !== '') {
const ts = this.state.prods.split(',')
for (var x = 0; x < ts.length; x++) {
var p = await getProductsById(ts[x])
var importedProducts = JSON.parse(p)
importedProducts.map(product => {
const prod = <Product key={product.id} product={product} handler={() => this.handleClick(product)} />
products.push(prod)
})
}
this.setState({products: products})
}
}
expand(event) {
this.setState({collapsed: !this.state.collapsed})
}
handleCheckout() {
console.log('checkout clicked')
}
render() {
return (
<div className={this.state.collapsed ? 'collapsed' : ''}>
<h6>Your cart</h6>
<p className={this.state.prods.length ? 'hidden' : ''}>Your cart is empty</p>
{this.state.products}
<h6>Total: {this.props.total}</h6>
<button onClick={this.handleCheckout} className={this.state.prods.length ? '' : 'hidden' }>Checkout</button>
<img src="./images/paypal.png" className="paypal" alt="Paypal" />
<a className="minify" onClick={this.expand} alt="My cart"></a>
<span className={this.state.prods.length ? 'pulse' : 'hidden'}>{this.state.prods.length}</span>
</div>
)
}
}
export default withCookies(Cart)
The Product component:
import React from 'react';
class Product extends React.Component {
constructor(props) {
super(props);
this.state = {
showDetails: false,
showModal: false,
cart: []
}
this.imgPath = './images/catalog/'
}
render() {
return (
<div className="Product">
<section>
<img src={this.imgPath + this.props.product.image} />
</section>
<section>
<div>
<h2>{this.props.product.title}</h2>
<h3>{this.props.product.artist}</h3>
<p>Product: {this.props.product.product_type}</p>
<h4>${this.props.product.price}</h4>
<button className="button"
id={this.props.product.id} onClick={this.props.handler}>Add to cart</button>
</div>
</section>
</div>
)
}
}
export default Product
If I log this.props.handler I get undefined. Everything works apart from the click handler, I was wondering if it might have something to with the async function. I am very new to React, there are still some concepts I'm not sure about, so any help is appreciated.
Okay, I see a few issues here.
First, there is no need to call this.handleClick = this.handleClick.bind(this) in the constructor, because you are using an arrow function. Arrow functions do not have a this context, and instead, accessing this inside your function will use the parent this found in the Class.
Secondly, it is wrong to store components in state. Instead, map your importedProducts inside the render function.
Thirdly, the issue with your handler is that this.props.handler doesn't actually call handleClick. You will notice in the definition handler={(product) => this.handleClick} it is returning the function to the caller, but not actually calling the function.
Try this instead.
class Product extends React.Component {
constructor(props) {
super(props);
}
render() {
return (
<div>
<button className="button" id={this.props.product.id} onClick={this.props.handler}>
Add to cart
</button>
</div>
);
}
}
export default Product;
import Product from './Product'
class Cart extends React.Component {
constructor(props) {
super(props);
}
handleClick = (o) => {
console.log('click');
};
render() {
return (
<div>
{importedProducts.map((product) => {
return <Product key={product.id} product={product} handler={() => this.handleClick(product)} />;
})}
</div>
);
}
}
export default Cart;
import { connect } from 'react-redux'
import { Link } from 'react-router-dom'
class MyStories extends React.Component {
addFavorite = (e) => {
this.setState({
bgcolor: "blue"
})
}
render () {
const { stories } = this.props;
const { storyBriefs } = this.props.stories.length > 0 ?
stories.map(t => (<div className="menu-inner-container"><p key={t.id}><Link to={`/stories/${t.id}`}>{t.attributes.title}</Link>
<div className="addFavoriteCss"
style={{backgroundColor: this.state.bgColor}}
onClick={this.addFavorite}> Favorite</div>
</p></div>))
//refactor - create a button that will allow for us to mark which our favorites are
return (
{ this.props.storyBriefs }
);
}
}
const mapStateToProps = state => {
return {
stories: state.myStories
}
}
export default connect(mapStateToProps)(MyStories)
getting this error
./src/components/MyStories.js
Line 26: Parsing error: Unexpected token, expected ":"
return (
^
{ this.props.storyBriefs }
);
}
I converted a functional component to a class component so that I could manipulate the state in order to change the color of the favorite button -- (I cannot use hooks or redux for the button function) Can anyone tell me what I am doing wrong?
You need to complete the ternary operator by adding :
const storyBriefs = this.props.stories.length > 0 ?
stories.map(t => (<div className="menu-inner-container"><p key={t.id}><Link to={`/stories/${t.id}`}>{t.attributes.title}</Link>
<div className="addFavoriteCss"
style={{backgroundColor: this.state.bgColor}}
onClick={this.addFavorite}> Favorite</div>
</p></div>))
: [] // you need something here after the ':', an empty array could be useful in this case
return storyBriefs
or you could shorten it to
return stories.map(t => (<div className="menu-inner-container"><p key={t.id}><Link to={`/stories/${t.id}`}>{t.attributes.title}</Link>
<div className="addFavoriteCss"
style={{backgroundColor: this.state.bgColor}}
onClick={this.addFavorite}> Favorite</div>
</p></div>))
As Jaromanda X said { this.props.storyBriefs } isn't valid. you need to provide the key value pair unless the variable doesn't have dot notation then you can define the object like that
This was the final code and it works,
import React from "react"
import { connect } from "react-redux"
import { Link } from "react-router-dom"
class MyStories extends React.Component {
constructor(props) {
super(props);
this.state = {
button: false
};
this.addFavorite = this.addFavorite.bind(this);
}
addFavorite = id => {
this.setState({
button: id
});
};
render() {
return this.props.stories.map(t => (
<div className="menu-inner-container">
<p key={t.id}>
<Link to={`/stories/${t.id}`}>{t.attributes.title}</Link>
<button
key={t.id}
className={this.state.button===t.id ? "buttonTrue" : "buttonFalse"}
onClick={() => this.addFavorite(t.id)}
>
Favorites
</button>
</p>
</div>
));
}
}
//refactor - create a button that will allow for us to mark which our favorites are
const mapStateToProps = state => {
return {
stories: state.myStories
};
};
export default connect(mapStateToProps)(MyStories);
I am trying to add a click handler to each button that is generated in a loop and inserted into an array.
However, clicking a button always outputs the last button of each row of buttons and not the specific button itself.
My code is rather verbose, but we only need to be looking at the time.push() part and the click handler setup. Everything else is just setup.
import React from 'react';
import { friendlyTimeSlot, scopedTimeslots } from '../../utilities/helpers';
class TimeSlotStack extends React.Component {
constructor() {
super();
this.clickHandler = this.clickHandler.bind(this);
this.state = {
times: undefined
};
}
componentWillMount() {
this.updatePropsAndState(this.props);
}
componentWillReceiveProps(nextProps) {
this.updatePropsAndState(nextProps);
this.forceUpdate();
}
updatePropsAndState(props) {
const time = [];
let matchedTimeSlots;
if (props.promotionId) {
matchedTimeSlots = props.timeSlots.filter(timeSlot => {
const timeSlotsIds = timeSlot.AvailablePromotions.map(p => p.Id);
if (timeSlotsIds.includes(props.promotionId)) {
return timeSlot;
}
return false;
});
} else {
matchedTimeSlots = props.timeSlots.filter(timeSlot => timeSlot.HasStandardAvailability);
}
const scopedTimes = scopedTimeslots(matchedTimeSlots, props.preferredTimeSlot);
scopedTimes.forEach((item, i) => {
const friendlyTime = friendlyTimeSlot(item.TimeSlot, true);
const leaveTimeRequired = item.IsLeaveTimeRequired;
let itemPromo;
let leaveTime;
let itemPrice;
if (props.promotionId) {
itemPromo = item.AvailablePromotions.find(ourItem => ourItem.Id === props.promotionId);
leaveTime = itemPromo.LeaveTime || item.LeaveTime;
itemPrice = (itemPromo.BasePrice > 0) ? `£${itemPromo.BasePrice}` : '';
} else {
leaveTime = item.LeaveTime;
}
time.push(
<button
className="btn btn-default"
type="button"
onClick={(e) => this.clickHandler(e)}
ref={input => {
this.button = input;
}}
key={i}
data-time={friendlyTime}
data-leave-time-required={leaveTimeRequired}
data-leave-time={leaveTime.slice(0, -3)}
data-promotion-id={props.promotionId}
>
{friendlyTimeSlot(item.TimeSlot)}<br />{itemPrice}
</button>
);
});
this.setState({
times: time
});
}
clickHandler(e) {
e.preventDefault();
console.log(this.button.dataset);
}
render() {
if (this.state.times && this.props.name && this.props.description) {
return (
<div className="panel panel-default">
<div className="panel-heading">
<h3 className="panel-title">{this.props.name}</h3>
</div>
<div className="panel-body">
<p>{this.props.description}</p>
{this.state.times}
</div>
</div>
);
}
return (
<p>No times available.</p>
);
}
}
TimeSlotStack.propTypes = {
name: React.PropTypes.string.isRequired,
description: React.PropTypes.string.isRequired,
timeSlots: React.PropTypes.array.isRequired,
preferredTimeSlot: React.PropTypes.string.isRequired,
promotionId: React.PropTypes.number
};
export default TimeSlotStack;
When I then click a button, I always get the last button from each list. Hopefully the screenshot below will help make this clearer:
The log above comes from:
clickHandler(e) {
e.preventDefault();
console.log(this.button.dataset);
}
...but was generated by clicking the first buttons of each row. You can see that it always outputs the last only.
Is there something I'm doing wrong? This is my first React project and it's gotten me all flustered. Please let me know if I'm doing something that's not the React way that could be causing this.
Thanks!
You are overwriting the button variable, this in this context is a reference to a TimeSlotStack instance. To do what you want you need to maintain a list of buttons, for instance.
constructor() {
super();
this.clickHandler = this.clickHandler.bind(this);
this.buttons = [];
this.state = {
times: undefined
};
}
....
// using a IFE so `clickHandler` is called with the correct index
((idx) => {
time.push(
<button
className="btn btn-default"
type="button"
onClick={(e) => this.clickHandler(e, idx)}
ref={button => {
this.buttons.push(button);
}}
key={idx}
data-time={friendlyTime}
data-leave-time-required={leaveTimeRequired}
data-leave-time={leaveTime.slice(0, -3)}
data-promotion-id={props.promotionId}
>
{friendlyTimeSlot(item.TimeSlot)}<br />{itemPrice}
</button>
);
})(i);
....
clickHandler(e, i) {
e.preventDefault();
console.log(this.buttons[i].dataset);
}