setState with arrow function does not work - javascript

Could you please have a look on the following code. I need to get some value from another class. This works asynchronous, so I provided a function handleGameDeserialization.
The function gets the right value (as I tested with the alert), however the setState function has no impact. Could that be a "this-context" issue?
export default class GameplayScreen extends React.Component {
constructor(props) {
super(props);
this.fbGame = new FBGame();
global.currentScreenIndex = 'Gameplay';
this.state = {
currentGame: 'N/A'
}
// this.handleGameDeserialization = this.handleGameDeserialization.bind(this);
if (this.props.route.params != null) {
this.gameKey = this.props.route.params.gameKey;
this.game = this.fbGame.deserializeGame(this.gameKey, this.handleGameDeserialization);
}
}
handleGameDeserialization = (game) => {
// alert('yeah'+game); // here comes the expected output
this.setState({
currentGame: game
});
}
render() {
return (
<View>
<Text>{this.state.currentGame}</Text>
</View>
/*<Board game={this.state.game}/>*/
)
}
}
I call that function when the component GameplayScreen is navigated to. As you can see above, there is a class FBGame, which does the deserialization (read the game from firebase database)
export default class FBGame {
...
deserializeGame(key, handleGameDeserialization) {
var gameRef = firebase.database().ref("games/"+key).child("serialized");
gameRef.on("value", snapshot => {
//console.log('deserialized: ' + );
handleGameDeserialization(snapshot.val().serialized);
});
}
...
}
edit:
When I use componentDidMount like below, it works fine. But this seems to be an anti-pattern. I still don't understand, why it doesn't work, when callded in the constructor and how I am supposed to solve this.
componentDidMount() {
this.game = this.fbGame.deserializeGame(this.gameKey, this.handleGameDeserialization);
}

For things like subscriptions that will update the state and other side-effects, you should put the logic out in componentDidMount() which will fire immediately after the component is mounted and won’t give you any trouble if you update the state inside of it.
You can't but things that call this.setState in the constructor.

Related

Component not rerendering on state change?

Whenever setState() is called, the component doesn't seem to rerender. As you can see by my comments, the state does in fact change and render seems to be called again, but if I don't add that if statement and simply add a paragraph tag that displays the data it will give me an error. I'm sure I'm missing something simple, but any help is appreciated.
import React from "react";
import axios from "axios";
import { constants } from "../constants/constants";
const { baseURL, apiKey, userName } = constants;
class User extends React.Component {
constructor(props) {
super(props);
this.state = {
user: []
};
}
componentDidMount() {
let getUserInfo = axios.create({
baseURL,
url: `?
method=user.getinfo&user=${userName}&api_key=${apiKey}&format=json`
});
getUserInfo().then(response => {
let data = response.data;
console.log(data.user.playcount); //logs second, displays correct
this.setState(state => ({
user: data
}));
});
}
render() {
console.log(this.state); //logs first and third, doesn't work on first but does on third
let toReturn;
if (this.state.user.length > 0) {
toReturn = <p>{this.state.user.user.playcount}</p>;
} else {
toReturn = <p>didn't work</p>;
}
return <div>{toReturn}</div>;
}
}
export default User;
React LifeCycle function sequence is Constructor and then it calls render method.
In constructor method it initialises the state which is currently empty user array.
Now it calls render() method as this.state.user is an empty array, referencing something out of it gives an error
this.state.user.user.playcount
this will generate an error if you dont have if condition.
After the first render it will call componentDidMount, now you fetch something update state. As setState occurred, render will be called again Now you have something in this.state.user then displaying will happen.
this.state.user.length > 0 is true
Look at this: https://reactjs.org/docs/react-component.html and https://reactjs.org/docs/conditional-rendering.html
You can right in single tag using conditional render like this
<p>{this.state.user.length ? this.state.user.user.playcount : 'loading'}
Hope this helps.
I think your problem might have something to do with the changing shape of the user value. You initialise the value to an empty array, but then—after the fetch is done—you assume it's an object (by using user.user).
Maybe you could simplify the code a bit to look more like the one below?
/* imports */
class User extends React.Component {
constructor(props) {
super(props);
this.state = {
user: null // Make it explicit there's no value at the beginning.
};
}
componentDidMount() {
let getUserInfo = axios.create(/* ... */);
getUserInfo().then(response => {
let data = response.data;
this.setState({ // No need to for a setter function as you dno't rely on the previous state's value.
user: data.user // Assign the user object as the new value.
});
});
}
render() {
let toReturn;
// Since it's now a `null`, you can use a simple existence check.
if (this.state.user) {
// User is now an object, so you can safely refer to its properties.
toReturn = <p>{this.state.user.playcount}</p>;
} else {
toReturn = <p>No data yet.</p>;
}
return <div>{toReturn}</div>;
}
}
export default User;

