New to React, OnClick not calling function - javascript

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;

Related

Parent scope not triggering child rerender in React

i have a prent comp and a child cmponent. as follows
parent
export class ExpressionMenu extends Component {
constructor(props) {
super(props)
}
state = {
apiArray: [],
}
updateStateFromChild = (arrayType, propertyType, value) => {
let { apiArray } = this.state
let currentArray = []
let idx = apiArray.findIndex((q) => q.id === id)
currentArray = apiArray
switch(propertyType) {
case 'updateObject': {
currentArray = value
break;
}
}
this.setState({
apiArray: currentArray
})
}
render () {
const {
apiArray
} = this.state
return (
<React.Fragment>
<div >
<div>
<ApiPanel
apiArray={apiArray}
updateStateFromChild={this.updateStateFromChild}
/>
</div>
</div>
</React.Fragment>
)
}
}
ExpressionMenu.propTypes = {
styleOverride: PropTypes.object,
eventHandler: PropTypes.func,
};
export default ExpressionMenu;
child
export class ApiPanel extends Component {
constructor(props) {
super(props),
}
removeApi = (id) => {
let { apiArray } = this.props
apiArray = apiArray.filter((q) => q.id !== id);
this.props.updateStateFromChild('api', 'updateObject', apiArray)
};
addApi = () => {
let { apiArray } = this.props
const id = uniqid();
let obj = {}
obj.id = id
apiArray.push(obj)
this.props.updateStateFromChild('api', 'updateObject', apiArray)
};
render() {
const { apiArray } = this.props
return (
<React.Fragment>
{
apiArray.map((apiObj, i) =>
<div key={i} >
<span onClick={() => this.removeApi(apiObj.id) } className={[classes.deleteRow,'material-icons'].join(' ')}>
close
</span>
<div>
<label><b>Hi</b></label>
</div>
<div onClick={this.addApi}>+Add Api</div>
}
</React.Fragment>
)
}
}
ApiPanel.propTypes = {
apiArray: PropTypes.array,
updateStateFromChild: PropTypes.func
}
export default ApiPanel
Now when i call addApi(), it updates the parent but doesnt rerenders the child.
But when i call removeApi() , it updates parent as well as rerenders the child component properly.
in the first case when i manually reload the componnt i can see the change.
Dont understand why this is happening
Try to change your addApi function.
addApi = () => {
let { apiArray } = this.props
this.props.updateStateFromChild('api', 'updateObject', [...apiArray, {id : uniqid()} ])
};
You need to return an enriched copy of your array
Whenever we are updating the stating using arrays, objects. We need to always create a new array [...array], a new object {...obj}. Because if we update value in the array or obj without creating it won't change the reference value hence it assumes the state is not update and won't re-render.

Trying to get a counter to work with React and multiple components

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!

Accessing updated props from parent stateless function component from child stateless class component

child component
export class child extends Component {
buttonclick() {
const { pin } = this.props
if (pin === null) {
add().then(result => {
updatePin(result.data)
})
} else {
remove(pin.id).then(result => {
updatePin(result)
})
}
}
render() {
const { pin } = this.props
const label =
pin === null
? 'yes'
: 'no'
const icon =pin === null ? 'yes' : 'no'
return (
<div>
<Button
icon={icon}
label={label}
onClick={() => this.buttonclick()}
/>
</div>
)
}
}
parent component(classless component)
const parent= props =>{
const { pins = []} = props
const { pin } = Data
}
const updatePin = result => {
// here iam updating the pin
}
const renderchildComponent=()=>{
return(
<div>
<ChildComponent
pin={pin}
updatePin={result => updatePin(result)}
/>
</div>
)
}
here in the above code the pin is updating in the parent component but how to pass it to child component every time when the button click happens without refreshing the page. please help me out with this

Maximum call stack size exceeded - Connected React Component

I can't for the life of me figure out why I'm getting error:
Maximum call stack size exceeded
When this code is run. If I comment out:
const tabs = this.getTabs(breakpoints, panels, selectedTab);
the error goes away. I have even commented out other setState() calls to try and narrow down where the problem was at.
Code (removed the extra functions):
export default class SearchTabs extends Component {
constructor() {
super();
this.state = {
filters: null,
filter: null,
isDropdownOpen: false,
selectedFilter: null,
};
this.getTabs = this.getTabs.bind(this);
this.tabChanged = this.tabChanged.bind(this);
this.setSelectedFilter = this.setSelectedFilter.bind(this);
this.closeDropdown = this.closeDropdown.bind(this);
this.openDropdown = this.openDropdown.bind(this);
}
componentDidMount() {
const { panels } = this.props;
if (!panels || !panels.members || panels.members.length === 0) {
this.props.fetchSearch();
}
}
getTabs(breakpoints, panels, selectedTab) {
const tabs = panels.member.map((panel, idx) => {
const { id: panelId, headline } = panel;
const url = getHeaderLogo(panel, 50);
const item = url ? <img src={url} alt={headline} /> : headline;
const classname = classNames([
searchResultsTheme.tabItem,
(idx === selectedTab) ? searchResultsTheme.active : null,
]);
this.setState({ filter: this.renderFilters(
panel,
breakpoints,
this.setSelectedFilter,
this.state.selectedFilter,
this.state.isDropdownOpen,
) || null });
return (
<TabItem
key={panelId}
classname={`${classname} search-tab`}
headline={headline}
idx={idx}
content={item}
onclick={this.tabChanged(idx, headline)}
/>
);
});
return tabs;
}
render() {
const { panels, selectedTab } = this.props;
if (!panels || panels.length === 0) return null;
const tabs = this.getTabs(breakpoints, panels, selectedTab);
return (
<div className={searchResultsTheme.filters}>
<ul className={`${searchResultsTheme.tabs} ft-search-tabs`}>{tabs}</ul>
<div className={searchResultsTheme.dropdown}>{this.state.filter}</div>
</div>
);
}
}
export const TabItem = ({ classname, content, onclick, key }) => (
<li key={key} className={`${classname} tab-item`} onClick={onclick} >{content}</li>
);
Because of this loop:
render -----> getTabs -----> setState -----
^ |
| |
|____________________________________________v
You are calling getTabs method from render, and doing setState inside that, setState will trigger re-rendering, again getTabs ..... Infinite loop.
Remove setState from getTabs method, it will work.
Another issue is here:
onclick={this.tabChanged(idx, headline)}
We need to assign a function to onClick event, we don't need to call it, but here you are calling that method, use this:
onclick={() => this.tabChanged(idx, headline)}

Click handler for each button incorrectly returns the last button of a set

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);
}

Categories

Resources