Tour never starts when `run` is `true` from mount and `steps` are populated asynchronously

Author: ArmaanjeetSandhuCreated Jul 11, 2026Updated Jul 11, 2026

Describe the bug

When run is true from the very first render but steps is initially empty and populated later (e.g. fetched asynchronously), the tour silently never starts. It gets stuck at status: ready and no beacon/tooltip ever renders.

Steps to reproduce

javascript
function App() {
  const [steps, setSteps] = useState([]);

  useEffect(() => {
    fetchSteps().then(setSteps); // arrive after mount
  }, []);

  // run is hard-coded true from the first render
  return <Joyride run continuous steps={steps} />;
}

Expected: once steps is non-empty, the tour starts (status: running, tour:start fires).

Actual: the store goes idle → ready and stops there; the tour never runs.

Root cause

controls.start() is only invoked in two places: the mount effect (run && size, which is false at mount because steps is empty) and usePropSync when run changes to truthy (which never happens here, because run was already true). The useUpdateEffect in useTourEngine that handles steps-arriving-after-mount only transitions the store to ready, never to running:

javascript
useUpdateEffect(() => {
  if (run && size && status === STATUS.IDLE) {
    store.current.updateState({ status: STATUS.READY }); // stops at ready
  }
}, [run, size, status]);

react-joyride version

3.2.0

Reproduction link

No response

Screenshots or videos

No response

Additional context

The common pattern run={steps.length > 0} works, because there run transitions false → true and goes through usePropSync. Only the "run hard-coded true + async steps" combination is affected.

Source: gilbarbara/react-joyride