how to setstate on button click event in a table - reactjs

I have master-detail table in react. If I select (or click a button) on a row in the master table, I want to set a state variable with the id of the selected row, which will be used to render the details table for that ID.
However, react does not allow me to use setState in the event function.
constructor(props){
super(props);
this.state = {
customer: {},
customerRequests: [],
interactions: [],
order: [],
error: '',
selectedCustomerRequest: ''
}
this.onSelectCustomerRequest = this.onSelectCustomerRequest.bind(this);
}
The event function is
onSelectCustomerRequest(id){
console.log(id)
// this.setState({selectedCustomerRequest: id});
}
The HTML is
<tr key={item._id} onClick={this.onSelectCustomerRequest(item._id)}>
<td><button onClick={this.onSelectCustomerRequest(item._id)} /></td>
<td>{moment(item.orderDate).format('DD/MM/YYYY hh:mm')}</td>
<td>{item.genericName}</td>
<td>{item.quantity}</td>
<td>{item.priority}</td>
<td>{interaction.orderStatus}</td>
<td>{moment(interaction.created).format('DD/MM/YYYY hh:mm')}</td>
<td>{interaction.createdBy}</td>
</tr>
if I enable the setState in onSelectCustomerRequest(id){} I get error. secondly I see that the onSelectCustomerRequest() gets executed for every row as I can see it console.log on my terminal.
I want to setState only when I click the button only.
I am sure I am doing a silly mistake somewhere, can you help? Please.

the problem here is that you call the function immediately by add parentheses () in the end of method.
To make it work, I would recommend create a closure within a method, so your case it would look like
onSelectCustomerRequest(id) {
return () => {
console.log(id)
}
}
So you basically by calling onSelectCustomerRequest in onClick return a function, which you can fire late.

Related

Cannot add to list of items in ReactJS

Its been a while since I used ReactJS and I need to create a list of items that I can add to and remove. I've added an onClick event to my li to remove it. I also have a button to add new items, these seem to work but the state is not updating.
var new_items = [...Array(1)].map((val, i) => `No Items`);
<ul className="App-list">
{new_items.map((item, i) => (<li key={`item_${i}`} onItemClick={onItemClick(i)}>{ item }</li>))}
</ul>
the onClick function is here
function onItemClick(num) {
this.setState({
new_items: this.state.new_items.concat('new value')
})
}
I just need to either delete a line from the List or Add depending on status but even though it runs it does not update the state. Can someone give me either a batter way of updating a list of rows dynamically or tell me what I'm doing wrong.
You need to add a constructor as follows:
class MyClassName {
constructor(props) {
super(props);
this.state = {
new_items: [] // or null or any other initial value depending on your use case
}
this.onItemClick = this.onItemClick.bind(this);
}
function onItemClick(num){ ... }
}
Then while calling the onClick function you call it as follows:
onItemClick={this.onItemClick(i)}
Also, if you are using the generic onClick functionality, you would have to change onItemClick to onClick:
<li key={`item_${i}`} onClick={()=>this.onItemClick(i)}>
Since you are not using the event information from the click, you have to add an anonymous function that calls your desired onClick handler. Hence the ()=>this.onItemClick(i)
Since you are using Class Component you have to call the method with the context of class
onItemClick={this.onItemClick(i)}

Updating a value that's within an array within an array in a state

I'm trying to update the state of the id value in shoeList. Currently I have a textfield that allows for entering of a new ID and I want the state to update when the OK button is clicked.
Here is some of the relevant code:
state = {
editingToggle: false,
shoeList : [
{name: 'bob', id: '123213-0', shoeSize: 'L'}
],
}
<TextInput className='text-area-header' id="textM" width="m" type="text" placeholder={this.state.shoeList[0].id} />
<Button className="ok-button" variant="tertiary" size ='xs' type="button" handleClick={this.saveHeader}>OK</Button>
saveHeader(e) {
this.setState(state=> ({
shoeList[0].name:
}))
alert('Header changed to ' + this.state.shoeList[0].id);
e.preventDefault();
}
I'm not sure what to put in this.setState as I haven't found anything on how to update nested values through a google search. Also whenever I put a value attribute to the TextInput tags it doesn't allow me to edit in the textinput on the webpage anymore. Any help would be great
Consider this example:
saveHeader() {
this.state.shoeList[0].id = 'newid'
this.setState({ shoeList: this.state.shoeList })
}
setState() checks if the values have changed, and if not, it does not update the component. Changing a nested value does not change the reference to the array, which means that simply calling setState() is not enough.
There are two ways around this. The first is to use forceUpdate():
saveHeader() {
this.state.shoeList[0].id = 'newid'
this.forceUpdate()
}
This forces the component to re-render, even though the state didn't change.
The second way is to actually change the shoeList array by creating a new one:
saveHeader() {
let newShoeList = this.state.shoeList.slice()
newShoeList[0].id = 'newid'
this.setState({ shoeList: newShoeList })
}
Using .slice() on an array creates a new array that is completely identical. But because it is a new array, React will notice that the state changed and re-render the component.

