shepherd-pro/react-shepherd

Wrap Shepherd.js around a React application

Baspa opened this issue ยท 7 comments

Baspa commented

I want to implement the Shepherd.js package in my React application to make a guided tour. They have a React wrapper which I thought would be good to use. My application consist about a huge amount of components so I want to wrap the tour around my application so it can be used everywhere and across multiple pages. In my application I already have some Higher-Order Components like dialogs which are wrapped around my App:

class Root extends Component {
  render() {
    return (
      <MuiThemeProvider theme={theme}>
        <MuiPickersUtilsProvider utils={MomentUtils}>
          <I18nProvider>
            <LoadingProvider>
              <DeleteDialogProvider>
                <WarningDialogProvider>
                  <StateProvider>
                    <Router>
                      <App />
                    </Router>
                  </StateProvider>
                </WarningDialogProvider>
              </DeleteDialogProvider>
            </LoadingProvider>
          </I18nProvider>
        </MuiPickersUtilsProvider>
      </MuiThemeProvider>
    );
  }
}

At first I thought I could just wrap the ShepherdTour and TourMethods components around App it but then I got this error:

Warning: A context consumer was rendered with multiple children, or a
child that isn't a function. A context consumer expects a single child
that is a function. If you did pass a function, make sure there is no
trailing or leading whitespace around it.

Their example on their GitHub page looks like this:

   <ShepherdTour steps={steps} tourOptions={tourOptions}>
        <TourMethods>
            {(tourContext) => (

                <button className="button dark" onClick={tourContext.start}>
                    Start Tour
            </button>
            )}
        </TourMethods>
    </ShepherdTour>

Is it possible to make this a HOC so I can call the tourContext.start function from everywhere in my application? I am a bit stuck on how I can do this because I am not sure how to implement this well. Any ideas are welcome.

@chuckcarpenter this sounds like a logical request. Any idea what we need to do to support it?

@Baspa I need to update the readme as well, since I made a few changes to use hooks in the component, so I recommend setting it up more like what I have here: https://github.com/shipshapecode/react-shepherd/blob/master/example/src/App.js

It's a cleaner way to handle context, from what I understand of it. Let me know how that works for you.

Baspa commented

@Baspa I need to update the readme as well, since I made a few changes to use hooks in the component, so I recommend setting it up more like what I have here: https://github.com/shipshapecode/react-shepherd/blob/master/example/src/App.js

It's a cleaner way to handle context, from what I understand of it. Let me know how that works for you.

This definitely works for me now.

Baspa commented

Any idea on how I can check in the beforeShowPromise function if a compononentDidMount? Because sometimes I get the error that the element don't exist yet because the component didn't mount fast enough @chuckcarpenter

@Baspa beforeShowPromise wouldn't know much about lifecycle methods. I haven't had to do this in React, but you can use the native MutationObserver and then call the next step once the node exists in the DOM.

Baspa commented

Okay thank you, I am gonna take a look at it @chuckcarpenter

Baspa commented

I ended up with creating a function that checks if the element exist, if it doesn't it should check again after 0,5 seconds.

function waitForElementToDisplay(selector) {
    return new Promise(function (resolve) {
        (function checkIfElementExists() {
            if (document.querySelector(selector) !== null) {
                resolve();
            } else {
                setTimeout(checkIfElementExists, 500);
            }
        })();
    })
}

Then in the beforeShowPromise function I made the Promise function async so that it waits until the function is resolved:

        beforeShowPromise: () => waitForElementToDisplay('.meetingTitle'),