veliovgroup / flow-router

🚦 Carefully extended flow-router for Meteor
https://packosphere.com/ostrio/flow-router-extra
BSD 3-Clause "New" or "Revised" License
201 stars 30 forks source link

add Route Changing reactive source? #88

Closed crapthings closed 3 years ago

crapthings commented 3 years ago

do we have such status, so we can check this status to prevent watching further watchpathchange reactive source that call react setState?

when u work with ui framework like react, you build a tracker container no matter a react class componet or functional component with useeffect hook, you start a tracker in it and you track flowrouter's reactive source like watchpathchange, getparams etc, then after you invoke flowrouter.go, this will lead container's tracker rerun Immediately then call react setState, but meanwhile this container will recieve undefine props from route state and pass down to child component will cause crash before next flowrouter's action call a reactdom.render.

i will post a repo to reproduce.

and wonder how people solve such problem in normal?

crapthings commented 3 years ago
useEffect(function () {
  const comp = Tracker.autorun(function () {
    const keyword = flowrouter.getQuery('keyword')
if (!keyword) return // i want to replace here to watch if route is not changing
   setState(keyword)
  })

  return function () {
    comp?.stop()
  }
}, [])
dr-dimitru commented 3 years ago

Hello @crapthings,

It's solved either with wrapping reactive code into Tracker.nonreactive or accessing non-reactive state FlowRouter._current.route. And checking if state returns undefined props to avoid any further action.

Maybe @afrokick can tell you more, I know they are using FR with react a lot

afrokick commented 3 years ago

Yeah, we had the same issue. We decided to use Tracker.nonreactive.

//our custom hook
const useParam = pName => Tracker.nonreactive(() => FlowRouter.getParam(pName));

function MyPage() {
   const someNonReactiveParam = useParam('p');

  ...
}

If we want to handle params changes, just pass it to component via route definition in action function. Another solution to use custom hook like useReactiveParam.

function MyPage2({ someReactiveParam }) {
  ...
}

FlowRouter.route('/somepath', {
  action({ pName }) {
    render(<MyPage2 someReactiveParam={pName}/>);
  },
crapthings commented 3 years ago

https://github.com/crapthings/fr88

image

{path: "/", params: {…}, route: Route, context: Context, oldRoute: Route, …} undefined
page2 unmount
home mount
{path: "/", params: {…}, route: Route, context: Context, oldRoute: Route, …} undefined

i'm about to leave page2 to the home page, and you can see that an tracker rerun happend and it tracks the next route state.

yes i do use pass props to react component, but what i try to achieve is something like

image

the container don't use react context to pass down deep props, it accesses route state directly with tracker

crapthings commented 3 years ago

something like this

export default function () {
  useEffect(function () {
    console.log('page2 mount')
    const trackerHandler = Tracker.autorun(function () {
      if (FlowRouter.changingRoute()) return
      console.log(FlowRouter.current(), FlowRouter.getQueryParam('something'))
    })

    return function () {
      console.log('page2 unmount')
      trackerHandler?.stop()
    }
  }, [])
  return (
    <div>page2</div>
  )
}

meteor account has such reactive source that know a state of login

https://docs.meteor.com/api/accounts.html#Meteor-loggingIn

afrokick commented 3 years ago
export default function () {
  useEffect(function () {
    console.log('home mount')
    const trackerHandler = Tracker.nonreactive(function () {
      console.log(FlowRouter.current(), FlowRouter.getQueryParam('something'))
    })

    return function () {
      console.log('home unmount')
      trackerHandler?.stop()
    }
  }, [])
  return (
    <div>home</div>
  )
}
crapthings commented 3 years ago

@afrokick

FlowRouter.setQueryParams({ something: 111111 })
FlowRouter.go('/page1?something=111111122222')

but these methods will failed if we stay same page

crapthings commented 3 years ago

it looks useLayoutEffect works

export default function () {
  // const mounted = useRef(false)

  useLayoutEffect(function () {
    console.log('page1 mount')
    const trackerHandler = Tracker.nonreactive(function () {
      return Tracker.autorun(function () {
        // if (mounted.current === false) return
        console.log('page1 inside tracker', FlowRouter._current, FlowRouter.getQueryParam('something'))
      })
    })

    return function () {
      // mounted.current = false
      console.log('page1 unmount')
      trackerHandler?.stop()
    }
  }, [])

  return (
    <div>page1</div>
  )
}
afrokick commented 3 years ago

If you want to handle query changes, use action params:

export default function (props) {
  console.log(`props:`, props);

  useEffect(function () {
    console.log('page1 mount')

    return function () {
      console.log('page1 unmount')
    }
  }, [])
  return (
    <div>page1</div>
  )
}

FlowRouter.route('/page1', {
  action (_,q) {
    render(<Layout><Page1 something={q.something} /></Layout>, ReactRoot)
  }
})
crapthings commented 3 years ago

@afrokick i do use these in some case, but what i want is i don't use props pass down, or react createContext provider, i want to use Meteor's reactive source, and make an react wrapper component to subscribe state from reactive dict, var, minimongo etc...

thanks useLayoutEffect works like componentDidMount after i read react document again.

the issue only happen in hook component.