How to update state individually? - reactjs

I have this code below, which when I click the button, it will render the current time
But when I click again to update the new time, it will update all three at the same time. I want to update only the button I clicked
I'm guessing the problem is all three using the same state.
constructor(props) {
super(props);
this.state = {
button1: false,
button2: false,
button3: false,
};
}
onClickHandler = (type) => {
this.setState({[type]: true})
}
render() {
return (
<div>
{this.state.button1 && <div>{ this.state.curTime }</div>}
{this.state.button2 && <div>{ this.state.curTime }</div>}
{this.state.button3 && <div>{ this.state.curTime }</div>}
<div>
<Button
onClick={() => { this.getTimePress(); this.onClickHandler("button1") }}
>Button 1
</Button>
<Button
onClick={() => { this.getTimePress(); this.onClickHandler("button2") }}
>Button 2
</Button>
<Button
onClick={() => { this.getTimePress(); this.onClickHandler("button3") }}
>Button 3
</Button>
</div>
</div>
)
}
}
And I was thinking of using map() might solve the problem, but I'm having a hard time figuring out how to modify boolean for each button.
this.state = {
buttonList: [
{ id: 'button1', title: "Button 1", pressed: false },
{ id: 'button2', title: "Button 2", pressed: false },
{ id: 'button3', title: "Button 3", pressed: false }
],
};
}

this.state = {
buttons: [
{ id: 'button1', title: "Button 1", pressed: false, timePress : null },
{ id: 'button2', title: "Button 2", pressed: false, timePress : null },
{ id: 'button3', title: "Button 3", pressed: false, timePress : null }
],
};
handlePressButton (button,index) {
this.state.buttons[index].pressed = true
this.state.buttons[index].timePress= Date.now() //or new Date()
this.setState({
buttons : [...this.state.buttons ]
})
}
render () {
return (
<>
this.state.buttons.map(button => <div> Pressed at : {button.timePress || "not yet"} </div> )
this.state.buttons.map((button , index) => <Button onClick={() =>this.handlePressButton(button,index)} > {button.title} </Button> )
</>
)
}

Related

Array object not updating in react?

