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>
);
}
};
Related
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.
I have this code:
export default class FinancesPage extends React.Component {
constructor(props) {
super(props);
this.state = {users: []};
}
componentWillMount() {
firebase.database().ref('Users').orderByChild('transactions').startAt(1).on('value', snap => {
const users = arrayFromObject(snap.val());
this.setState({users: users});
});
}
render() {
return (
<div>
<NumberOfPurchasesComponent users={this.state.users}/>
</div>
)
}
}
And this code:
export default class NumberOfPurchasesComponent extends React.Component {
constructor(props) {
super(props);
this.state = {users: this.props.users};
}
componentWillMount() {
const users = this.state.users;
// Do stuff here
}
render() {
return (
{/*And render stuff here*/}
);
}
}
What's happening right now: The parent element FinancesPage passes an empty array of users to the child NumberOfPurchasesComponent. I need it to pass a new value of the array every time there is an update.
And i want to pass the users from FinancesPage to NumberOfPurchasesComponent, but users data is obtained async. How can I make the NumberOfPurchasesComponent refresh when the variable value is obtained?
Have you tried to use componentWillReceiveProps? I mean something like:
export default class NumberOfPurchasesComponent extends React.Component {
constructor(props) {
super(props);
this.state={users: []}
}
componentWillReceiveProps(nextProps) {
if(nextProps.users && nextProps.users!==this.state.users){
this.setState({
users: nextProps.users
})
}
}
render() {
return (
{/*And render stuff here*/}
);
}
}
This way the component knows when it has to re-render.
The FinancesPage implementation looks good. The problem lies in NumberOfPurchasesComponent in this particular piece of code :
constructor(props) {
super(props);
this.state = {users: this.props.users};
}
Am assuming in the render method of NumberOfPurchasesComponent you are using this.state.users instead of this.props.users.
constructor runs only once. Now as you mentioned data is fetched async, which means NumberOfPurchasesComponent is initially rendered even before the the response is obtained. Hence its constructor method which runs only once sets the users state to []. Even if the props gets updated from FinancesPage, as the render in NumberOfPurchasesComponent uses state, no re-render happens.
Try using this.props.users directly in NumberOfPurchasesComponent render and see if it works.
As per FinancesPage page it is well and good with codebase, but problem is why you are making setstate if there is no any manipulation of user's data as you got from API call.
So without making setState just pass it as direct
render() {
return (
<div>
<NumberOfPurchasesComponent users={this.props.users}/>
</div>
)
}
so whenever the API calls to fetch the response, here update value get in passed to NumberOfPurchasesComponent class.
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.
I am having a bit of an issue rendering components before the state is set to the data from a returned asynchronous API request. I have a fetch() method that fires off, returns data from an API, and then sets the state to this data. Here is that block of code that handles this:
class App extends Component {
constructor() {
super();
this.state = {
currentPrice: null,
};
}
componentDidMount() {
const getCurrentPrice = () => {
const url = 'https://api.coindesk.com/v1/bpi/currentprice.json';
fetch(url).then(data => data.json())
.then(currentPrice => {
this.setState = ({
currentPrice: currentPrice.bpi.USD.rate
})
console.log('API CALL', currentPrice.bpi.USD.rate);
}).catch((error) => {
console.log(error);
})
}
getCurrentPrice();
}
You will notice the console.log('API CALL', currentPrice.bpi.USD.rate) that I use to check if the API data is being returned, and it absolutely is. currentPrice.bpi.USD.rate returns an integer (2345.55 for example) right in the console as expected.
Great, so then I assumed that
this.setState = ({ currentPrice: currentPrice.bpi.USD.rate }) should set the state without an issue, since this data was received back successfully.
So I now render the components like so:
render() {
return (
<div>
<NavigationBar />
<PriceOverview data={this.state.currentPrice}/>
</div>
);
}
}
export default App;
With this, I was expecting to be able to access this data in my PriceOverview.js component like so: this.props.data
I have used console.log() to check this.props.data inside my PriceOverview.js component, and I am getting 'null' back as that is the default I set intially. The issue I am having is that the components render before the API fetch has ran it's course and updated the state with the returned data. So when App.js renders the PriceOverview.js component, it only passes currentPrice: null to it, because the asynchronous fetch() has not returned the data prior to rendering.
My confusion lies with this.setState. I have read that React will call render any time this.setState is called. So in my mind, once the fetch() request comes back, it calls this.setState and changes the state to the returned data. This in turn should cause a re-render and the new state data should be available. I would be lying if I didn't say I was confused here. I was assuming that once the fetch() returned, it would update the state with the requested data, and then that would trigger a re-render.
There has to be something obvious that I am missing here, but my inexperience leaves me alone.. cold.. in the dark throws of despair. I don't have an issue working with 'hard coded' data, as I can pass that around just fine without worry of when it returns. For example, if I set the state in App.js to this.state = { currentPrice: [254.55] }, then I can access it in PriceOverview.js via this.props.data with zero issue. It's the async API request that is getting me here, and I am afraid it has gotten the best of me tonight.
Here App.js in full:
import React, { Component } from 'react';
import './components/css/App.css';
import NavigationBar from './components/NavigationBar';
import PriceOverview from './components/PriceOverview';
class App extends Component {
constructor() {
super();
this.state = {
currentPrice: null,
};
}
componentDidMount() {
const getCurrentPrice = () => {
const url = 'https://api.coindesk.com/v1/bpi/currentprice.json';
fetch(url).then(data => data.json())
.then(currentPrice => {
this.setState = ({
currentPrice: currentPrice.bpi.USD.rate
})
console.log('API CALL', currentPrice.bpi);
}).catch((error) => {
console.log(error);
})
}
getCurrentPrice();
}
render() {
return (
<div>
<NavigationBar />
<PriceOverview data={this.state.currentPrice}/>
</div>
);
}
}
export default App;
Here is PriceOverview.js in full:
import React, { Component } from 'react';
import './css/PriceOverview.css';
import bitcoinLogo from './assets/bitcoin.svg';
class PriceOverview extends Component {
constructor(props) {
super(props);
this.state = {
currentPrice: this.props.data
}
}
render() {
return (
<div className="overviewBar">
<div className="currentPrice panel">
{ this.state.currentPrice != null ? <div className="price">{this.state.currentPrice}</div> : <div className="price">Loading...</div> }
</div>
</div>
)
}
}
export default PriceOverview;
Thank you in advance to any help, it's much appreciated.
this.setState ({
currentPrice: currentPrice.bpi.USD.rate
})
Do not put an = in this.setState
Ok First thing, when you're writting code on React the components that hold state are the class base components so ... What I see here is that you're creating two class base components so when you pass down props from your app class component to your PriceOverview wich is another class base component you're essentially doing nothing... Because when your constructor on your PriceOverview get call you're creating a new state on that Component and the previous state ( that's is the one you want to pass down) is being overwritten and that's why you're seem null when you want to display it. So it should work if you just change your PriveOverview component to a function base component ( or a dumb component). So this way when you pass down the state via props, you're displaying the correct state inside of your div. This is how would look like.
import React from 'react';
import './css/PriceOverview.css';
import bitcoinLogo from './assets/bitcoin.svg';
const PriceOverview = (data) => {
return (
<div className="overviewBar">
<div className="currentPrice panel">
//Im calling data here because that's the name you gave it as ref
//No need to use 'this.props' you only use that to pass down props
{data != null ? <div className="price">
{data}</div> : <div className="price">Loading...</div>
}
</div>
</div>
)
}
}
export default PriceOverview;
Whenever you're writing new components start always with function base components if you component is just returning markup in it and you need to pass some data go to his parent component update it (making the api calls there or setting the state there) and pass down the props you want to render via ref. Read the React docs as much as you can, hope this explanation was useful (my apologies in advance if you don't understand quite well 'cause of my grammar I've to work on that)
The thing is constructor of any JS class is called only once. It is the render method that is called whenever you call this.setState.
So basically you are setting currentPrice to null for once and all in constructor and then accessing it using state so it will always be null.
Better approch would be using props.
You can do something like this in your PriceOverview.js.
import React, { Component } from 'react';
import './css/PriceOverview.css';
import bitcoinLogo from './assets/bitcoin.svg';
class PriceOverview extends Component {
constructor(props) {
super(props);
this.state = {
}
}
render() {
return (
<div className="overviewBar">
<div className="currentPrice panel">
{ this.props.data!= null ? <div className="price">{this.props.data}</div> : <div className="price">Loading...</div> }
</div>
</div>
)
}
}
export default PriceOverview;
Or you can use react lifecycle method componentWillReceiveProps to update the state of PriceOverview.js
componentWillReceiveProps(nextProps) {
this.setState({
currentPrice:nextProps.data
});
}
render() {
return (
<div className="overviewBar">
<div className="currentPrice panel">
{ this.state.currentPrice != null ? <div className="price">{this.state.currentPrice }</div> : <div className="price">Loading...</div> }
</div>
</div>
)
}
}
As i've read online and official documentation. Order is
Constructor-> ComponentWillMount -> Render -> ComponentDidMount and others.
class Navigation extends Component {
constructor(props) {
super(props);
console.log('constructor', this);
this.state = { items: [ ] };
}
componentDidMount() {
console.log('componentDidMount');
if ( toDisplay( ) ) {
api.bringCats()
.then( categories => {
const tot_cat = categories.map( cat => { return {name: cat.name, link: cat.link}; })
this.setState({items: tot_cat,})
})
}
}
render() {
console.log("render")
//some stuff
}
}
im expecting the the log to be in this order
1. constructor
2. ComponentDidMount
3. render
im logging this with constructor inside constructor method. im able to get value for items which is getting its value in componentDidMount why this is happening? and its logging in the correct order (constructor->didmount->render) but why im getting value of items even before calling componentDidMount.
im using <Navigation/> component inside <Header/>
Header extends Component{
render(){
return (<div> <Navigation/> </div>);
}
}
i was trying something and found out that this is working fine.
window.cnj = require('circular-json') /* i added this in starting point first line so i will have access to this inside all imports. */
window.props = JSON.parse(window.cnj.stringify(simple));
I can access window.props from the devtools and its working as expected.
sample can be string, arrays, normal object or circular object.