React Calling Parent's Method Nothing Happens - javascript

Probably a simple question and have so many people answered here but in this scenario, I cannot figure out why it doesn't work ...
In the parent I have
updateAccountNumber = value => {
console.log(value);
};
<Child updateAccountNumber={this.updateAccountNumber} />
In the child I have
<ListItem
button
key={relatedSub.office + relatedSub.account}
onClick={() =>
this.props.updateAccountNumber(
relatedSub.office + relatedSub.account
)
}
Even if I do parent like this, still no help..
<Child updateAccountNumber={() => this.updateAccountNumber()} />
if I have the below child item, then when I click on the menu that runs the child items, the component calls itself as many items as there are...
onClick={this.props.updateAccountNumber(
relatedSub.office + relatedSub.account
)}
It won't even run the below code, simple code, I can't see why it wouldn't kick of the handleClick event...
import React, { Component } from "react";
import List from "#material-ui/core/List";
import ListItem from "#material-ui/core/ListItem";
import ListItemText from "#material-ui/core/ListItemText";
const handleClick = () => {
debugger;
alert("sda");
console.log("bbb");
};
export default class RelatedSubAccounts extends Component {
Links = () => {
if (this.props.RelatedSubAccounts) {
let RelatedSubArray = this.props.RelatedSubAccounts;
let source = RelatedSubArray.map(relatedSub => (
<ListItem
button
onClick={handleClick}
key={relatedSub.office + relatedSub.account}
className={
relatedSub.office + relatedSub.account !== this.props.OfficeAccount
? ""
: "CurrentRelatedSub"
}
>
<ListItemText primary={relatedSub.office + relatedSub.account} />
</ListItem>
));
return (
<div id="RelatedSubLinks">
<List>{source}</List>
</div>
);
} else {
return "";
}
};
render() {
return this.Links();
}
}
Please ask if any other related code is missing, and I can share.

I was able to get an example that works with the code you shared by using RelatedSubAccounts like this:
<RelatedSubAccounts RelatedSubAccounts={[{ office: 1, account: 2 }]} />
Code Sandbox Example
I see a few things that stand out as potentially confusing. I will point them out in code below with comments:
import React, { Component } from "react";
import List from "#material-ui/core/List";
import ListItem from "#material-ui/core/ListItem";
import ListItemText from "#material-ui/core/ListItemText";
const handleClick = () => {
debugger;
alert("RelatedSubAccounts clicked");
console.log("bbb");
};
export default class RelatedSubAccounts extends Component {
// Capitalization like this in react normally indicates a component
Links = () => {
/*
Having a prop that is the same name as the component and capitalized is confusing
In general, props aren't capitalized like the component unless you are passing a component as a prop
*/
if (this.props.RelatedSubAccounts) {
// Again, capitalization on RelatedSubArray hints that this is a component when it really isn't
let RelatedSubArray = this.props.RelatedSubAccounts;
let source = RelatedSubArray.map(relatedSub => (
<ListItem
button
onClick={handleClick}
key={relatedSub.office + relatedSub.account}
className={
relatedSub.office + relatedSub.account !== this.props.OfficeAccount
? ""
: "CurrentRelatedSub"
}
>
<ListItemText primary={relatedSub.office + relatedSub.account} />
</ListItem>
));
return (
<div id="RelatedSubLinks">
<List>{source}</List>
</div>
);
} else {
return "";
}
};
render() {
return this.Links();
}
}

