Reputation: 1142
I am making simple react app. I need to make an order. For that order I need a list of all users and products.I have CreateOrder
component which has 2 variables in its state
- users
and products
. Here is the constructor
this.state.orders = OrderStore.getState()
this.state.products = ProductStore.getState()
this.state.users = UserStore.getState()
this.onOrderChange = this.onOrderChange.bind(this)
this.onProductChange = this.onProductChange.bind(this)
this.onUserChange = this.onUserChange.bind(this)
this.createOrder = this.createOrder.bind(this)
this.renderProducts = this.renderProducts.bind(this)
this.renderUsers = this.renderUsers.bind(this)
this.users = []
this.products = []
As you can see I am using 3 stores and therefore 3 functions for updating these stores. After that I have 2 functions for rendering products and users. Those functions take all products and users from their respective variable and create an array of option
tags to be used <select>
. Those arrays are stored in this.users
and this.products
so that I can use them.
Here is the code in renderUsers
and renderProducts
renderUsers(){
this.users = this.state.users.users.map((user, i) => (
<option
key={i + 1}
value={user}>
{user.username}
</option>
)
)
console.log(this.users)
}
renderProducts(){
this.products = this.state.products.products.map((product, i) => (
<option
key={i + 1}
value={product}>
{product.name}
</option>
)
)
console.log(this.products)
}
I can't see much of a difference between the two. After the arrays are made and filled with option
tags I use console.log
to see the result. Here is a picture of what actually
As you can see there are 2 arrays of objects and each object is react component as expected.
When I render CreateOrder
I use this.products
and this.users
to be rendered and at first they are empty. Then when something changes in the stores (they are filled with all products and users) the functions renderUsers
and renderProducts
are executed and therefore the arrays are filled with data. When this happens I expect the view to be updated.
Now the problem - When this happens and this.products
is being updated the view actually changes and is actually updated with a list of all products. However - using the same logic and the same functions and code - the users are not being updated. They stay the same. Even though I have them as elements in an array and I call this array in render
they are not being updated.
To add some information here is my whole CreateOrder
file.
/**
* Created by PC on 01-Aug-17.
*/
import React from 'react'
import OrderActions from '../actions/OrderActions'
import ProductActions from '../actions/ProductActions'
import ProductStore from '../stores/ProductStore'
import UserActions from '../actions/UserActions'
import UserStore from '../stores/UserStore'
import OrderStore from '../stores/OrderStore'
import Select from './sub-components/SelectComponent'
import Input from './sub-components/InputComponent'
export default class CreateOrder extends React.Component {
constructor (props) {
super(props)
this.state = {}
this.state.orders = OrderStore.getState()
this.state.products = ProductStore.getState()
this.state.users = UserStore.getState()
this.onOrderChange = this.onOrderChange.bind(this)
this.onProductChange = this.onProductChange.bind(this)
this.onUserChange = this.onUserChange.bind(this)
this.createOrder = this.createOrder.bind(this)
this.renderProducts = this.renderProducts.bind(this)
this.renderUsers = this.renderUsers.bind(this)
this.users = []
this.products = []
}
componentDidMount () {
OrderStore.listen(this.onOrderChange)
ProductStore.listen(this.onProductChange)
UserStore.listen(this.onUserChange)
ProductActions.getAllProducts()
UserActions.getAllUsers()
}
componentWillUnmount () {
OrderStore.unlisten(this.onOrderChange)
UserStore.unlisten(this.onUserChange)
ProductStore.unlisten(this.onProductChange)
}
onOrderChange (state) {
this.setState({orders:state})
}
onProductChange(state) {
this.setState({products:state})
this.renderProducts()
}
onUserChange(state){
this.setState({users:state})
this.renderUsers()
}
createOrder (event) {
event.preventDefault()
let data = {}
data['username'] = this.state.orders.username
data['productName'] = this.state.orders.product.name
data['productPrice'] = this.state.orders.product.price
data['quantity'] = this.state.orders.quantity
OrderActions.addOrder(data)
}
renderUsers(){
this.users = this.state.users.users.map((user, i) => (
<option
key={i + 1}
value={user}>
{user.username}
</option>
)
)
console.log(this.users)
}
renderProducts(){
this.products = this.state.products.products.map((product, i) => (
<option
key={i + 1}
value={product}>
{product.name}
</option>
)
)
console.log(this.products)
}
render () {
return (
<div>
<div className='has-error'>
{this.state.orders.error}
</div>
<div className='has-success'>
{this.state.orders.success}
</div>
<form>
<select>{this.users}</select>
<select>{this.products}</select>
<div className="inputField">
<label htmlFor='title'>Quantity</label>
<Input
type='number'
name='price'
id='price'
placeholder='Price'
onChange={OrderActions.handleQuantityChange}
value={this.state.quantity}/>
</div>
<input type='submit' onClick={this.createOrder.bind(this)} value='Create Order'/>
</form>
</div>
)
}
}
Upvotes: 0
Views: 8987
Reputation: 104369
I am not sure about the actual cause of the problem but i think this is related to async behaviour of setState, we can't expect the updated state value just after the setState.
Check this answer for more details about async behaviour of setState.
Solution:
Storing the ui component in state variable or in global variable is not a good idea, all ui logic should be inside render method, so instead of storing the options in global variable, directly return from that function.
Whenever we do setState
react will re-render the component and update the ui with new data.
Step1 - Use these methods, removed another function calling:
onProductChange(state) {
this.setState({products:state})
}
onUserChange(state){
this.setState({users:state})
}
Step2 - Use these methods to render the options:
renderUsers(){
if(!this.state.users.users) return null;
return this.state.users.users.map((user, i) => (
<option
key={i + 1}
value={user}>
{user.username}
</option>
)
)
}
renderProducts(){
if(!this.state.products.products) return null;
return this.state.products.products.map((product, i) => (
<option
key={i + 1}
value={product}>
{product.name}
</option>
)
)
}
Step3 - Call these functions from render to create options:
<select>{this.renderUsers()}</select>
<select>{this.renderProducts()}</select>
Upvotes: 1
Reputation: 5645
setState
is asynchronous. Try putting your this.renderUsers();
in the componentWillUpdate
lifecycle method.
componentWillUpdate() is invoked immediately before rendering when new props or state are being received. Use this as an opportunity to perform preparation before an update occurs. This method is not called for the initial render.
You could also use the callback
argument of setState(updater, [callback]).
Upvotes: 0