Function for changing numbers with React doesn't work properly - javascript

I'm trying to change number stored in a variable by clicking a button but the first time I click the button, it doesn't change the value of the variable but the second one does. I change the number in increments of 1, so when I click the button its currentNumber += 1 and I run a console.log after it to see if it changes. The first time I click it, it prints the default value, and the second time that I click it is when it actually changes, and it's messing up the intended functionality of my code. I'm using React for this.
constructor(props) {
super(props);
this.state = {
currentSize: parseInt(this.props.size),
min: 4,
max: 40,
}
};
increaseSize(){
this.setState({currentSize: this.state.currentSize + 1}, function(){
if(this.state.currentSize >= this.state.max){
this.state.currentSize = this.state.max;
this.state.isRed = true;
} else if(this.state.currentSize < this.state.max){
this.state.isRed = false;
}
});
console.log(this.state.currentSize);
};
render() {
var isBold = this.state.bold ? 'normal' : 'bold';
var currentSize = this.state.currentSize;
var textColor = this.state.isRed ? 'red' : 'black';
return(
<div>
<button id="decreaseButton" hidden='true' onClick={this.decreaseSize.bind(this)}>-</button>
<span id="fontSizeSpan" hidden='true' style={{color: textColor}}>{currentSize}</span>
<button id="increaseButton" hidden='true' onClick={this.increaseSize.bind(this)}>+</button>
<span id="textSpan" style={{fontWeight: isBold, fontSize: currentSize}} onClick={this.showElements.bind(this)}>{this.props.text}</span>
</div>
);
}
The number in the variable is then displayed but the one being displayed has a different value to the one inside the variable
As you can see in the picture, the number displayed is 26 but in the variable its 25.
Additionally, you can see that I set a min and max value for the counter. When it reaches either value, it goes 1 further in the display, but not in the console. So in the display it stops at 3 and 41 but in the console it stops at 4 and 40.
What am I doing wrong?
edit: the default value is 16, and that's whats printed to the console the first time I click the button, which is why its not working properly.

Use the functional version of setState() in order to get a hold of prev state values -- because React handles state changes asynchronously, you can't guarantee their values when you set them; This is also the cause where you are using the console.log. Instead go for something like:
increaseSize(){
const aboveMax = this.state.currentSize >= this.state.max;
this.setState( prevState => ({
currentSize: aboveMax ? prevState.max : prevState.currentSize + 1,
isRed: aboveMax
})
, () => console.log(this.state.currentSize) );
};
Or, move the console statement to the render() method if you don't want to the setState() callback function.
See https://reactjs.org/docs/react-component.html#setstate
Don't forget to set isRed in your constructor as well :)

Related

Lightning Web Component setting dynamic style not working as expected

I'm currently trying to render a specific class across two lightning-badge components that is suppose to change both badges from inverse to success, but am getting this instead:
When the value on the left badge equals the value on the right (so in this case both are 3), they should both be green, otherwise they should both be grey. They should never be seperate colours.
The value on the left increases as the user saves a record and is checked on status of "Completed". For some reason only the class on the second badge is being updated with the new class that includes slds-theme_success. I may be missing something small, but just haven't been able to figure it out. Please see code below:
badgeClass = "slds-badge_inverse slds-var-m-horizontal_x-small slds-col";
get patientsCompleted() {
if(this.records) {
let completedArr = this.records.filter(value => value.fields.Status__c.value == "Completed");
if(completedArr.length === this.patientsTotal) {
this.badgeClass = "slds-badge_inverse slds-theme_success slds-var-m-horizontal_x-small slds-col";
}
return completedArr.length;
}
};
get patientsTotal(){
if(this.records) {
return this.records.length;
}
};
<span class="slds-col_bump-left">
<div class="slds-grid slds-gutters">
<lightning-badge class={badgeClass} label={patientsCompleted}></lightning-badge>
<div class="slds-col"> of </div>
<lightning-badge class={badgeClass} label={patientsTotal}></lightning-badge>
</div>
</span>
Have you tried moving badgeClass to a getter? Something like this:
get patientsCompleted() {
if(this.records) {
let completedArr = this.records.filter(value => value.fields.Status__c.value == "Completed");
// No longer needed
// if(completedArr.length === this.patientsTotal) {
// this.badgeClass = "slds-badge_inverse slds-theme_success slds-var-m-horizontal_x-small slds-col";
// }
return completedArr.length;
}
};
get patientsTotal(){
if(this.records) {
return this.records.length;
}
};
get badgeClass() {
let baseClass = "slds-badge_inverse slds-var-m-horizontal_x-small slds-col";
return this.patientsCompleted === this.patientsTotal ? `${baseClass} slds-theme_success` : `${baseClass}`
}
I suspect LWC field tracking has some precautionary mechanism and didn't trigger the update.
I am not sure but perhaps if 0 records are available you want the badges to remain gray? In that case include this.patientsTotal > 0 in the get badgeClass() {...}.
Happy coding.

