React and jquery-ui/ui/widgets/sortable: Child component not reorder as expect

Tags: , ,



I have an array in “State” of parent component called “portfolios”. You can access by using: “this.state.portfolios”.

And a child component called “StockDetailsComponent”.

In parent component, I’m using map function to render child component “StockDetailsComponent” like this:

  {
    this.state.portfolios.map((obj, key) => {
      return <StockDetailsComponent key={key} portfolio={obj} onRemovePortfolio={this.onRemovePortfolio}/>;
    })
  }

It’s ok. But when I reorder “this.state.portfolios”, child component re-render not as expected.

Before: portfolios = [“object_stock_symbol_1”, “object_stock_symbol_2”];

After re-order: portfolios = [“object_stock_symbol_2”, “object_stock_symbol_1”];

Parent component looks like as below and lets me explan:

 class VndDomain extends React.Component {
  constructor(props) {
    super(props);
    this.socket = null;
    this.sortableEnabled = false;
    this.state = {
      portfolios: []
    };
  }

  render() {
    return (
      <div id="SQ-vnd">
        <HeaderComponent/>

        <div className="SQ-body">
          <div className="SQ-text-right">
            <p className="SQ-d-inline-block SQ-cursor-pointer SQ-mt-5 SQ-mb-5" onClick={this.removeAllPortfolios}>Xóa toàn bộ</p>
          </div>

          <StockFormCreateComponent onCreatePortfolio={this.onCreatePortfolio}/>

          <div id="JSSQ-portfolio">
            {
              this.state.portfolios.map((obj, key) => {
                return <StockDetailsComponent key={key} portfolio={obj} onRemovePortfolio={this.onRemovePortfolio}/>;
              })
            }
          </div>

        </div>

        <FooterComponent/>
      </div>
    );
  }

  componentDidMount() {
    this.enableSortable();
    this.getAllPortfolioByUserId();
  }

  /**
   * Get all portfolios belong to current user
   * @return {Promise} [description]
   */
  getAllPortfolioByUserId = async () => {
    try {
      this.props.dispatch(loadingSpinnerActions.showLoadingSpinner());
      let result = await PortfolioService.getAllPortfolioByUserId();
      if(result.data.status === "SUCCESSFUL") {
        this.setState({portfolios: result.data.data}, () => {
          this.props.dispatch(loadingSpinnerActions.hideLoadingSpinner());
        });
      } else {
        throw new Error(`${result.data.message}`);
      }
    } catch(error) {
      this.props.dispatch(loadingSpinnerActions.hideLoadingSpinner());
      CommonUtilities.ShowLog(error.message);
    }
  }

  /**
   * Enable drag and drop to reorder
   * @return {[type]} [description]
   */
  enableSortable = () => {
    let parentEl = $("#JSSQ-portfolio");
    // Check duplicate sortable before set
    if (this.sortableEnabled) {
      parentEl.sortable("destroy");
      parentEl.sortable();
    } else {
      parentEl.sortable();
      this.sortableEnabled = true;
    }

    parentEl.on("sortupdate", async () => {
      let sorted = parentEl.sortable("serialize");
      let newOrderArrObj = sorted.split("&").map((value) => {
        let symbol = value.replace("SQ[]=", "").toUpperCase();
        let portfolio = this.state.portfolios.find(obj => obj.symbol === symbol);
        return {
          _id: portfolio._id,
          symbol
        };
      });

      try {
        this.props.dispatch(loadingSpinnerActions.showLoadingSpinner());
        let result = await PortfolioService.reorderPortfolio({newOrder: newOrderArrObj});
        if(result.data.status === "SUCCESSFUL") {
          this.setState({portfolios: result.data.data}, () => {
            this.props.dispatch(loadingSpinnerActions.hideLoadingSpinner());
          });
        } else {
          throw new Error(`${result.data.message}`);
        }
      } catch(error) {
        this.setState((prevState) => {
          return {portfolios: prevState.portfolios};
        }, () => {
          this.props.dispatch(loadingSpinnerActions.hideLoadingSpinner());
        });
      }

    });
  }

First, I get list Portfolios from the database, assign to “portfolios” of state, shown in the Client. And enable drag/drop to re-order by “enableSortable” function. At this time, it’s working fine.

When I drag to re-order, “this.state.portfolios” changed as expected, I can see in “console.log()” but child component render is wrong. Not as order.

The code is very long, so you only need to pay attention to the following options I tried: Option 1:

this.setState({portfolios: result.data.data}, () => {
  this.props.dispatch(loadingSpinnerActions.hideLoadingSpinner());
});

result.data.data is data after re-ordered, it’s fine but re-render not work as order.

Option 2: If I clear state by an empty array and set it again like code below, it’s work because child component has been “Unmount” and re-render instead just update like Option 1.

 this.setState({portfolios: []}, () => {
   this.setState({portfolios: result.data.data}, () => {
     this.props.dispatch(loadingSpinnerActions.hideLoadingSpinner());
   });
 });

Please help me 🙁 I don’t want to setState and then setState again.

Answer

It looks like that your portfolio data is a complex object which React is not able to determine that has changed. Yoiu can do like below:

You can do like :

let newPrtfolio = Object.assign ([],result.data.data); // Assign data to new object and assign it

this.setState({portfolios: newPrtfolio }, () => {
  this.props.dispatch(loadingSpinnerActions.hideLoadingSpinner());
});

or if your portfolio is an object and not an array than you may try below way:

 let newPrtfolio = Object.assign ({},result.data.data); // Assign data to new object and assign it
    
    this.setState({portfolios: newPrtfolio }, () => {
      this.props.dispatch(loadingSpinnerActions.hideLoadingSpinner());
    });

You may try both the ways, and one of them will work out for you depending upon the structure of your portfolio object



Source: stackoverflow