简体   繁体   中英

Why React component doesn't render?

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>
    )
  }
}

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>

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]) .

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM