简体   繁体   中英

Refs in mount/update component lifecycle

I have an infinite scrolling list that scrolls in both directions. The list presents a 5 item-per-row grid that represents the 5 work days in a 7 day week. The days are zebra stripped by month (even months have a slightly darker color). I want to position the month title in a column to the left of the grid, starting at the first day of the month, or the earliest day available for the month (given the AJAXed in data -- does not always start with the first weekday of the month).

I have all the necessary logic in place to figure out which day is the first day (as I'm only showing weekdays, if the first falls on a weekend, then the start of the month might not be the first).

I have the following so far:

  1. When rendering the grid, tag the beginning of the month and the end of the month with a ref in the format of YYYYMM_start or YYYYMM_end .
  2. I then render the MonthTitle component with a monthStamp prop that is in the same format of YYYYMM , as well as a findMonthNode prop that receives a function to lookup the start and end node on the parent component (using regex).

  3. In the MonthTitle component, in componentDidMount , I can then position the title absolutely relative to the start node for that month. I also invoke the exact same function in componentDidUpdate .

My issue is when I scroll the list (remember, infinite scroll in both directions) back in time: my AJAX fires and loads in the new tiles with the new month titles, and the new month titles render appropriately, but the first of the original titles does NOT update accordingly. After some poking around, I realized that this.refs in the parent component in componentDidUpdate does not equal this.refs in the parent component in componentDidMount . componentDidMount seems to have the complete list, whereas componentDidUpdate only has the list of nodes that existed before the new nodes (from the AJAX response) have been mounted.

Since componentDidUpdate does not have all the new refs, I cannot position the previously first month title accordingly, and it remains stuck where it was initially rendered in componentDidMount , which--given the new data--is now the incorrect position.


Why is this.refs different in these two functions ( componentDidMount & componentDidUpdate )? What can I do to ensure that I have access to the complete this.refs when mounting AND updating components?

Using react 0.13.3 FYI

If you look at the component lifecycle specs here , you will see that componentDidUpdate is run every time your component has been updated (via this.setState in either the component or one of its ancestors), but not after the initial render:

componentDidUpdate(object prevProps, object prevState)

Invoked immediately after the component's updates are flushed to the DOM. This method is not called for the initial render.

componentDidMount on the other hand is run after the initial render, but not after updates via setState :

componentDidMount()

Invoked once, only on the client (not on the server), immediately after the initial rendering occurs.

So if you update your state (via this.setState in your component) and re-render your component, potentially adding new child elements that have ref attributes, those will not be visible in the parent's componentDidMount but in componentDidUpdate .

On the other hand, if you update your parent component and the update renders new child components, those child components' componentDidMount will indeed fire before the parent's componentDidUpdate as the parent component is not done updating until all its new child components have mounted and rendered properly. It will go like this:

  1. Any initial childrens' componentDidMount execute (somewhat surprising perhaps, but the parent isn't done mounting until all its children have mounted, which makes sense)
  2. Parent's componentDidMount executes
  3. Parent updates via setState with some added children and some children already existing from last render
  4. All newly added childrens' componentDidMount execute and all previously existing childrens' componentDidUpdate execute
  5. Parent's componentDidUpdate executes

Here's a JSFiddle demonstrating this.

Ok, I figured out a solution. Instead of MonthTitle handling its own position during its lifecycle callbacks ( componentDidMount and componentDidUpdate ), I am having their parent component ( Grid ) invoke methods on the titles to position them in the parent's componentDidUpdate .

So, roughly, in Grid , I have the following:

componentDidUpdate() {
  for (let [ref, component] of entries(this.refs)) {
    if (/_title$/.test(ref)) {
      var startOfMonthNode = React.findDOMNode(findRef("startOfMonth", this.refs, component.props.monthStamp))
      var endOfMonthNode = React.findDOMNode(findRef("endOfMonth", this.refs, component.props.monthStamp))

      component.position(startOfMonthNode, endOfMonthNode)
    }
  }
}

Then, inside my MonthTitle component, I define the positioning logic:

position(startOfMonthNode, endOfMonthNode) {
  React.findDOMNode(this).style["top"] = getDimension((startOfMonthNode || endOfMonthNode), "top") + window.scrollY - 116 + "px"
}

This means that I had to add the titles themselves to my refs list in Grid .


So, conceptually, the difference was moving when the positioning updates happened from the child component's lifecycle to the parent component's lifecycle. Since the parent component's componentDidUpdate will fire after any new child components have been mounted, I have the complete list of refs available.

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