My React function gets called twice, with the second call setting values incorrectly

i have a function that calculates pages and page buttons based on an api. Inside the buttons get rendered and they have an onClick function. When i click the button, this is supposed to happen:
sets the current page number and writes it into state
calls the api which gets text elements to display according to current page
evaluates page buttons and numbers based on api and marks the current page with a css class
event handler:
handleClick(event) {
let currentPage = Number(event.target.id)
localStorage.setItem("currentPage", currentPage)
this.setState ({
currentPage: currentPage
})
this.fetchApi()
}
then i'm returning the component that deals with pages:
return(
<div>
<Paging
data = {this}
currentPage = {this.state.currentPage}
state = {this.state}
lastPage = {this.state.lastPage}
handleClick = {this.handleClick}
/>
</div>
)
and the component looks like this:
function Paging(props) {
const apiPaging = props.state.apiPaging
const apiPagingSliced = apiPaging.slice(1, -1)
const renderPageNumbers = apiPagingSliced.map((links, index) => {
return <button key={index} id={links.label}
onClick={(index)=>props.handleClick(index)}
className={(links.active ? "mark-page" : "")}
>{links.label} {console.log(links.label) }
</button>
})
return (
<div id = "page-nums">
{renderPageNumbers}
</div>
)
So what happens is that Paging() function gets called twice. There is a handy value inside the api called "active" (links.active) which is a boolean, and if set to true, means that the page is the current page. i then add a class "mark-page" on to highlight that i'm currently on that page. If i {console.log(links.label)} i see that it's invoked twice, first being the correct values and second being the previously clicked values. So it works correctly only if i reload the page again.
i.e if i click page 2,it stays on page 1 and marks page 1. if i then click page 3, it marks page 2. and (afaik) Paging() gets only invoked once, at the end of my only class (Body).
I've been at it yesterday and today and have no idea anymore.
change your handleClick function to this.
handleClick(event) {
window.scrollTo({
top: 0,
behavior: 'smooth',
});
if (event.target.id >= 1) {
let currentPage = Number(event.target.id);
localStorage.setItem('currentPage', currentPage);
this.setState({
currentPage: JSON.parse(localStorage.getItem('currentPage')),
},()=>{
this.fetchApi();
});
}
}
in your fetchApi function you reference currentPage as below.
const apiQuery = JSON.parse(this.state.currentPage);
But it hasn't updated yet.
see https://reactjs.org/docs/react-component.html#setstate

Why is my state counter one value behind what it should be?

Im new to reactjs. Im trying to create a comment section for some uploaded files, and keeping a counter on the comment buttons attached to each file. However, the counter is returning strange values.
Here is the relevent code:
class ListItem extends React.Component {
constructor(props){
super(props)
this.clicked = false
this.commentButtonRef = React.createRef();
this.state = {clickCounter:0, counterMat:[]}
}
handleClick = () =>{
console.log(this.state.clickCounter)
this.clicked = true;
this.counterMat = []
this.props.onCommentButtonClick(this.props.file, this.clicked)
this.clicked = false;
//update click counter
this.setState({clickCounter:this.state.clickCounter + 1}, this.updateCounterMatrix())
}
updateCounterMatrix = ()=> {
const temp = this.state.counterMat.slice() //copy the array
temp[1] = this.state.clickCounter //execute the manipulations
this.setState({counterMat: temp},console.log(this.state.counterMat, this.state.clickCounter))
}
createCounterMat=(element)=>{
// use ref callback to pass DOM element into setState
this.setState({counterMat:[element,this.state.clickCounter]})
console.log(this.counterMat)
}
render(){
return(
<div className="item">
<i className="large file alternate icon"></i>
<div className="content">
<div className="header">{this.props.file}</div>
<button className='comment-button'
id = {this.props.file}
onClick = {this.handleClick}
key = {this.props.file}
ref = {this.createCounterMat}
clickcounter = {this.state.clickCounter}
> Comment</button>
</div>
</div>
)
}
}
Here are the issues im having:
1) As soon as this page first renders, my use of a reactRef callback function createCounterMat in the button element should console log's undefined, which is unexpected.
2) On the first click of my button, the handleClick function calls correctly. However, the console log's inside both handleClick and updateCounterMatrix both return a value of 0 for this.state.clickCounter. I expected the first to be 0, but the second console.log to be 1 by this stage.
3) On the second click, the clickCounter state seems to correctly increment by 1. However, the console.log(this.state.counterMat, this.state.clickCounter) gives a value of 0 inside this.state.counterMat, and a value of 1 in the case of simply this.state.clickCounter.
Here is a screenshot showing all of this
Can anyone help me work out what's going on?
You're calling console.log before the set state, not after. This:
this.setState(
{counterMat: temp},
console.log(this.state.counterMat, this.state.clickCounter)
)
... means "call console.log, then pass its result along with {counterMat: temp} into this.setState". You probably meant to do:
this.setState(
{counterMat: temp},
() => console.log(this.state.counterMat, this.state.clickCounter)
)

