Wu Wei
Wu Wei

Reputation: 2297

Meteor/React: Redirect outside of component as callback of AccountsTemplates.logout()

I'm trying to integrate the meteor-useraccounts package with React in my Meteor app. I've come pretty far, but am struggling to implement a history.push() or equivalent as a callback function to the AccountsTemplate.logout() - the latter being the built in way of logging out a user.

The configuration of the callback func is done this way:

AccountsTemplates.configure({
    onLogoutHook: myLogoutFunc
})

But I can't do this within a "class" of React.Component. So I need to define callback function that uses the browser history, but is outside of any Components scope, I guess.

For the case of hitting a Sign In/Out button, I found a solution as follows:

class LogInOutButton extends Component {
    constructor(props) {
        super(props);

        this.state = {
            loggedIn: Meteor.userId() ? true : false,
            redirect: false
        }

        this.redirectToSignIn = this.redirectToSignIn.bind(this);

        window.logInOutButton = this;
    }

    redirectToSignIn() {
        if (!this.state.loggedIn && document.location.pathname !== "/signIn")
            this.setState({redirect: <Redirect to="/signIn" />});
    }

    render() {
        const { redirect } = this.state
        return (
            <li className="nav-item">
                {redirect}
                <a className="nav-link" href="#"
                    onClick={Meteor.userId() ? AccountsTemplates.logout : this.redirectToSignIn}>
                    {Meteor.userId() ? "Sign Out" : "Sign In"}
                </a>
            </li>
        )
    }
}

As you can see I, for example, tried calling the redirectToSignIn() method from outside by making it a member of the global window object. Doesn't feel right and doesn't work either:

var myLogoutFunc = () =>
    window.logInOutButton.redirectToSignIn();

I also tried the following approach:

var myLogoutFunc = withRouter(({history}) => 
    <div onLoad={history.push="/signIn"}>
    </div>
) 

However, this won't work, because withRouter needs a Component as param, if I got it right? At least this is how I try to explain the following error:

Exception in delivering result of invoking 'logout': TypeError: "props is undefined"

After reading this in the withRouter module:

var withRouter = function withRouter(Component) {
  var C = function C(props) {
     ...

How would you solve this?

Upvotes: 0

Views: 265

Answers (2)

Dom Ramirez
Dom Ramirez

Reputation: 2212

Are you just trying to redirect the user to a landing page when they log out, are logged out or when they come to the app/site while not logged in? I often do this a few different ways depending on the complexity of my app/routes/permissions/etc.

I tend to use iron:router and Blaze, not React, but I think the most straightforward solution is easy to translate. I drop this somewhere at the top level of my client-side code:

Tracker.autorun(function() {
    const userDoc = Meteor.user();
    const loggingIn = Meteor.loggingIn();

    if(!userDoc && !loggingIn) {
        Router.go('/'); // Or comparable redirect command
    }
});

On page load: If the userDoc is not defined (meaning the user is not logged in successfully) and loggingIn is not true (meaning Meteor is not in the process of logging a user in on page load), we'll redirect the user to the home page.

On log out: Since userDoc and loggingIn are both reactive data sources, this autorun function will rerun any time either of those variables change. So, when the user logs out by any means, userDoc will cease to be defined, the conditional will resolve to true and it'll redirect the user to the home page.

On log in: If a logged-out user logs in or begins to log in, nothing will happen. The function will re-run, but forcing an automatic redirect any time the userDoc or loggingIn variable change can become more complicated.

Upvotes: 1

Wu Wei
Wu Wei

Reputation: 2297

To be honest, I abandoned the idea of using a package for creating an UI for the account system.

The package meteor-useraccounts was made for Blaze, and although in the official Meteor docs they encourage wrapping the {{> atForm }} template which renders the forms for Sign In, Sign Up, etc. into a React.Component, I found it to cause incompatibilities especially with handling the routes. (I had used the package gadicc:blaze-react-component for wrapping.) In the end I found myself trying to understand this black box, that was made up of multiple layers, incl. another one for bootstrap4 pre-styling. For example I also couldn't make the email verification work.

So I decided to dump it all, and started building the whole UI and logic myself on top of the Meteor Accounts and Meteor Passwords API.

Now redirecting is darn simple, because I can do it within my own React.Component. The only thing you need to do is import { withRouter } from 'react-router';, then export default withRouter(LogInOutButton); and finally you can use this.props.history.push("/signIn") to redirect:

import React, { Component } from 'react';
import { withRouter } from 'react-router';

class LogInOutButton extends Component {
    constructor(props) {
        super(props);

        this.state = {
            loggedIn: Meteor.userId() ? true : false,
        }

        this.redirectToSignIn = this.redirectToSignIn.bind(this);
    }

    redirectToSignIn() {
        if (this.state.loggedIn)
            Meteor.logout()
        if (document.location.pathname !== "/signIn")
            this.props.history.push("/signIn")
    }

    render() {
        return (
            <li className="nav-item">
                <a className="nav-link" href="#"
                    onClick={this.redirectToSignIn}>
                    {Meteor.userId() ? "Sign Out" : "Sign In"}
                </a>
            </li>
        )
    }
}

export default withRouter(LogInOutButton);

PS: (Just my opinion) Of course there a things to solve when building your own UI/Logic for the account system. For example form validation. (I decided to built on top of the HTML5 validation, but add my own visual feedback with bootstrap4.)

I guess in the case of Meteor and React there is simply no working package at the moment. I tried them all, believe me. They were all a pain in the ass (when trying to integrate them with React) or not customizable enough. If I'd spent the time to build my own from the start, I'd long be finished by now. And the great thing about doing it yourself is, you know how it works and can easily refine and reuse it later.

It also helps keep dependencies low, which I find crucial, because the Meteor project has been progressing and changing rapidly in the last years. It's easier to keep your project up to date, when you know what you did.

Upvotes: 0

Related Questions