setState not .slicing() into state's array the way I'm intending

Background:
I'm developing an itinerary builder which is made up of rows, or component instances named EventContainers, that represent an activity on a user's given day.
The class, shown below, has an array in state.events that accepts EventContainers from the setState in the pushNewEventContainerToState function.
Of note, each EventContainer contains a button that is intended to give the user the ability to onClick an additional row/EventContainer by calling pushNewEventContainerToState.
The same button is also listed as its own component instance, named NewEventButton, and is displayed before any EventContainers.
Any EventContainer that is setStated to state.events is supposed to be placed in the index immediately after the EventContainer that calls setState, not at the beginning or end.
Method
I'm using .slice() in setState with the intention of doing just that => placing the newest EventContainer in the index immediately following the EventContainer that called setstate.
Problem
However, there are three issues I see:
a) Only the very first button, NewEventButton, will actually call setState. The buttons on the new EventContainer's won't do anything.
b) The EventContainers that pass through setState seem to be .pushed() to state.events, not .sliced(), but I don't need them at the end of the array.
c) When I check on Chrome devtools, I see that any EventContainer that's setStated to state.events is undefined.
What I've tried
I've tried placing two different kinds of props directly into the EventContainer that's inside pushNewEventContainerToState:
1. The first prop I tried didn't do anything -> onClick={() => this.pushNewEventContainerToState(index)
2. The second prop I tried was the same as the first except, instead of onClick, I named it pushNewEventContainerToState. This got the button on new EventContainers working but the EventContainers seemed to again be .pushed() instead of .sliced the way I need them to be. Chrome devtools will then show this is defined but I don't see a bound like I do for the first NewEventButton.
Thank you very much for taking a look.
class DayContainer extends React.Component {
constructor(props){
super(props);
this.state = {
events: [],
};
this.pushNewEventContainerToState = this.pushNewEventContainerToState.bind(this);
}
pushNewEventContainerToState(index) {
let newEvent = < EventContainer / > ;
this.setState(prevState => {
const updatedEvents = [...prevState.events.slice(0, index), newEvent, ...prevState.events.slice(index + 1)];
return {
events: updatedEvents
};
})
}
render(){
return (
<>
<div>
<ul>
{
this.state.events === null
? <EventContainer pushNewEventContainerToState={this.pushNewEventContainerToState} />
: <NewEventButton pushNewEventContainerToState={this.pushNewEventContainerToState} />
}
{this.state.events.map((item, index) => (
<li
key={item}
onClick={() => this.pushNewEventContainerToState(index)}
>{item}</li>
))}
</ul>
</div>
</>
)
}
}
Try fixing this part of your function first, looks like it doesn't behave as expected:
[...prevState.events.slice(0, index), newEvent, ...prevState.events.slice(index + 1)]
change to:
[...prevState.events.slice(0, index), newEvent, ...prevState.events.slice(index)]
.slice()'s second argument tells it to stop there, but doesn't include the index of the element. So if you have [1, 2, 3].slice(0,1) in example, you'll get only the first element -> [1].
Hope that fixes the issue.

ReactJS - Scroll to a table's row as the page loads

I've got a page with a table generated by a class component where, depending on some logic, I have only one of its rows "active" in a certain moment. I'm trying to scroll to the "active" row as the user opens the page.
I've already read this discussion but it didn't help
ReactJS how to scroll to an element
Doing like this I get that this.scrollHere.current is null
constructor(props) {
super(props);
this.scrollHere = React.createRef();
}
componentDidMount() {
// not using 'current', scrollIntoView() results not to be a function
this.scrollHere.current.scrollIntoView({behavior: 'smooth', block: 'start'})
}
render() {
return(
...
<tr ref={this.scrollHere}><td>...</td></tr>
...
)
}
Is that because the reference, when componentDidMount() is called, doesn't yet exist? If so, how to make it work?
We can do something like this (use scrollIntoView), this issue with your code is that you are not using callback refs-:
it should be defined like this -:
<tr ref={e => this.scrollHere = e}><td>....</td></tr>
this.scrollHere.scrollIntoView()
Here is a working demo https://stackblitz.com/edit/react-yzszzv
also replace
this.scrollHere.current.scrollIntoView({behavior: 'smooth', block: 'start'})
with this.scrollHere.scrollIntoView({behavior: 'smooth', block: 'start'})