React Component User Not in State

COnsider the following:
componentDidMount(){
this.props.requestUser(this.props.match.params.userID).then(res =>
{
this.pageOwner = res.user;
debugger;
})
}
Why is my this.pageOwner still returning undefined? I have been trying my as off to get a user into a state that my user profile can actually acces.
If you're trying to access this.pageOwner, you need to do so after the this.props.requestUser() async function has completed. You're currently getting the user in componentDidMount(), so your render() will initially see this.pageOwner as undefined until the async call is finished.
Additionally, it might be better to store the result of your async call in state so that your component will re-render once the value is filled.
As #colin said, put it in this.state. If you are getting an error in the render() function, then just make your render like so:
constructor() {
super();
this.state = {
pageOwner: null
}
}
componentDidMount() {
this.props.requestUser(this.props.match.params.userID).then(res =>
{
this.setState({pageOwner: res.user});
debugger;
})
}
render() {
if(this.state.pageOwner)
return ( ... )
else
return null;
}

ReactJS element not updating after setState being called

Good Afternoon,
I have a React component that is dynamically rendered in reponse to an API call. I have set the value of one of the elements to a state within the component. During an onClick function (minusOne) this value is supposed to change.
The value is initially rendered successfully based on the state, the function does indeed change the state, however the rendered element stays the same despite the state changing. Does anyone have any ideas of why this might be the case?
If you have any questions, please ask away!
export class Cart extends React.Component {
constructor(props) {
super(props);
this.state={
quantities: []
};
this.minusOne = this.minusOne.bind(this);
}
minusOne(i) {
var self = this;
return function() {
let quantities = self.state.quantities;
if (quantities[i] > 1) {
quantities[i] --;
}
self.setState({
quantities
})
}
}
componentDidMount() {
let cart = this.props.cartTotals;
this.setState({
cart
});
if(cart.lines) {
let cartTotal = [];
let quantities = [];
for (var i = 0; i < cart.lines.length; i++) {
if(cart.lines[i]) {
quantities.push(cart.lines[i].quantity);
}
}
//Initial setting of state
this.setState({
quantities
})
Promise.all(
cart.lines.map(
(cart, i) => axios.get('http://removed.net/article/' + cart.sku)
)
).then(res => {
const allCartItems = res.map((res, i) => {
const data = res.data;
return(
<div key={i} className="cart-item-container">
<img className ="cart-item-picture" src={data.image} name={data.name} />
<div className="cart-item-description">
<p>{data.name}</p>
<p>{data.price.amount} {data.price.currency}</p>
</div>
<div className="cart-item-quantity">
<button onClick={this.minusOne(i)} name="minus">-</button>
//This is the troublesome element
<p className="cart-current-quantity">{this.state.quantities[i]}</p>
<button name="plus">+</button>
</div>
</div>
)
})
this.setState({
allCartItems
})
})
}
}
render() {
return (
{this.state.allCartItems}
);
}
}
Thanks for reading! Any advice will be helpful.
There are two issues:
First, you need to render (including where the onClick is) in render(). ConponentDidMount is only called once and supposed to perform initialization but not render.
Then, there is a problem in minusOne:
quantities points to this.state.quantities. So you are changing the old state, React looks at both the old state and the new one, sees there is no change, and dodesn't render, although the values have changed.
If you will copy this.state.quantities to a new array, like:
newQ = this.state.quantities.slice(0, -1);
Then modify newQ, then do
this.setState({ quantities: newQ });
It should work.
I think you don't need to return a function at minusOne(i) method. Just update the state is enough. You should change the array by specific id.
let quantities = self.state.quantities;
let mutatedQuantities = quantities.map((el, index) => {
return (index === i) ? el - 1 : el;
})
this.setState({quantities: [...mutatedQuantities]})
--- edited ---
I deleted everything I wrote before to make it more concise.
Your problem is that you assign what you want to render to a variable in componentDidMount. This function does only get called once, hence you asigne the variable allCartItems only once. The setState function does not have any effect because it does not trigger componentDidMount and therefore your variable allCartItems does not get reassigned.
What can you do? Well you can do a lot of stuff to enhance your code. First I will let you know about how you can solve your problem and then give you some further improvements
To solve the problem of your component not updating when you call setState you should move your jsx to the render Method. In the componentDidMount you just get all the data you need to render your component and once you have it you can set a flag for example like ready to true. Below you can see an example of how your code could look like.
import React from 'react';
class Cart extends React.Component {
constructor(props) {
super(props);
this.state = {
carts: null,
ready: false,
};
}
componentDidMount() {
fetch('www.google.com').then((carts) => {
this.setState({
carts,
ready: true,
});
});
}
render() {
const myCarts = <h2> Count {this.state.carts} </h2>;
return (
{
this.state.ready
? myCarts
: <h2> Loading... </h2>
}
);
}
}
I made you a demo with a simple counter with some explanations of your case and how you can make it work. You can check it out codesandbox. In the NotWorkingCounter you can see the same problem as in your component of the variable not being updated. In the WorkingCount you can see an example where I implemented what a I wrote above with waiting until your data has arrived and only then render it.
Some more suggestions concerning code:
Those two syntaxes below are identical. One is just a lot more concise.
class Cart extends React.Component {
constructor(props) {
super(props);
this.state = {
carts: null,
ready: false,
};
}
}
class Cart extends React.Component {
state = {
carts: null,
ready: false,
}
}
I would suggest to use arrow function if you want to bind your context. Below you can see your example simplified and an example on how you can achieve the same thing with less syntax.
export class Cart extends React.Component {
constructor(props) {
super(props);
this.minusOne = this.minusOne.bind(this);
}
minusOne(i) {
///
}
}
export class Cart extends React.Component {
constructor(props) {
super(props);
}
minusOne = (i) => {
/// minus one function
}
}
Your minusOne could also be rewritten if you use arrow functions and be a lot smaller, something in the area of
minusOne = (i) => (i) => {
let quant = self.state.quantities[i];
if(quant > 1) {
this.setState({
quantities: quant-1,
})
}
}
In your componentDidMount you call this.setState twice. Every time you call this function your component gets rerender. So what happens in your component is when your mount your component it gets rendered the first time, once it is mounted componentDidMount gets called, in there you call this.setState again twice. This means your component get's rendered in the best case three times before the user sees your component. If you get multiple promises back this means your rerender your state even more. This can create a lot of load for your component to cope with. If you rerender every component three times or more you end up having some performance issues once your application grows. Try to not call setState in your componentDidUpdate more than once.
In your case your first call to setState is totally unnecessary and just creates load. You still have access to quantities in your promise. Just call setState once at the end of your promise.then() with both elements.
In the example below you are using the index i as a key. This is not a good case practice and react should also log you at least a warning in the console. You need to use a unique identifier which is not the index. If you use the index you can get sideeffects and weird rendering which is difficult to debut. Read more on it here
then(res => {
const allCartItems = res.map((res, i) => {
const data = res.data;
return(
<div key={i} className="cart-item-container">
Another suggestion is to replace all var with const or let, as var exposes your variable to the global scope. If you don't understand what that means read this.
Last but not least have a look at object deconstruction. It can help you to clean up your code and make it more resistant to unwanted sideffects.

Component doesnt update view properly

Render does indeed get called and even though the debugger shows that temp is populated properly, the change doesnt seem to take place (the json string doesnt make it to the dom). I am probably missing something obvious.
class ProfileComponent extends Component {
constructor(props){
this.props = props;
}
componentDidMount(){
window.mainStore.subscribe(this.render.bind(this))
}
render() {
var temp = JSON.stringify(window.mainStore.getState().profile);
return (
<div>
{temp}
</div>
);
}
}
Debugging looks like :
It seems that the first time 'ProfileComponent' rendered we don't have the subscribe method, after the componentDidMount we see the correct result , lets try to add a state which blocks the first render to return invalid {temp}:
class ProfileComponent extends Component {
constructor(props){
super(props);
this.state = { loading: true, temp:''};
}
componentDidMount() {
window.mainStore.subscribe(); // I don't see all your code but i think here we don't need to bind the render because the component will render after again after changing the state (you can try it both)
const temp = JSON.stringify(window.mainStore.getState().profile); // better keeping 'window' logic here (especially if our app is SSR)
this.setState({loading: false,temp});
}
render() {
const {loading, temp} = this.state;
if(loading) {return (<div>Loading...</div>)}
return (
<div>
{temp}
</div>
);
}
};

React Higher Order Component conditional data load

Imagine I have some "page" component, which needs to ask for data from a server. The data it requests will depend on whether or not the current user is authenticated. Further, in the event of a login, the page will want to reload the data. My question is, how can I accomplish something like this using HOCs rather than inheritance?
To illustrate the problem, I'll demonstrate a solution using inheritance. The program will have the following objects. I'll leave out the boilerplate code.
session: an EventEmitter that emits start when the session changes (either a login or a log out).
Page: the superclass that all pages inherit from
MyPage: the subclass of Page in this example
API: will be an API class for retrieving data from the server
Here's the code:
// Page superclass
class Page extends React.Component {
componentWillMount() {
session.on("start", this.loadData);
this.loadData();
}
loadData() {
// this method is overwritten in subclasses
}
}
// MyPage subclass
class MyPage extends Page {
loadData() {
if(session.isAuthenticated()) {
API.loadPrivateData();
} else {
API.loadPublicData();
}
}
}
Here's a solution that uses an HOC, but seems less elegant than inheritance. It still requires that every "subclass" page have a method loadData, and it requires that method to be called in every "subclass's" componentWillMount.
// Page HOC
function Page(WrappedComponent) {
return class EnhancedPage extends React.Component {
componentWillMount() {
session.on("start", this.loadData);
// this._page.loadData() will fail here
// since this._page is undefined until rendering finishes
}
loadData() {
this._page.loadData();
}
render() {
return <WrappedComponent {...props} ref={(e) => { this._page = e; }} />
}
}
}
// MyPage
class MyPage extends React.Component {
componentWillMount() {
this.loadData();
}
loadData() {
if(session.isAuthenticated()) {
API.loadPrivateData();
} else {
API.loadPublicData();
}
}
}
const component = Page(MyPage)
// what would make sense here is to have a method something like
// const component = Page(MyPage, () => MyPage.loadData())
// but then the MyPage.loadData logic would need to be defined
// elsewhere
This pattern will happen often: I'll want to load some data, then reload when the session changes. I'd like to understand the "react" way of accomplishing the same.
EDIT: I am not trying to pass a username or "loggedIn" flag through the HOC. That is to say something like <WrappedComponent isLoggedIn={session.isAuthenticated()} {...props} /> won't cut it here. Tying the API logic to props requires that I check for changes in MyPage.componentWillUpdate().
When using a HOC you shouldn't place the loadData function on the wrapped component. Instead pass the function as a parameter to the HOC constructor.
Something like this might work for you. The sessionHoc function takes a callback function which'll be called every time the session state changes. Its result will be passed to WrappedComponent as a data prop.
function sessionHoc(onSessionChange) {
return function (WrappedComponent) {
return class extends React.Component {
constructor(props) {
super(props);
this.state = {
data: null,
};
session.on('start', this.handleSessionChange.bind(this));
}
handleSessionChange() {
this.setState({
data: onSessionChange(),
});
}
render() {
return <WrappedComponent data={data} {...this.props} />
}
};
};
}
class MyPage extends React.Component {
render() {
// Just access this.props.data here!
}
}
const EnhancedPage = sessionHoc(function () {
if (session.isAuthenticated()) {
return API.loadPrivateData();
} else {
return API.loadPublicData();
}
})(MyPage);
Hopefully this helped! :)

Categories