I am experimenting with js + React and I am facing an unexpected behavior:
In the following example, while it seems to work fine at first I do not get a score change when (this.state.progress.length%3==0) as expected.
The progress string seems to be updating nicely but the score updates every fourth click...
Edit: I should pin-point the source of the issue because ppl are busy, the problem is the way the handleClick() on the child component interacts (calls) the scoreUpdate() from the same component. However I do not think the solution is trivial because the consol.log() example at the end of the question works.
There is obviously an issue on the way I am organizing my code, but what?
Should I be using Promises to call my scoreUpdate() function?
Or is there a better way to go around this?
Child component:
import React from 'react';
export class Child extends React.Component {
constructor(props) {
super(props);
this.state = { progress: "0",
score: 0};
this.handleClick = this.handleClick.bind(this);
this.scoreUpdate = this.scoreUpdate.bind(this);
}
handleClick(e) {
let previous = this.state.progress;
let score = Number(e.currentTarget.id);
this.setState({progress: previous+e.currentTarget.id});
this.scoreUpdate(score);
}
scoreUpdate(score) {
if (this.state.progress.length%3==0) {
let previous = this.state.score;
this.setState({score: previous+score}); }
}
render() {
return (
<div>
<ul>
<li id="1" onClick={this.handleClick}>a</li>
<li id="2" onClick={this.handleClick}>b</li>
</ul>
<p>progress</p>
<p>{this.state.progress}</p>
<p>progress length</p>
<p>{this.state.progress.length}</p>
<p>score</p>
<p>{this.state.score}</p>
</div>
);
}
}
Parent component:
import React from 'react';
import ReactDOM from 'react-dom';
import {Child} from './components/Child';
class Parent extends React.Component {
render() {
return (
<Child />
);
}
}
ReactDOM.render(
<Parent />,
document.getElementById('app')
);
Any valid insight / explanation on why is this hapening would be highly appreciated. What puzzles me is that when I type in the console:
var b = 1;
function c() {
b=b+2;
d();
}
function d() {
console.log(b);
}
c();
This returns 3 as expected.
If you know this question to have a duplicate please leave a comment in order for me to remove it.
Try like this:
handleClick(e) {
let previous = this.state.progress;
let score = Number(e.currentTarget.id);
this.setState({progress: previous+e.currentTarget.id}, () => this.scoreUpdate(score));
}
scoreUpdate(score) {
if (this.state.progress.length%3==0) {
let previous = this.state.score;
this.setState({score: previous+score}); }
}
I've setup a JSFiddle for your component, but I still have absolutely no idea what's happening. Your state.progress appears to be a string concatenation of the event.target's id attribute: 0111 for instance.
Thus each time scoreUpdate is invoked, it adds the id (which in the JSFiddle's case is always 1) attribute to the end:
Click 1: state.progress === 0
Click 2: state.progress === 01
Click 3: state.progress === 011
Click 4: state.progress === 0111
Only on the fourth click does this.state.progress.length % 3 == 0 yield true, and therefore update state.score.
Please elucidate?
Related
I'm trying to build a react component that allows a die to be rolled and update the total of the die roll on the page.I'm only including what I perceive to be necessary as the function is pretty long but this should cover the problems I'm having
import "./Dice.css";
import img from "./img/D8.png";
class Dice extends Component {
constructor(props) {
super(props);
this.state = {
bonusRoll: 0,
penaltyRoll: 0,
totalRoll: 1
};
this.rollDie = this.rollDie.bind(this);
};
rollDie(bonus,penalty) {
let rolls = []
const reducer = (previousValue, currentValue) => previousValue + currentValue;
console.log("test1")
if (!bonus && !penalty){
console.log("test2")
for (let i = 0; i < 2; i++) {
rolls.push(Math.ceil(Math.random() * 8)) * i; // within my code this is line 41
};
this.setState({totalRoll: rolls.reduce(reducer)});
// return rolls.reduce(reducer) // this worked when the function was called in vanilla JS
}};
render() {
return (
<div className="Dice">
<div className="Dice-rolls-wrap">
<div className="Dice-base-roll">
<h5>Baseroll: {this.state.totalRoll}</h5>
</div>
</div>
<button className="Dice-btn" onClick={this.rollDie}>
<img className="Dice-img" src={img} alt="Dice icon" />
</button>
</div>
);
}
}
export default Dice;
with the above code I get the following error when trying to run/build the app
src/Dice.js
Line 41:9: Expected an assignment or function call and instead saw an expression no-unused-expressions
Search for the keywords to learn more about each error.
if I add a return statement on line 41 everything renders but when clicking the button it will not proceed past the conditional (EG: "test1" shows in console but "test2" doesn't) I'm not passing in arguments to the function so it should be responding to this first bit of logic as far as I can tell but I'm not sure where I'm going wrong. (I'm also probably using setState wrong but I can't get the code to reach there yet to say). Also please be gentle this is my first Stack Overflow question
The problem i have is that React does not update in the situation below.
I added a forceUpdate() when the component should update just to make extra sure.
The code is simple so there is not much to say.
It's as if React does not see that it should update or am i doing something really wrong here?
class Greetings extends React.Component{
constructor(props){
super(props)
this.switchLanguage = this.switchLanguage.bind(this)
this.state = {
languageID: 0,
}
this.arrayContainingRenderValues = [
<span>{this.props.greetingArray[this.state.languageID]}!</span>,
<span>No greetings for you!!</span>
]
}
switchLanguage(){
this.setState((previousState) => ({languageID: (previousState.languageID + 1) % this.props.greetingArray.length}))
this.forceUpdate()
}
componentDidMount(){
this.timerID = setInterval(this.switchLanguage, 500)
}
componentWillUnmount(){
clearInterval(this.timerID)
}
render(){
return this.arrayContainingRenderValues[0]
//The return below works without problem
return <span>{this.props.greetingArray[this.state.languageID]}!</span>
}
}
let content = <Greetings greetingArray={["Good morning","Bonjour","Buenos días","Guten tag","Bom dia","Buongiorno"]}/>
ReactDOM.render(content, document.getElementById('root'))
The state gets updated, you can see that simply by commenting out the first return.
A i got an answer, it is just that the value of the content in this.arrayContainingRenderValues[] was computed and then fixed when first assigned inside the constructor(), to have it recompute the array had to be reassigned in the render().
So in the end i may as well not use the array at all. But i just wanted to test how react works thanks for the help.
I am using React 15 on Chrome and want to hook up an event listener to detect changes to a parent container. After looking around for options, I came across ResizeObserver and am not sure how to get it to work in my project.
Currently, I am putting it in my constructor but it does not seem to print any text and I am not sure what to put in the observe call.
class MyComponent extends React.Component {
constructor(props) {
super(props);
const resizeObserver = new ResizeObserver((entries) => {
console.log("Hello World");
});
resizeObserver.observe(somethingGoesHere);
}
render() {
return (
<AnotherComponent>
<YetAnotherComponent>
</YetAnotherComponent>
<CanYouBelieveIt>
</CanYouBelieveIt>
<RealComponent />
</AnotherComponent>
);
}
}
Ideally, I also don't want to wrap RealComponent in a div and give that div an id. Is there a way to the RealComponent directly?
My goal is to observe any resize changes to the RealComponent but MyComponent is fine too. What should I put in the somethingGoesHere slot?
EDIT:
For the sake of getting something to work, I bit the bullet and wrapped a div tag around RealComponent. I then gave it an id <div id="myDivTag"> and changed the observe call:
resizeObserver.observe(document.getElementById("myDivTag"));
However, when running this, I get:
Uncaught TypeError: resizeObserver.observe is not a function
Any help would be greatly appreciated.
ComponentDidMount would be the best place to set up your observer but you also want to disconnect on ComponentWillUnmount.
class MyComponent extends React.Component {
resizeObserver = null;
resizeElement = createRef();
componentDidMount() {
this.resizeObserver = new ResizeObserver((entries) => {
// do things
});
this.resizeObserver.observe(this.resizeElement.current);
}
componentWillUnmount() {
if (this.resizeObserver) {
this.resizeObserver.disconnect();
}
}
render() {
return (
<div ref={this.resizeElement}>
...
</div>
);
}
}
EDIT: Davidicus's answer below is more complete, look there first
ResizeObserver can't go in the constructor because the div doesn't exist at that point in the component lifecycle.
I don't think you can get around the extra div because react components reduce to html elements anyway.
Put this in componentDidMount and it should work:
componentDidMount() {
const resizeObserver = new ResizeObserver((entries) => {
console.log("Hello World");
});
resizeObserver.observe(document.getElementById("myDivTag"));
}
I was fighting a the similar problem recently with the difference that my app is predominantly using hooks and functional components.
Here is an example how to use the ResizeObserver within a React functional component (in typescript):
const resizeObserver = React.useRef<ResizeObserver>(new ResizeObserver((entries:ResizeObserverEntry[]) => {
// your code to handle the size change
}));
const resizedContainerRef = React.useCallback((container: HTMLDivElement) => {
if (container !== null) {
resizeObserver.current.observe(container);
}
// When element is unmounted, ref callback is called with a null argument
// => best time to cleanup the observer
else {
if (resizeObserver.current)
resizeObserver.current.disconnect();
}
}, [resizeObserver.current]);
return <div ref={resizedContainerRef}>
// Your component content here
</div>;
The first time my array is rendered it is in the correct order, however, if it is changed the rendered order remains the same.
For example:
construct() {
this.state = {
test_array: [1,2,3,4]
}
let self = this;
setTimeout(function(){
self.scramble();
}, 5000);
}
scramble() {
this.state.test_array = [3,1,2,4];
this.setState(self.state);
}
render() {
this.state.test_array.forEach(function(item){
console.log(item);
});
return (
<div>
{this.state.test_array}
</div>
);
}
Results in:
On the console (the current order, correct):
3
1
2
4
Rendered as DOM (the original order, incorrect):
1
2
3
4
Any idea why this is failing to render in the correct order?
You were very close. Here's a few things I changed to fix it:
construct should be constructor
You always need to call super() as the first line of a constructor. (You don't really need to worry about this, it's an Object Oriented thing; google it if you're curious)
Use "arrow functions" instead of "keyword functions" or .bind(this) to prevent this from changing contexts
Do not modify this.state; always call this.setState if you want it to change
class OrderThing extends React.Component {
constructor() {
super()
this.state = {
test_array: [1,2,3,4]
}
setTimeout(() => {
this.scramble();
}, 5000);
}
scramble() {
this.setState({
test_array: [3,1,2,4]
});
}
render() {
this.state.test_array.forEach(function(item){
console.log(item);
});
return (
<div>
{this.state.test_array}
</div>
);
}
}
const div = document.createElement('div')
document.body.appendChild(div)
ReactDOM.render(<OrderThing />, div)
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
A few suggestions here.
First of all, there is no construct() in js, but there is constructor().
Secondly, you should always call super method with props as an argument in constructor, like this:
constructor(props) {
super(props);
...
}
Finally, react developers highly recommend to modify state only using setState() method.
So you should rewrite your scramble method.
scramble() {
this.setState({test_array: [3,1,2,4]});
}
This changes should help you a little bit.
I've got this React parent component here. The children components at this point are just returning dropdown menus. I expected that componentWillReceiveProps would update the state here, which in turn should be passed to StopList as props. However, when state.selectedSub is changed through handleSubSelect, nothing happens and StopList doesn't receive any props.
Is my mistake with the asynchronous nature of componentWillReceiveProps? Is it in the wrong place in my code? Am I using the wrong lifecycle method?
// We're controlling all of our state here and using children
// components only to return lists and handle AJAX calls.
import React, { Component } from 'react';
import SubList from './SubList';
import StopList from './StopList';
class SubCheck extends Component {
constructor (props) {
super(props);
this.state = {
selectedSub: '--',
selectedStop: null,
stops: ['--'],
};
this.handleSubSelect.bind(this);
this.handleStopSelect.bind(this);
}
// We want the user to be able to select their specific subway
// stop, so obviously a different array of stops needs to be
// loaded for each subway. We're getting those from utils/stops.json.
componentWillReceiveProps(nextProps) {
var stopData = require('../utils/stops');
var stopsArray = [];
var newSub = nextProps.selectedSub
for(var i = 0; i < stopData.length; i++) {
var stop = stopData[i];
if (stop.stop_id.charAt(0) === this.state.selectedSub) {
stopsArray.push(stop.stop_name);
}
}
if (stopsArray.length !== 0 && newSub !== this.state.selectedSub) {
this.setState({stops: stopsArray});
}
}
handleSubSelect(event) {
this.setState({selectedSub:event.target.selectedSub});
}
handleStopSelect(event) {
this.setState({selectedStop:event.target.selectedStop})
}
render() {
return (
<div>
<SubList onSubSelect={this.handleSubSelect.bind(this)}/>
<StopList stops={this.state.stops} onStopSelect={this.handleStopSelect.bind(this)}/>
</div>
);
}
}
export default SubCheck;
You are duplicating data, and causing yourself headaches that aren't necessary.
Both selectedSub and selectedStop are being stored as props and as state attributes. You need to decide where this data lives and put it in a singular location.
The problem you are encountering entirely revolves round the fact that you are changing the state attribute and expecting this to trigger a change to your props. Just because they share a name does not mean they are the same value.