Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

React Context API + withRouter - can we use them together?

I built a large application where a single button on the navbar opens a modal.

I'm keeping track of the modalOpen state using context API.

So, user clicks button on navbar. Modal Opens. Modal has container called QuoteCalculator.

QuoteCalculator looks as follows:

class QuoteCalculator extends React.Component {
    static contextType = ModalContext;
    // ...

    onSubmit = () => {
        // ...
        this.context.toggleModal();
        this.props.history.push('/quote');
        // ..
    };

    render() {
       //...
       return(<Question {...props} next={this.onSubmit} />;)
    }
}

export default withRouter(QuoteCalculator);

Now, everything works as expected. When the user submits, I go to the right route. I just see the following warning on the console

index.js:1446 Warning: withRouter(QuoteCalculator): Function components do not support contextType.

I'm tempted to ignore the warning, but I don't think its a good idea.

I tried using Redirect alternatively. So something like

QuoteCalculator looks as follows:

    class QuoteCalculator extends React.Component {
        static contextType = ModalContext;
        // ...

        onSubmit = () => {
            // ...
            this.context.toggleModal();
            this.setState({done: true});
            // ..
        };

        render() {
           let toDisplay;
           if(this.state.done) {
               toDisplay = <Redirect to="/quote"/>
            } else {
               toDipslay = <Question {...props} next={this.onSubmit} />;
            }
           return(<>{toDisplay}</>)
        }
    }

    export default QuoteCalculator;

The problem with this approach is that I kept on getting the error

You tried to redirect to the same route you're currently on

Also, I'd rather not use this approach, just because then I'd have to undo the state done (otherwise when user clicks button again, done is true, and we'll just get redirected) ...

Any idea whats going on with withRouter and history.push?

Here's my app

class App extends Component {
    render() {
        return (
            <Layout>
                <Switch>
                    <Route path="/quote" component={Quote} />
                    <Route path="/pricing" component={Pricing} />
                    <Route path="/about" component={About} />
                    <Route path="/faq" component={FAQ} />
                    <Route path="/" exact component={Home} />
                    <Redirect to="/" />
                </Switch>
            </Layout>
        );
    }
}
like image 466
Asool Avatar asked Oct 16 '22 08:10

Asool


1 Answers

Source of the warning

Unlike most higher order components, withRouter is wrapping the component you pass inside a functional component instead of a class component. But it's still calling hoistStatics, which is taking your contextType static and moving it to the function component returned by withRouter. That should usually be fine, but you've found an instance where it's not. You can check the repo code for more details, but it's short so I'm just going to drop the relevant lines here for you:

function withRouter(Component) {
  // this is a functional component
  const C = props => {
    const { wrappedComponentRef, ...remainingProps } = props;

    return (
      <Route
        children={routeComponentProps => (
          <Component
            {...remainingProps}
            {...routeComponentProps}
            ref={wrappedComponentRef}
          />
        )}
      />
    );
  };

  // ...
  // hoistStatics moves statics from Component to C
  return hoistStatics(C, Component);
}

It really shouldn't negatively impact anything. Your context will still work and will just be ignored on the wrapping component returned from withRouter. However, it's not difficult to alter things to remove that problem.

Possible Solutions

Simplest

Since all you need in your modal is history.push, you could just pass that as a prop from the modal's parent component. Given the setup you described, I'm guessing the modal is included in one place in the app, fairly high up in the component tree. If the component that includes your modal is already a Route component, then it has access to history and can just pass push along to the modal. If it's not, then wrap the parent component in withRouter to get access to the router props.

Not bad

You could also make your modal component a simple wrapper around your modal content/functionality, using the ModalContext.Consumer component to pass the needed context down as props instead of using contextType.

const Modal = () => (
  <ModalContext.Consumer>
    {value => <ModalContent {...value} />}
  </ModalContext.Consumer>
)

class ModalContent extends React.Component {
  onSubmit = () => {
    // ...
    this.props.toggleModal()
    this.props.history.push('/quote')
   // ..
  }

  // ...
}
like image 78
shadymoses Avatar answered Oct 21 '22 11:10

shadymoses