Update Redux prop/state following div onclick

I have a table - let's call it table 1. When clicking on a row in table 1 another table is being displayed, let's call this one table 2. Table 2 displays data relevant to the clicked row in table 1. Sometimes a vertical scroll needs to be displayed in table 2 and sometimes not -depends on the number of rows.Need to solve: there is an unwanted transition of the border when the scroll is not being displayed:
. The idea for the solution: "change margin-right" according to conditions which show whether the scroll exits or not.Save the result of this condition into Redux prop:
element.scrollHeight > element.clientHeight || element.scrollWidth >
element.clientWidth
The problem: Trying to update the display/non-display of the scroll into redux prop from different React events such as componentDidMount, componentWillReceiveProps,CopmponentDidUpdate (set state causes infinte loop here) and from the click event.Tried to use forceUpdate() after setting props into Redux as well.
When console.log into the console in chrome (F12), the only result which is correlated correctly to the display/non display of the scrollbar is coming from within the componentDidUpdate and it doesn't reflect in the redux prop (isoverflown function returns true, redux this.props.scrollStatus and this.state.scrollStatus are false). Also don't like the usage of document.getElementById for the div which contains the rows, because it breaks the manipulation of the dom from within the props and state,but didn't find a different solution for now.
The F12 console when display the scroll bar:
The F12 console when no scroll bar is displayed:
.
The rest of the code:
1) action:
export function setScrollStatus(scrollStatus) {
return {
type: 'SET_SCROLL_STATUS',
scrollStatus: scrollStatus
};
}
2) reducer:
export function scrollStatus(state = [], action) {
switch (action.type) {
case 'SET_SCROLL_STATUS':
return action.scrollStatus;
default:
return state;
}
}
3)Page.js (please click on the picture to see the code)
import {setScrollStatus} from '../actions/relevantfilename';
function isOverflown(element) {
return element.scrollHeight > element.clientHeight ||element.scrollWidth > element.clientWidth;
}
class SportPage extends React.Component {
constructor(props) {
super(props);
this.state = initialState(props);
this.state = {
scrolled:false,
scrollStatus:false};
componentDidUpdate() {
console.log( "1 isoverflown bfr redux-this.props.setScrollStatus inside componentDidUpdate",isOverflown(document.getElementById('content')));
//redux props
this.props.setScrollStatus( isOverflown(document.getElementById('content')));
console.log( "2 isoverflown aftr redux-this.props.setScrollStatus inside componentDidUpdate",isOverflown(document.getElementById('content')));
//redux props
this.props.scrollStatus ? console.log (" 3 this.props.scrollStatus true inside componentDidUpdate") : console.log("this.props.scrollStatus false inside componentDidUpdate");
console.log ("4 state scrollstatus inside componentDidUpdate" , this.state.scrollStatus)
}
componentDidMount(){
console.log( "3 isoverflown bfr set",isOverflown(document.getElementById('content')));
this.props.setScrollStatus("set inside didMount", isOverflown(document.getElementById('content')));
console.log( "4 isoverflown aftr set didMount",isOverflown(document.getElementById('content')));
this.props.scrollStatus ? console.log ("scrollStatus true") : console.log("scrollStatus false");
console.log ("state scrollstatus inside didMount" , this.state.scrollStatus)
}
render() {
<div style={{overflowY:'scroll',overflowX:'hidden',height:'50vh',border:'none'}}>
{
this.props.rowData.map((row,index )=>
<div style={{ display: 'flex',flexWrap: 'wrap', border:'1px solid black'}}
onClick={ e => { this.setState({ selected: index, detailsDivVisible: true,scrolled:isOverflown(document.getElementById('content')),
scrollStatus:isOverflown(document.getElementById('content')) },
this.props.setScrollStatus( isOverflown(document.getElementById('content'))),this.forceUpdate(),console.log ("onclick this.state.scrollStatus", this.state.scrollStatus),
console.log ("onclick pure funtion", isOverflown(document.getElementById('content'))));
const mapDispatchToProps = (dispatch) => {
return {
setScrollStatus: function (scrollStaus) {dispatch (setScrollStatus(scrollStaus))},
};
};
export default connect(mapStateToProps, mapDispatchToProps)(Page);
Thank you for your reply. However,solved it in different way which does not involve the life cycle/events:
1) Calculate the height of the scroll by- multiple the height of single row by number of items to be displayed (arr.length, the arr comes from JSON)
2) setting the max height of the scroll to a needed value
3) setting the max height of the content to be the calculated height:
The result is a scroll that displays all the time with the correct height. This solved the indentation problem.
<div style={{overflowY:'auto', marginRight: '18px',zIndex:'1000',borderBottom:'1px solid black',borderRight:'1px solid black', height: this.props.rowData[this.state.selected].rowItemsList.length * singleRowHeight + 'px', maxHeight:'100px' }}>
<div style={{ width:'inherit', maxHeight:this.props.this.props.rowData[this.state.selected]‌​.rowItemsList.length * singleRowHeight + 'px' }}>
Lets simplify this. All you need is to dispatch reducer each time some one clicks inside a div.Please find the code snippet useful please go through the comments.
//import store from "./store/directory" - update this to ur store
let DOMObject = document.getElementById("id1"); //dom reference i did it based on ID its up to u to refer how u like it
//call back happens each time onclick event is triggered
DOMObject.onclick = ()=> {
/* store.dispatch(
{
type:"reducer to invoke",
data:"the data to update on click"
}
);
*/
//uncomment above and update to your requirement
console.log("clicked - Please update the dispatch event to you requirement");
}
#id1 {
padding :100px 150px 100px 80px;
background-color: lightblue;
}
<div id="id1">
DIV AREA - clcik
</div>

Update value of input type time (rerender) and focus on element again with React

In the spec for my app it says (developerified translation): When tabbing to a time element, it should update with the current time before you can change it.
So I have:
<input type="time" ref="myTimeEl" onFocus={this.handleTimeFocus.bind(null, 'myTimeEl')} name="myTimeEl" value={this.model.myTimeEl} id="myTimeEl" onChange={this.changes} />
Also relevant
changes(evt) {
let ch = {};
ch[evt.target.name] = evt.target.value;
this.model.set(ch);
},
handleTimeFocus(elName, event)
{
if (this.model[elName].length === 0) {
let set = {};
set[elName] = moment().format('HH:mm');
this.model.set(set);
}
},
The component will update when the model changes. This works well, except that the input loses focus when tabbing to it (because it gets rerendered).
Please note, if I would use an input type="text" this works out of the box. However I MUST use type="time".
So far I have tried a number of tricks trying to focus back on the element after the re-render but nothing seems to work.
I'm on react 0.14.6
Please help.
For this to work, you would need to:
Add a focusedElement parameter to the components state
In getInitialState(): set this parameter to null
In handleTimeFocus(): set focusElement to 'timeElem` or similar
Add a componentDidUpdate() lifecycle method, where you check if state has focusedElement set, and if so, focus the element - by applying a standard javascript focus() command.
That way, whenever your component updates (this is not needed in initial render), react checks if the element needs focus (by checking state), and if so, gives the element focus.
A solution for savages, but I would rather not
handleTimeFocus(elName, event)
{
if (this.model[elName].length === 0) {
let set = {};
set[elName] = moment().format('HH:mm');
this.model.set(set);
this.forceUpdate(function(){
event.target.select();
});
}
},
try using autoFocus attrribute.
follow the first 3 steps mention by wintvelt.
then in render function check if the element was focused, based on that set the autoFocus attribute to true or false.
example:
render(){
var isTimeFocused = this.state.focusedElement === 'timeElem' ? true : false;
return(
<input type="time" ref="myTimeEl" onFocus={this.handleTimeFocus.bind(null, 'myTimeEl')} name="myTimeEl" value={this.model.myTimeEl} id="myTimeEl" onChange={this.changes} autoFocus={isTimeFocused} />
);
}

Categories