ctrlplusb / react-tree-walker

Walk a React (or Preact) element tree, executing a "visitor" function against each element.
MIT License
345 stars 34 forks source link

Suggestion: Show recipe with react router 4 #3

Closed jaredpalmer closed 6 years ago

jonjaques commented 7 years ago

Here's my crack at it — One issue I'm currently trying to wrap my head around is how to block on or access nodes that conditionally include further fetch() components. As it currently stands, this code will never pick them up. Also, haven't gotten to solving this on the client side yet.

@jaredpalmer I realize this doesn't necessarily help with React Router, but for me this is the same problem. Once I figure out how to actually recursively call these promises, I think it's just a matter of hooking into router's transition manager and delay based on the resolve promise.

function resolve(App, store, http, routingContext) {
  // Takes the <App/> and wraps <Provider>, etc. around it
  let component = instantiateComponent(App, store, http, routingContext)

  return new Promise(done => {
    let promises = []

    // Walk the app tree, finding static fetch() fns
    walk(component, visitor)

    // Queue up promise all at the end of the callstack
    // This allows the visitor to gobble up all the promises
    // before the time Promise.all is called
    process.nextTick(() => {
      Promise.all(promises).then(x => done(component))
    })

    function visitor(element, instance, context) {
      let promise;
      if (element.type
          && element.type.fetch
          && typeof element.type.fetch === 'function'
      ) {
        promise = element.type.fetch(element.props, store, http)
        promises.push(promise)
        // promise.then(resolved => { walk(element, visitor, context); return resolved })
        // return false
      }
    }
  })
}

To illustrate the problem here is an example (I'm hoping @ctrlplusb will have an idea 😄 ):

@connect(state => ({ data: state.stuff.data })
class App extends Component {
  static fetch(props, store) {
    // props.data === null
    return store.dispatch(loadData()) // returns promise
  }

  render() {
    return <div>
      Hello!
      {this.props.data && <SomeOtherAsync id={96} />}    {/* This one is never found in our tree! */}
      <SomeOtherAsync id={42} />  {/* this fires as expected */}
    </div>
  }
}

@connect(state => ({ data: state.things.data })
class SomeOtherAsync extends Component {
  static fetch(props, store) {
    return store.dispatch(loadSomeOtherData(props.id))
  }

  render() {
    return <div>
      {JSON.stringify(this.props.data)}
    </div>
  }
}

So I think the problem is that the props are never propagated back down the tree. In my logging, App.props.data never changes value, even after the fetch() is resolved.

The two lines I've commented out were trying to play around with pausing walking to wait for a promise as suggested in the README, though things still don't seem to pan out.

I've also tried reinstantiating the whole tree and walking from the top - however that has it's own issues such as hitting the same promises again. However, I kind of wonder if that's what is going to be necessary - and I need to figure out some way to mark them as resolved for further walking?

WangLarry commented 7 years ago

Can use async, await function when loop visit component tree?

ctrlplusb commented 7 years ago

async/await is just syntax sugar for promises, so in theory it should work fine.

edit: with v2

ctrlplusb commented 7 years ago

@jonjaques see the newly released API, also react-async-bootstrapper for an example implementation.

jonjaques commented 7 years ago

@ctrlplusb Ooohhh, does that work like I think it does too? AKA it's fully recursive?

ctrlplusb commented 7 years ago

Yep :)