I have an application where the user will select a seat and then will click reserve and the seats will be greyed out. For some reason my object array of seats are not updating in the array and the seats are not greying out. when I log the seating sometimes, the isReserved is true, and when I log it again it goes back to false.
Here is what the code looks like:
const seats: any[] = [
{ id: 1, isSelected: false, isReserved: false },
{ id: 2, isSelected: false, isReserved: false },
{ id: 3, isSelected: false, isReserved: false },
{ id: 4, isSelected: false, isReserved: true },
{ id: 5, isSelected: false, isReserved: false },
{ id: 6, isSelected: false, isReserved: false },
];
const Seatbooking = () => {
const [isSelected, setIsSelected] = useState(0);
const [seating, setSeating] = useState(seats);
function onSelected(select: any) {
console.log(select.id);
console.log("selected ", select);
setIsSelected(select.id);
console.log("it is selected ", select.id);
}
const onReserved = (id: any) => {
setSeating((seat) => {
return seat.map((item) => {
return item.id === id
? { ...item, isReserved: !item.isReserved }
: item;
});
});
};
return (
<>
<div className="grid-container">
{seats.map((seat) => (
<div style={{ width: "50%" }}>
<button
key={seat.id}
style={{
backgroundColor:
seat.isReserved === true
? "grey"
: seat.id === isSelected
? "red"
: "#2d95c9",
}}
className="seat_buttons"
onClick={() => onSelected(seat)}
>
{seat.id}
</button>
</div>
))}
</div>
<button className="seat_booking" onClick={() => onReserved(isSelected)}>
Reserve seat
</button>
</>
);
};
Working solution with fixed naming and optimised conditions.
import { useState } from 'react';
const seats = [
{ id: 1, isSelected: false, isReserved: false },
{ id: 2, isSelected: false, isReserved: false },
{ id: 3, isSelected: false, isReserved: false },
{ id: 4, isSelected: false, isReserved: true },
{ id: 5, isSelected: false, isReserved: false },
{ id: 6, isSelected: false, isReserved: false },
];
export const Seatbooking = () => {
const [selectedSeatId, setSelectedSeatId] = useState(0);
const [seating, setSeating] = useState(seats);
function onSelected(select) {
console.log(select.id);
console.log('selected ', select);
setSelectedSeatId(select.id);
console.log('it is selected ', select.id);
}
const onReserved = (id) => {
const updatedArr = seating.map((item) => {
return item.id === id ? { ...item, isReserved: !item.isReserved } : item;
});
setSeating(updatedArr);
};
return (
<>
<div className="grid-container">
{seating.map((seat) => (
<div key={seat.id} style={{ width: '50%', display: 'flex' }}>
<button
style={{
backgroundColor: seat.isReserved
? 'grey'
: seat.id === selectedSeatId
? 'red'
: '#2d95c9',
}}
className="seat_buttons"
onClick={() => onSelected(seat)}
>
{seat.id}
</button>
</div>
))}
</div>
<button className="seat_booking" onClick={() => onReserved(selectedSeatId)}>
Reserve seat
</button>
</>
);
};
You should work with state in your component, not with constant.
You do not need callback in your setSeating.
isSelected - name for boolean value. You store id - call it selectedSeatId.
Key should be on div, not on button.
seats.map((seat) => (
You're mapping over the original array, not your state variable. Change it to:
seating.map((seat) => (

React.js - changing text in a list

I am changing the text colour in a list when the item is clicked. Ideally, I would like to have a toggle function so only 1 item is highlighted at a time. The following code works however I can't stop thinking there is a much better way.
import React, { useState } from "react";
const listItems = [
{
id: 1,
title: "About",
selected: true,
},
{
id: 2,
title: "Contact",
selected: false,
},
{
id: 3,
title: "Products",
selected: false,
},
];
const ListView = () => {
const [menuItems, setMenuItems] = useState(listItems);
const handleListClick = (id) => {
setMenuItems([...menuItems.map((item)=> {
if (item.id === id){
item.selected=!item.selected
}
return item
})])
};
return (
<>
<ul>
{listItems.map((item, index) => {
return (
<li
key={item.id}
onClick={() => handleListClick(item.id)}
style={item.selected ? { color: "red" } : { color: "Blue" }}
>
{item.title}
</li>
);
})}
</ul>
</>
);
};
export default ListView;
Any ideas on simplifying this...
You can use the array index to update the item only without traversing the array like this
import React, { useState } from "react";
const listItems = [
{
id: 1,
title: "About",
selected: true,
},
{
id: 2,
title: "Contact",
selected: false,
},
{
id: 3,
title: "Products",
selected: false,
},
];
const ListView = () => {
const [menuItems, setMenuItems] = useState(listItems);
const handleListClick = (index) => {
const items = [...menuItems];
items[index].selected = !items[index].selected;
setMenuItems(items);
};
return (
<>
<ul>
{listItems.map((item, index) => {
return (
<li
key={item.id}
onClick={() => handleListClick(index)}
style={item.selected ? { color: "red" } : { color: "Blue" }}
>
{item.title}
</li>
);
})}
</ul>
</>
);
};
export default ListView;

Ant Design for React : Show/Hide particular column

I need a bit of help here, In an Ant Design table, I need to hide/show a particular column of a table depending on a state value. In the given sandbox link, I need to hide the surname column whenever the switch is Off and show when the switch is On.
Please, someone, look into this, and help me out.
Reference: https://codesandbox.io/s/purple-sun-1rtz1?file=/index.js
There is a working code, but it should be more customize, interactivize, and refactorize depending on your need:
// You can also modify the data in the `handleChnage`
// Or conditionally display it like here:
class EditableTable extends React.Component {
state = {
surNameShow: false
};
constructor(props) {
super(props);
this.columns = [
{
title: "Name",
dataIndex: "name",
width: "30%"
},
{
title: "Surname",
dataIndex: "surname",
width: "30%"
}
];
this.state = {
dataSource: [
{
key: "0",
name: "Edward 1",
surname: "King 1"
},
{
key: "1",
name: "Edward 2",
surname: "King 2"
}
]
};
}
handleChnage = key => {
this.setState({ surNameShow: !this.state.surNameShow }, () => {
console.log(this.state.surNameShow);
});
};
render() {
const { dataSource } = this.state;
const columns = this.columns;
return (
<div>
<p className="mr-3"> Show surname</p>
<Switch onChange={() => this.handleChnage()} />
<Table
bordered
dataSource={dataSource}
columns={
this.state.surNameShow
? columns
: columns.filter(ea => ea.dataIndex !== "surname")
}
pagination={false}
/>
</div>
);
}
}
ReactDOM.render(<EditableTable />, document.getElementById("container"));

Closing a modal popping up through a react table

I am popping up a modal an edit data through a button-click in the react table.
After performing the function when I try to close the modal, it would navigate to an empty page. I am calling the modal in the parent component from a child component as folllows.
<EditCardModal
show={this.state.show}
onHide={this.closeModal}
title="EDIT CARD"
id={this.state.id}
name={this.state.name}
uid={this.state.uid}
status={this.state.status}
serial={this.state.serial}
nodeType={this.state.nodeType}
onChange={this.handleChange}
handleEdit={this.handleEdit}
msg={this.state.msg}
/>
In the child component a close button is added to the modal header as below.
<Modal.Header closeButton>
<Modal.Title>{this.props.title}</Modal.Title>
</Modal.Header>
The method below shows how I close the modal.
closeModal = async event => {
event.preventDefault();
await this.setState({
show: false
});
};
TableList.js
class TableList extends Component {
constructor(props) {
super(props);
this.state = { show: false };
this.nodeCreationData = {};
this.state = {
show: false,
excelData: null,
rowInfo: null,
name: "",
uid: "",
order: "",
serial: "",
status: "",
excelDataValidation: false,
nodeType: "",
isSuccess: false,
a: null,
msg: "",
title: ""
};
this.showModal = this.showModal.bind(this);
this.closeModal = this.closeModal.bind(this);
}
showModal() {
// event.preventDefault();
this.setState({
show: true
});
}
closeModal() {
// event.preventDefault();
debugger;
this.setState({
show: false
});
}
componentWillMount() {
this.props.queryStart();
this.props.getCard();
}
getTdProps = (state, rowInfo, column, instance) => {
return {
onClick: async (e, handleOriginal) => {
console.log("It was in this row:", rowInfo.original.name);
await this.setState({
id: rowInfo.original.id,
name: rowInfo.original.name,
uid: rowInfo.original.uid,
serial: rowInfo.original.serial,
status: rowInfo.original.status,
nodeType: rowInfo.original.nodeType
});
if (handleOriginal) {
handleOriginal();
}
console.log("State", this.state);
}
};
};
tableHeaders = [
{
Header: "ID",
accessor: "id"
},
{
Header: "NAME",
accessor: "name"
},
{
Header: "UID",
accessor: "uid"
},
{
Header: "STATUS",
accessor: "status"
},
{
Header: "SERIAL",
accessor: "serial"
},
{
Header: "UPDATED_AT",
accessor: "updatedAt"
},
{
Header: "NODE TYPE",
accessor: "nodeType"
},
{
Header: "ACTION",
accessor: "action",
minWidth: 150,
Cell: ({ row }) => (
<div>
<Button type="submit" onClick={this.showModal}>
UPDATE CARD
</Button>
</div>
)
}
];
handleEdit = () => {
this.props.editCard({
id: this.state.id,
createdAt: this.state.createdAt,
name: this.state.name,
uid: this.state.uid,
serial: this.state.serial,
name: this.state.name,
status: this.state.status,
nodeType: this.state.nodeType
});
};
handleChange = event => {
if (event.target.name == "id") {
this.setState({
id: event.target.value
});
}
if (event.target.name == "nodeType") {
this.setState({
nodeType: event.target.value
});
}
if (event.target.name == "name") {
this.setState({
name: event.target.value
});
}
if (event.target.name == "serial") {
this.setState({
serail: event.target.value
});
}
if (event.target.name == "uid") {
this.setState({
uid: event.target.value
});
}
if (event.target.name == "status") {
this.setState({
status: event.target.value
});
}
};
render() {
const { TableListData, TableListLoading } = this.props;
var nodeTableList = null;
if (TableListData == null) {
nodeTableList = "No data";
} else {
nodeTableList = (
<ReactTable
// ref={r => {
// this.reactTable = r;
// }}
data={TableListData[0]}
columns={this.tableHeaders}
getTdProps={this.getTdProps}
/>
);
}
let loadingMsg = TableListLoading && <Alert>Loading</Alert>; //Show loading message
return (
<Grid fluid>
<EditCardModal
show={this.state.show}
onHide={this.closeModal}
title="EDIT CARD"
id={this.state.id}
name={this.state.name}
uid={this.state.uid}
status={this.state.status}
serial={this.state.serial}
nodeType={this.state.nodeType}
onChange={this.handleChange}
handleEdit={this.handleEdit}
msg={this.state.msg}
/>
<Row>
<Col xs={12} md={12}>
{loadingMsg}
{nodeTableList}
</Col>
</Row>
</Grid>
);
}
}
function mapStateToProps(state) {
return {
TableListData: state.cardUploader.data
};
}
export default withRouter(
connect(
mapStateToProps,
cardUploaderActions
)(TableList)
);
Can someone help me to solving this matter?

ComponentDidUpdate causing infinite render even when wrapped in condition

I'm trying to pull some data from Google Analytics reporting API and display the data inside the apex charts library. I managed to successfully do this. However, I now want filtering options to where if the user selects a certain date within the date range picker react wrapper, the apex charts data gets updated from the API.
I'm struggling figuring out on how when my data gets updated, to update the state with the new state within my life cycle method? I think I'm doing something minor I just don't know what it is. I looked up the documentation on the life cycle method and it says to make sure to wrap it inside a condition which I did. However, when the else condition is met, it causes an infinite render.
Here is my code: (the bug i'm stuck on is the componentWillUpdate lifecycle method) everything else works fine.
import React from "react";
import Header from "../common/Header";
import Footer from "../common/Footer";
import moment from "moment";
import $ from "jquery";
import ApexCharts from "apexcharts";
import Chart from "react-apexcharts";
import DateRangePicker from "react-bootstrap-daterangepicker";
const VIEW_ID = "";
class Charts extends React.Component {
constructor(props) {
super(props);
this.printResults = this.printResults.bind(this);
this.pageViews = this.pageViews.bind(this);
this.handleError = this.handleError.bind(this);
this.state = {
loading: true,
filterstartDate: "",
filterendDate: "",
// Start Series Bar State
ChartOne: {
chart: {
id: "ChartOne"
},
colors: ["#e31d1a"],
xaxis: {
categories: [],
labels: {
style: {
colors: []
}
},
title: {
text: "Locations"
}
},
yaxis: {
labels: {
style: {
colors: []
}
},
title: {
text: "Count"
}
}
},
ChartOneSeries: [],
}
pageViews = async () => {
window.gapi.client
.request({
path: "/v4/reports:batchGet",
root: "https://analyticsreporting.googleapis.com",
method: "POST",
body: {
reportRequests: [
{
viewId: VIEW_ID,
dateRanges: [
{
startDate: "7daysAgo",
endDate: "today"
}
],
metrics: [
{
expression: "ga:pageviews"
}
],
dimensions: [
{
name: "ga:country"
}
],
orderBys: [{ fieldName: "ga:pageviews", sortOrder: "DESCENDING" }]
}
]
}
})
.then(this.printResults, this.handleError);
};
componentDidMount() {
$.getScript("https://apis.google.com/js/client:platform.js").done(() => {
window.gapi.signin2.render("my-signin2", {
scope: "profile email",
width: 240,
height: 50,
longtitle: true,
theme: "dark",
onsuccess: this.pageViews,
onfailure: this.handleError
});
});
}
//log the data
printResults(response) {
let pageviewLocation = [];
let pageviewCount = [];
let pageviewTotal = response.result.reports[0].data.totals[0].values[0];
let totalComma = pageviewTotal
.toString()
.replace(/\B(?=(\d{3})+(?!\d))/g, ",");
response.result.reports[0].data.rows.map(value => {
//console.log(value.dimensions);
pageviewLocation.push(value.dimensions[0]);
pageviewCount.push(parseInt(value.metrics[0].values[0]));
});
//console.log(total);
this.setState({
loading: false,
ChartOne: {
title: {
text: totalComma,
align: "center",
style: {
fontSize: "20px"
}
},
subtitle: {
text: "Total Page Views",
align: "center",
style: {
fontSize: "14px",
cssClass: "apexcharts-yaxis-title"
}
},
plotOptions: {},
...this.state.ChartOne,
xaxis: {
width: 1,
...this.state.ChartOne.xaxis,
labels: {
show: false,
...this.state.ChartOne.xaxis.labels,
style: {
...this.state.ChartOne.xaxis.labels.style
}
},
categories: pageviewLocation
},
yaxis: {
min: 0,
...this.state.ChartOne.yaxis,
labels: {
//show: false,
...this.state.ChartOne.yaxis.labels,
style: {
...this.state.ChartOne.yaxis.labels.style
}
}
}
},
ChartOneSeries: [
{
name: "Total Page Views",
data: pageviewCount
}
]
});
}
componentDidUpdate(prevProps, prevState) {
if (this.state.filterstartDate === "" && this.state.filterendDate === "") {
console.log("they are empty");
} else {
this.setState({
// this fails immediately once the condition is met
test: "success!"
});
}
}
Datepicker = async (event, picker) => {
this.setState({
filterstartDate: moment(picker.startDate._d).format("YYYY-MM-DD"),
filterendDate: moment(picker.endDate._d).format("YYYY-MM-DD")
});
//console.log(this.state);
};
//or the error if there is one
handleError(reason) {
console.error(reason);
console.error(reason.result.error.message);
}
render() {
//console.log();
return (
<div className="containerfluid" id="fullWidth">
<Header />
<div className="container" id="chartContainer">
<h1>Site Analytics</h1>
<div className="row">
<div className="col-md-12">
<DateRangePicker
startDate={moment().format("MM-DD-YYYY")}
endDate={moment().format("MM-DD-YYYY")}
onApply={this.Datepicker}
>
<button className="btn btn-info">
<i className="fas fa-filter">
<span
style={{
fontFamily: "Roboto, san-serif",
fontWeight: "normal",
padding: "5px"
}}
>
Filter Date
</span>
</i>
</button>
</DateRangePicker>
</div>
</div>
<div className="row">
<div className="col-md-4">
{/* Chart One Line */}
{this.state.loading ? (
<React.Fragment>
<i className="fas fa-spinner fa-3x" id="loader" /> Please wait
...!
</React.Fragment>
) : (
<div className="chartContainer">
<Chart
options={this.state.ChartOne}
series={this.state.ChartOneSeries}
type="line"
width={400}
height={300}
/>
</div>
)}
</div>
</div>
<div id="my-signin2" />
</div>
<Footer />
</div>
);
}
}
export default Charts;
When you use setState you're triggering the lifecycle again. If you don't set your filterstartDate and filterendDate to "", you'll keep calling setState infinitely.
componentDidUpdate(prevProps, prevState) {
if (this.state.filterstartDate === "" && this.state.filterendDate === "") {
console.log("they are empty");
} else {
this.setState({
filterstartDate: "",
filterendDate: "",
test: "success!"
});
}
}

Resources