Can I force a rerender with setState even if the data hasn't changed? My GUI won't update?

So the problem I'm having right now is on first click, my GUI re-renders. But on second click, it does not re-render. I believe it's because I am not updating the state of "graphicLayers" which my render is binding through the "graphicLayers.map". That's my theory anyway (even though it works on first click? but not the 2nd click or anything after).
I tried forcing an setState update of graphicLayers, but it doesn't seem to be working. Like this:
let graphicLayersCopy = Object.assign([], this.state.graphicLayers);
this.setState({graphicLayers: graphicLayersCopy});
but that's not working. I know through the debugger that it's setting the data correctly, and if I refresh (it saves state and reloads the state), the GUI is then rendered correctly.
Is there anyway I can force a re-render of a variable some how even if it doesn't change value?
constructor
constructor(props, context) {
super(props, context);
this.state = {
graphicLayers: [id1, id2, id3],
graphicLayersById: {
id1: { ... },
id2: { ... },
id3: { ... }
}
this.addLayerClick = this.addLayerClick.bind(this);
};
render
render() {
return (
<div>
{this.state.graphicLayers.map((id) =>
<GraphicLayer addLayerClick={this.addLayerClick.bind(this)} />
)}
</div>
);
}
addLayerClick
addLayerClick() {
... change some property in graphicLayersById dictionary ...
self.setState({ graphicLayersById: newDataHere });
}
EDIT: I found the problem on my end, and it's not exactly shown here.
So my addLayerClick() actually calls another functions that is listening to a call and it sets the state inside. it's weird because the setState gets called in the callback function, but i got it to work by putting the setState in the addLayerClick() itself.. still dont know why this doens't work but i will upvote all of you at least
listenFunction() {
let self = this;
this.firebaseWrapper.storage.on('graphicLayersById', function (save) {
if (save) {
self.setState({ graphicLayersById: save }); // FOR SOME REASON THIS DOESN'T UPDATE THE GUI THE 2nd CLICK. The data is correct though and I see it going here on a breakpoint, but GUI won't update unless I setState in the actual button
}
else {
self.setState({ graphicLayersById: undefined });
}
});
}
In addLayerClick() you're only updating graphicLayersById, but rendering depends on graphicLayers. You should be updating the graphicLayers state in addLayerClick() as well.
addLayerClick() {
this.setState({
graphicLayers: ...
graphicLayersById: ....
});
}
On a side note, you shouldn't bind methods inside render() since that creates a brand new function on every render (and could impact performance). Instead of
<GraphicLayer addLayerClick={this.addLayerClick.bind(this)} />
do
<GraphicLayer addLayerClick={this.addLayerClick} />
and leave the binding in your constructor (the way you already have).
Actually, you have bound the addLayerClick() function to the component, so you can use this instead of self
You should revise your code like this: (there are about 2 changes)
constructor(props, context) {
super(props, context);
this.state = {
graphicLayers: [id1, id2, id3],
graphicLayersById: {
id1: { ... },
id2: { ... },
id3: { ... }
}
// don't need to bind here anymore, since you bind it in the click
//this.addLayerClick = this.addLayerClick.bind(this);
};
addLayerClick() {
//... change some property in graphicLayersById dictionary ...
// just use 'this' here
this.setState({ graphicLayersById: newDataHere });
// the below line is NOT recommended, which is force the component to update
// this.forceUpdate(); // this line will update the component even though there's no change
}
If this doesn't work yet, please post here how you handle onCLick function in the child component, and also post some errors if any, thanks
Hope these two possible way will reder your view
this.setState({graphicLayersById: newDataHere} , ()=>{
console.log(this.state.graphicLayersById);
});
OR
var update = require('react-addons-update');
var graphicLayers = update(this.state, {
graphicLayersById: {$set: newDataHere}
});
this.setState(graphicLayers);

Resources