This is going to be strangest solution but might give you a lesson or two.. I found the cause of the issue here.
So I have a menu like this
When you click on that arrow to open up the menu, it becomes active and when you click away onBlur kicks in and that menu goes away.. (menu created used react-select Creatable)
DropdownIndicator = props => {
return (
<div
onBlur={() => {
this.setState({ Focused: false, RelatedSub: false });
}}
so I had to update it to below:
onBlur={() => {
this.setState({ Focused: false });
setTimeout(() => {
this.setState({ RelatedSub: false });
}, 100);
}}

Related

Clicking a left arrow or a right arrow to switch an element in an array with the element on the left or right React

I am new to React and am trying to build an app in which a user can create a card, delete a card, and change the order of the cards array by clicking left or right arrow to switch elements with the element on the left or on the right.
I am struggling to code this functionaliy. I have the function written to switch the card with that on the left, but this function is not doing anything right now. I also do not get any errors in the console from this function, so I really cannot determine where I am going wrong here.
Here is the code so far:
CardList.js will display the form to add a card and display the array of CardItems, passing the functions to switch these items to the left or right ('moveLeft', 'moveRight') as props.
import React from "react";
import CardItem from "./CardItem";
import CardForm from "./CardForm";
import './Card.css';
class CardList extends React.Component {
state = {
cards: JSON.parse(localStorage.getItem(`cards`)) || []
// when the component mounts, read from localStorage and set/initialize the state
};
componentDidUpdate(prevProps, prevState) { // persist state changes to longer term storage when it's updated
localStorage.setItem(
`cards`,
JSON.stringify(this.state.cards)
);
}
render() {
const cards = this.getCards();
const cardNodes = (
<div style={{ display: 'flex' }}>{cards}</div>
);
return (
<div>
<CardForm addCard={this.addCard.bind(this)} />
<div className="container">
<div className="card-collection">
{cardNodes}
</div>
</div>
</div>
);
}
addCard(name) {
const card = {
name
};
this.setState({
cards: this.state.cards.concat([card])
}); // new array references help React stay fast, so concat works better than push here.
}
removeCard(index) {
this.state.cards.splice(index, 1)
this.setState({
cards: this.state.cards.filter(i => i !== index)
})
}
moveLeft(index,card) {
if (index > 1) {
this.state.cards.splice(index, 1);
this.state.cards.splice((index !== 0) ? index - 1 : this.state.cards.length, 0, card)
}
return this.state.cards
}
moveRight(index, card) {
// ?
}
getCards() {
return this.state.cards.map((card) => {
return (
<CardItem
card={card}
index={card.index}
name={card.name}
removeCard={this.removeCard.bind(this)}
moveLeft={this.moveLeft.bind(this)}
moveRight={this.moveRight.bind(this)}
/>
);
});
}
}
export default CardList;
CardItem is taking in those props and ideally handling moving the card left or right in the array once the left or right icon is clicked.
import React from 'react';
import Card from "react-bootstrap/Card";
import Button from "react-bootstrap/Button";
class CardItem extends React.Component {
render() {
return (
<div>
<Card style={{ width: '15rem'}}>
<Card.Header as="h5">{this.props.name}</Card.Header>
<Card.Body>
<Button variant="primary" onClick={this.handleClick.bind(this)}>Remove</Button>
</Card.Body>
<Card.Footer style={{ display: 'flex' }}>
<i class="arrow left icon" onClick={this.leftClick.bind(this)} style={{ color: 'blue'}}></i>
<i class="arrow right icon" onClick={this.rightClick.bind(this)} style={{ color: 'blue'}}></i>
</Card.Footer>
</Card>
</div>
)
}
handleClick(index) {
this.props.removeCard(index)
}
leftClick(index, card) {
this.props.moveLeft(index, card)
}
rightClick(index, card) {
this.props.moveRight(index, card)
}
}
export default CardItem;
Not sure where I am going wrong here. Any help would be appreciated
Edit #1
Hey guys, so I wrote out a different function to handle moving the card to the left, and I decided to bind "this" to that method in the constructor because I was getting errors saying the program could not read it. However, I am still getting errors basically saying that everything is not defined when I pass the function from CardList to CardItem as props. Does anybody know what the problem is? I suspect its my syntax when I call the methods in CardItem.
CardList.js
import React from "react";
import CardItem from "./CardItem";
import CardForm from "./CardForm";
import './Card.css';
class CardList extends React.Component {
constructor(props) {
super();
this.moveLeft = this.moveLeft.bind(this);
this.moveRight = this.moveRight.bind(this);
this.state = {
cards: JSON.parse(localStorage.getItem(`cards`)) || []
// when the component mounts, read from localStorage and set/initialize the state
};
}
componentDidUpdate(prevProps, prevState) { // persist state changes to longer term storage when it's updated
localStorage.setItem(
`cards`,
JSON.stringify(this.state.cards)
);
}
render() {
const cards = this.getCards();
const cardNodes = (
<div style={{ display: 'flex' }}>{cards}</div>
);
return (
<div>
<CardForm addCard={this.addCard.bind(this)} />
<div className="container">
<div className="card-collection">
{cardNodes}
</div>
</div>
</div>
);
}
addCard(name) {
const card = {
name
};
this.setState({
cards: this.state.cards.concat([card])
}); // new array references help React stay fast, so concat works better than push here.
}
removeCard(index) {
this.state.cards.splice(index, 1)
this.setState({
cards: this.state.cards.filter(i => i !== index)
})
}
moveLeft(index, card) {
this.setState((prevState, prevProps) => {
return {cards: prevState.cards.map(( c, i)=> {
// also handle case when index == 0
if (i === index) {
return prevState.cards[index - 1];
} else if (i === index - 1) {
return prevState.cards[index];
}
})};
});
}
moveRight(index, card) {
// ?
}
getCards() {
return this.state.cards.map((card) => {
return (
<CardItem
card={card}
index={card.index}
name={card.name}
removeCard={this.removeCard.bind(this)}
moveLeft={this.moveLeft}
moveRight={this.moveRight}
/>
);
});
}
}
export default CardList;
CardItem.js
import React from 'react';
import Card from "react-bootstrap/Card";
import Button from "react-bootstrap/Button";
class CardItem extends React.Component {
render() {
return (
<div>
<Card style={{ width: '15rem'}}>
<Card.Header as="h5">{this.props.name}</Card.Header>
<Card.Body>
<Button variant="primary" onClick={this.handleClick.bind(this)}>Remove</Button>
</Card.Body>
<Card.Footer style={{ display: 'flex' }}>
<i class="arrow left icon" onClick={leftClick(index, card)} style={{ color: 'blue'}}></i>
<i class="arrow right icon" onClick={rightClick(index, card)} style={{ color: 'blue'}}></i>
</Card.Footer>
</Card>
</div>
)
}
handleClick(index) {
this.props.removeCard(index)
}
leftClick(index, card) {
this.props.moveLeft(index,card)
}
rightClick(index, card) {
this.props.moveRight(index, card)
}
}
export default CardItem;
To update state arrays in React, you shouldn't use splice, push or the [] operator.
Instead use the methods that return a new array object viz. map, filter, concat,slice.
For a detailed explanation, see this article.
So you can do something like :
moveLeft(index,card) {
this.setState((prevState, prevProps)=> {
return {cards: prevState.cards.map((c,i)=> {
// also handle case when index == 0
if(i == index) {
return prevState.cards[index-1];
} else if(i == index-1) {
return prevState.cards[index];
}
})};
});
}
When updating React state using the previous value, always use
setState((prevState,prevProps)=>{ return ...})
as such state updates may be asynchronous. See React docs.
Since you are calling the parent component method from child, it's better to bind these methods in the CardList constructor. Eg:
this.moveLeft = this.moveLeft.bind(this);
this.moveRight ....

How do I integrate my Switch component into my Table component as a separate column, and have separate state on both?

I am new to React.js, and so far, I am loving it. I am still confused on the concept of stateful components, although. I am using Bootstrap tables to build my table, and my GET request for its data grab worked flawlessly. I am using the material-ui lib for my switch component as well (no need to reinvent the wheel here!)
Although, I am now trying to integrate a new column that will be a switch for each row in my table, and that, when toggled, changes the boolean of said switch to true/false, which will then send a PUT request down to my backend. I have not built my PUT request yet, as I cannot get this UI portion functioning. Here is my code so far, and the dumby UI works, but I don't know how to integrate the stateful render I defined in NodeTableContainer at <SwitchState/> and SwitchState(), into my definition at selectionRenderer: Switches in my NodeTable component. The stateful render does render a toggle switch under the table, essentially as its own independent component. But I want to integrate that toggle switch component in const selectRow = {mode: 'checkbox', clickToSelect: true,selectionRenderer: Switches}. Here is my code, and I hope my I have explained my issue well. I have Googled endlessly, but I believe my own ignorance has blocked my from discovering the answer I need.
Table Component (NodeTable)
import React from 'react';
import {
Row,
Col,
Card,
CardBody,
} from 'reactstrap';
import BootstrapTable from 'react-bootstrap-table-next';
import ToolkitProvider, { Search, CSVExport, ColumnToggle } from 'react-bootstrap-table2-toolkit';
import paginationFactory from 'react-bootstrap-table2-paginator';
import 'chartjs-plugin-colorschemes';
import Switches from './Switch'
const columns = OMIT
const defaultSorted = [
{
dataField: 'id',
order: 'asc',
},
]
const TableWithSearch = (props) => {
const { SearchBar } = Search;
const { ExportCSVButton } = CSVExport;
const selectRow = {
mode: 'checkbox',
clickToSelect: true,
selectionRenderer: Switches
}
return (
<Card>
<CardBody>
<h4 className="header-title">OMIT</h4>
<p className="text-muted font-14 mb-4">OMIT</p>
<ToolkitProvider
bootstrap4
keyField="fqn"
data={props.data}
columns={columns}
columnToggle
search
exportCSV={{ onlyExportFiltered: true, exportAll: false }}>
{props => (
<React.Fragment>
<Row>
<Col>
<SearchBar {...props.searchProps} />
</Col>
<Col className="text-right">
<ExportCSVButton {...props.csvProps} className="btn btn-primary">
Export CSV
</ExportCSVButton>
</Col>
</Row>
<BootstrapTable
{...props.baseProps}
bordered={false}
defaultSorted={defaultSorted}
pagination={paginationFactory({ sizePerPage: 5 })}
selectRow={selectRow}
wrapperClasses="table-responsive"
/>
</React.Fragment>
)}
</ToolkitProvider>
</CardBody>
</Card>
);
};
export default TableWithSearch;
Switch Component
// #flow
import React from 'react';
import 'chartjs-plugin-colorschemes';
import './Switch.css'
import Switch from '#material-ui/core/Switch';
export default function Switches({ isOn, handleToggle }) {
return (
<div>
<Switch
checked={isOn}
onChange={handleToggle}
name="checkedA"
inputProps={{ 'aria-label': 'secondary checkbox' }}
/>
</div>
);
}
Parent Component (NodeTableContainer)
import axios from 'axios';
import React, { Component, useState } from 'react';
import Switch from './Switch';
import App from './index';
export default class MainComp extends React.Component {
state = {
nodesData: [],
chartRef: [],
conn: [],
switchstate: [],
}
componentDidMount() {
axios.get('OMIT')
.then(res => {
const nodestate = res.data.map(x => x.nodestate);
for (var i = 0; i < nodestate.length; i++) {
if (nodestate[i] == 'up') {
nodestate[i] = true;
}
else {
nodestate[i] = false;
}
}
this.setState({ nodesData: res.data, switchstate: nodestate });
})
}
render() {
return (
< >
<App data={this.state.nodesData} checked={this.state.switchstate} />,
<SwitchState />
</>
)
}
}
function SwitchState() {
const [value, setValue] = useState(false);
console.log(value)
return (
<div className="app">
<Switch
isOn={value}
onColor="#EF476F"
handleToggle={() => setValue(!value)}
/>
</div>
);
}
Also, my SwitchState component is in a dumby form as you will see, until I can see the log showing its boolean state changing. Also, nodestate in the NodeTableContainer was my pathetic try at pulling data via the same state data. That is nonfunctional as you will also see. I will build the state properly once I can get this figured out, or you wonderful individuals aid me in this as well. Again, I am showing my ignorance here, so if there is an easier way, or if I am using an odd flavor of libs for this, please let me know. I want to learn and thrive. If you have a solution of your own, that's a completely different flavor, I plea to you to share it! Thank you all!
I figured this out for react-bootstrap. I fat arrowed in the formatter, and passed the state to formatExtraData. I then pass state from my component that holds all state, and it works flawlessly. Time to integrate my PUT request in with the event handler!
Below are my changes in code:
Table Component
export default class TableComp extends React.Component
formatter: (cell, row, index, extra) => {
if (cell === 'up') {
cell = true
}
else {
cell = false
}
return (
<div>
<Switch
checked={cell}
onChange={extra.handle}
name={row.name}
inputProps={{ 'aria-label': 'secondary checkbox' }}
/>
</div>
)
},
formatExtraData: { handle: this.props.handleClick }
Parent Component (Holds all state)
handleClick = (e) => {
var indexFound = this.state.data.findIndex(y => y.name === e.target.name.toString())
let data= [...this.state.data];
let item = { ...data[indexFound] }
if (item.state === 'up') {
item.state = 'down'
}
else {
item.state = 'up'
}
data[indexFound] = item
this.setState({ data})
}

Need tabs to hold their own state, so it can hold its own number of items within

import axios from 'axios';
import React from 'react';
import { makeStyles } from '#material-ui/core/styles';
import TextField from '#material-ui/core/TextField';
import Button from '#material-ui/core/Button';
import StarIcon from '#material-ui/icons/Star';
import List from '#material-ui/core/List';
import ListItem from '#material-ui/core/ListItem';
import Paper from '#material-ui/core/Paper';
import Tabs from '#material-ui/core/Tabs';
import Tab from '#material-ui/core/Tab';
import { FontAwesomeIcon } from '#fortawesome/react-fontawesome';
import TwitterIcon from '#material-ui/icons/Twitter';
import CloseIcon from '#material-ui/icons/Close';
import Highlighter from 'react-highlight-words';
class TwitterBot extends React.Component {
constructor(props) {
super(props);
this.handleChange = this.handleChange.bind(this);
this.handleTabState = this.handleTabState.bind(this);
}
state = {
loaded: [],
searched: [],
searchedTicker: '',
actveTab: '',
addedTickers: []
};
async componentDidMount() {
//Gathering data from heroku API I built and adding tweets to loaded array state
let feed = await axios.get('https://boiling-plains-63502.herokuapp.com/');
let tweets = feed.data;
this.setState({
loaded: tweets
});
}
handleChange = (e) => {
//Watching input and changing searchedTicker string while typing
this.setState({ searchedTicker: e.target.value });
};
handleTabChange = (event, newValue) => {
//Selecting the correct tab
this.setState({ tabValue: newValue });
};
handleTabState = (e, data) => {
//This is changing searchedTicker state to the value of whichever tab is clicked
this.setState({ searchedTicker: data });
};
showAll = () => {
//Clearing searched state
this.setState({ searchedTicker: '' });
};
addTicker = () => {
// Adding ticker to saved list
if (this.state.searchedTicker.length > 0) {
this.setState((state) => {
const tickers = state.addedTickers.push(state.searchedTicker);
return {
tickers,
searchedTicker: ''
};
});
} else {
alert('Plase enter a symbol to search');
}
};
removeTicker = (e, data) => {
// Removing tab
let tickers = this.state.addedTickers;
if (tickers.indexOf(data) === 0) {
tickers.shift();
this.showAll();
console.log('zero');
} else {
tickers.splice(tickers.indexOf(data));
this.showAll();
}
};
savedTickerFilter = (f) => {
this.setState({ searchedTicker: f.target.value });
};
render() {
//Trimming searched input to all lowercase and filtering displayed post within return based on search
let loaded = this.state.loaded,
searchedTicker = this.state.searchedTicker.trim().toLowerCase();
if (searchedTicker.length > 0) {
loaded = loaded.filter(function(i) {
return i.text.toLowerCase().match(searchedTicker);
});
}
//Copying loaded state and attempting to added individual numbers of tweets to each tab
let copyOfLoaded = [ ...this.state.loaded ];
let filterCopy = copyOfLoaded.filter(function(i) {
return i.text.toLowerCase().match(searchedTicker);
});
let numOfTweets = filterCopy.length;
return (
<div className="main" style={{ marginTop: 40 + 'px' }}>
<h4>Search a stock symbol below to find relevant tweets from Stocktwitz.</h4>
<h4>You may then press Add to Favorites to create a saved tab for later reference.</h4>
<div className="main__inner">
<TextField
type="text"
value={this.state.searchedTicker}
onChange={this.handleChange}
placeholder="Search Ticker..."
id="outlined-basic"
label="Search"
variant="outlined"
/>
<Button onClick={this.addTicker} variant="contained" color="primary">
Add to favorites <StarIcon style={{ marginLeft: 10 + 'px' }} />
</Button>
</div>
{/* This will be the Filter Tabs component and that will import the list thats below the Paper component below */}{' '}
<Paper square>
<Tabs indicatorColor="primary" textColor="primary" onChange={this.handleTabChange}>
<Tab label={<div className="tabs-label">All ({loaded.length})</div>} onClick={this.showAll} />
{//Mapping through tabs that are added in TwitterBot component and passed down as props to this component
this.state.addedTickers.map((i) => {
return (
<div className="tab-container">
<Tab
label={
<div className="tabs-label">
{i}
({numOfTweets})
</div>
}
key={i}
onClick={(e) => this.handleTabState(e, i)}
/>
<CloseIcon value={i} onClick={(e) => this.removeTicker(e, i)} />
</div>
);
})}
</Tabs>
</Paper>
<List className="tweets">
{loaded.map(function(i) {
return (
<ListItem key={i.id}>
{' '}
<TwitterIcon style={{ marginRight: 10 + 'px', color: '#1da1f2' }} />
<Highlighter
highlightClassName="YourHighlightClass"
searchWords={[ searchedTicker ]}
autoEscape={true}
textToHighlight={i.text}
/>,
</ListItem>
);
})}
</List>
</div>
);
}
}
export default TwitterBot;
Above is the entire component that holds all necessary logic.
I basically want {{numOfTweets}} within the tab-label to be static to each Tab thats mapped through once created. Right now it correctly will show how many items per tab there are while searching, and if clicked on current tab, but all tabs will react. I need them to stay static after search so if clicked on another tab, the past tab will still show how many tweets there were for that searched tab. Right now it's happening just because it's referencing the global loaded state, I just need way to copy that and render each one individually. I hope I explained that clear enough. You can see what I mean on my demo here: https://5ec5b3cfc2858ad16d22bd3c--elastic-khorana-7c2b7c.netlify.app/
I understand I need to break out and componentize this more, but I know theres has to be an easy solution, somehow using a simple functional component to wrap the Tab component or simple just the number that will be displayed. (I'm using Material UI)
Thank you, anything helps, just need to wrap my head around it.
Please check the codesandbox here https://codesandbox.io/s/proud-leftpad-j0rgd
I have added an object instead of a string for Addedtickers so that the count can be tracked and remains constant throughout. You can further optimize this , if you want to search again within each individual tab, but you get the gist.
Please let me know if this works for you

onMouseOver() function does not trigger in react

So I am trying to use onMouseOver on my React component like this
<CookieDetails
key={cookie.id}
name={cookie.name}
cost={cookie.cost}
value={cookie.cost}
numOwned={purchasedItems[cookie.id]}
onMouseOver={event => {
console.log('cookies');
if (numCookies < cookie.cost) {
alert('Not Enough Cookies');
return;
}
setPurchasedItems({
...purchasedItems,
[cookie.id]: purchasedItems[cookie.id] + 1,
});
setNumCookies(numCookies - cookie.cost);
console.log('purchased', purchasedItems[cookie.id], numCookies);
}}
/>;
and here is whats inside my component for now
import React from 'react';
const CookieDetails = ({ name, cost, value, numOwned }) => {
return (
<React.Fragment>
<div className="cookie-details-wrapper">
<h3>{name}</h3>
<p>Cost:{cost}</p>
<p>Value:{value}</p>
<p>Owned:{numOwned}</p>
</div>
</React.Fragment>
);
};
export default CookieDetails;
However when I mouse over my component, nothing happens. Doesn't event console log cookies. Any help would be appreciated
CookieDetails isn't a DOM element (so it doesn't have native handling for events) it is a component you wrote. You don't do anything with the onMouseOver prop.
You need to read that prop and attach it to a DOM element (like the div).
const CookieDetails = ({ name, cost, value, numOwned, onMouseOver }) => {
return (
<React.Fragment>
<div onMouseOver={onMouseOver} className="cookie-details-wrapper">
So here onMouseOver is a prop that you are passing to children. You need to trigger onMouseOver on children
<CookieDetails
key={cookie.id}
name={cookie.name}
cost={cookie.cost}
value={cookie.cost}
numOwned={purchasedItems[cookie.id]}
onMouseOver={event => {
console.log('cookies');
if (numCookies < cookie.cost) {
alert('Not Enough Cookies');
return;
}
setPurchasedItems({
...purchasedItems,
[cookie.id]: purchasedItems[cookie.id] + 1,
});
setNumCookies(numCookies - cookie.cost);
console.log('purchased', purchasedItems[cookie.id], numCookies);
}}
/>;
import React from 'react';
const CookieDetails = ({ name, cost, value, numOwned, onMouseOver }) => {
return (
<React.Fragment>
<div className="cookie-details-wrapper" onMouseOver={onMouseOver}>
<h3>{name}</h3>
<p>Cost:{cost}</p>
<p>Value:{value}</p>
<p>Owned:{numOwned}</p>
</div>
</React.Fragment>
);
};
export default CookieDetails;

Pass onClick to material-ui button - works only once

I'm trying to wrap material-ui button into another component. Everything goes fine unless I've tried to handle onClick event. It seems that it works only once.
(not) Working example available at:
https://codesandbox.io/embed/material-demo-nn0ut?fontsize=14
Source code:
import React from "react";
import { useState } from "react";
import MaterialButton from "#material-ui/core/Button";
import { Component } from "react";
import { withStyles } from "#material-ui/core";
const stylesMain = {
root: {
fontSize: 16
}
};
const stylesSecondary = {
root: {
fontSize: 14
}
};
const StyledButtonMain = withStyles(stylesMain)(MaterialButton);
const StyledButtonSecondary = withStyles(stylesSecondary)(MaterialButton);
class Button extends Component {
constructor(props) {
super(props);
this.onClick = function() {};
this.href = null;
this.target = null;
this.type = "button";
if (props.onClick) {
this.onClick = props.onClick;
}
if (props.href) {
this.href = props.href;
}
if (props.target) {
this.target = props.target;
}
if (props.type) {
this.type = props.type;
}
}
render() {
const StyledButton =
this.props.color === "secondary"
? StyledButtonSecondary
: StyledButtonMain;
return (
<StyledButton
type={this.type}
href={this.href}
target={this.target}
onClick={this.onClick}
variant="contained"
style={{ whiteSpace: "nowrap" }}
>
{this.props.children}
</StyledButton>
);
}
}
export default function Counter(props) {
const [counter, setCounter] = useState(0);
return (
<div>
<h1>Counter: {counter}</h1>
<Button
onClick={() => {
setCounter(counter + 1);
}}
>
ClickMe
</Button>
</div>
);
}
I've expected, that onClick should work in the same manner as in "bare" material ui button. How can I fix that?
Your issue is that you're binding the onClick function in the Button constructor. As you may know, the constructor function is only called once, whenever an instance of the Button class is created.
In your case, you're basically binding the setCounter function with a fixed value of 1 right in the constructor and from that point on, you ignore the function values passed in the onClick prop.
To fix this, all you need to do is replace the following line in the Button render function:
onClick={this.onClick}
With this one:
onClick={this.props.onClick}
You are losing the value of onClick() between renders. On initial load it will set it based on the prop, but then the next time it renders it loses the value since you aren't loading it again.
You can just use the props directly like below and use ternary operators like I did for onClick for null checks if you want
class Button extends Component {
render() {
const StyledButton =
this.props.color === "secondary"
? StyledButtonSecondary
: StyledButtonMain;
return (
<StyledButton
type={this.props.type}
href={this.props.href}
target={this.props.target}
onClick={this.props.onClick ? this.props.onClick : () => {}}
variant="contained"
style={{ whiteSpace: "nowrap" }}
>
{this.props.children}
</StyledButton>
);
}
}

Categories