Implement Facebook API login with reactjs

57,984

Solution 1

I have figured out how to modify the Facebook tutorial for the Login API with ReactJS. I hope this helps anyone else struggling with this.

Firstly, in the react component where you want the Login link, include this code:

componentDidMount: function() {
  window.fbAsyncInit = function() {
    FB.init({
      appId      : '<YOUR_APP_ID>',
      cookie     : true,  // enable cookies to allow the server to access
                        // the session
      xfbml      : true,  // parse social plugins on this page
      version    : 'v2.1' // use version 2.1
    });

    // Now that we've initialized the JavaScript SDK, we call
    // FB.getLoginStatus().  This function gets the state of the
    // person visiting this page and can return one of three states to
    // the callback you provide.  They can be:
    //
    // 1. Logged into your app ('connected')
    // 2. Logged into Facebook, but not your app ('not_authorized')
    // 3. Not logged into Facebook and can't tell if they are logged into
    //    your app or not.
    //
    // These three cases are handled in the callback function.
    FB.getLoginStatus(function(response) {
      this.statusChangeCallback(response);
    }.bind(this));
  }.bind(this);

  // Load the SDK asynchronously
  (function(d, s, id) {
    var js, fjs = d.getElementsByTagName(s)[0];
    if (d.getElementById(id)) return;
    js = d.createElement(s); js.id = id;
    js.src = "//connect.facebook.net/en_US/sdk.js";
    fjs.parentNode.insertBefore(js, fjs);
  }(document, 'script', 'facebook-jssdk'));
},

// Here we run a very simple test of the Graph API after login is
// successful.  See statusChangeCallback() for when this call is made.
testAPI: function() {
  console.log('Welcome!  Fetching your information.... ');
  FB.api('/me', function(response) {
  console.log('Successful login for: ' + response.name);
  document.getElementById('status').innerHTML =
    'Thanks for logging in, ' + response.name + '!';
  });
},

// This is called with the results from from FB.getLoginStatus().
statusChangeCallback: function(response) {
  console.log('statusChangeCallback');
  console.log(response);
  // The response object is returned with a status field that lets the
  // app know the current login status of the person.
  // Full docs on the response object can be found in the documentation
  // for FB.getLoginStatus().
  if (response.status === 'connected') {
    // Logged into your app and Facebook.
    this.testAPI();
  } else if (response.status === 'not_authorized') {
    // The person is logged into Facebook, but not your app.
    document.getElementById('status').innerHTML = 'Please log ' +
      'into this app.';
  } else {
    // The person is not logged into Facebook, so we're not sure if
    // they are logged into this app or not.
    document.getElementById('status').innerHTML = 'Please log ' +
    'into Facebook.';
  }
},

// This function is called when someone finishes with the Login
// Button.  See the onlogin handler attached to it in the sample
// code below.
checkLoginState: function() {
  FB.getLoginStatus(function(response) {
    this.statusChangeCallback(response);
  }.bind(this));
},

handleClick: function() {
  FB.login(this.checkLoginState());
},

Then, in your render method, make sure you have some HTML that will call that handleClick:

<a href="#" onClick={this.handleClick}>Login</a>

Note, this is the same code from the tutorial, but placed in a ReactJS component. The only difference is that you have to bind this strategically to make the Facebook API functions part of your react component. This login will finish with a response message parsed from the response given by FB.getLoginStatus(). You can also take the token out of that response object and send it to your backend for authentication with something like passport-facebook-token.

Solution 2

I used Promises for Facebook Auth flow

mixins/facebook.js

const promises = {
    init: () => {
        return new Promise((resolve, reject) => {
            if (typeof FB !== 'undefined') {
                resolve();
            } else {
                window.fbAsyncInit = () => {
                    FB.init({
                        appId      : '<app_id>',
                        cookie     : true, 
                        xfbml      : true,  
                        version    : 'v2.5'
                    });
                    resolve();
                };
                (function(d, s, id) {
                    var js, fjs = d.getElementsByTagName(s)[0];
                    if (d.getElementById(id)) return;
                    js = d.createElement(s); js.id = id;
                    js.src = "//connect.facebook.net/en_US/sdk.js";
                    fjs.parentNode.insertBefore(js, fjs);
                }(document, 'script', 'facebook-jssdk'));
            }
        });
    },
    checkLoginState: () => {
        return new Promise((resolve, reject) => {
            FB.getLoginStatus((response) => {
                response.status === 'connected' ? resolve(response) : reject(response);
            });
        });
    },
    login: () => {
        return new Promise((resolve, reject) => {
            FB.login((response) => {
                response.status === 'connected' ? resolve(response) : reject(response);
            });
        });
    },
    logout: () => {
        return new Promise((resolve, reject) => {
            FB.logout((response) => {
                response.authResponse ? resolve(response) : reject(response);
            });
        });
    },
    fetch: () => {
        return new Promise((resolve, reject) => {
            FB.api(
                '/me', 
                {fields: 'first_name, last_name, gender'},
                response => response.error ? reject(response) : resolve(response)
            );
        });
    }
}

export const Facebook = {
    doLogin() {
        this.setState({
            loading: true
        }, () => {
            promises.init()
                .then(
                    promises.checkLoginState,
                    error => { throw error; }
                )
                .then(
                    response => { this.setState({status: response.status}); },
                    promises.login
                )
                .then(
                    promises.fetch,
                    error => { throw error; }
                )
                .then(
                    response => { this.setState({loading: false, data: response, status: 'connected'}); },
                    error => { throw error; }
                )
                .catch((error) => { 
                    this.setState({loading: false, data: {}, status: 'unknown'});
                    console.warn(error); 
                });
        });
    },
    doLogout() {
        this.setState({
            loading: true
        }, () => {
            promises.init()
                .then(
                    promises.checkLoginState,
                    error => { throw error; }
                )
                .then(
                    promises.logout,
                    error => { this.setState({data: {}, status: 'unknown'}); }
                )
                .then(
                    response => { this.setState({loading: false, data: {}, status: 'unknown'}); },
                    error => { throw error; }
                )
                .catch(error => { 
                    this.setState({loading: false, data: {}, status: 'unknown'});
                    console.warn(error); 
                });
        });
    },
    checkStatus() {
        this.setState({
            loading: true
        }, () => {
            promises.init()
                .then(
                    promises.checkLoginState,
                    error => { throw error; }
                )
                .then(
                    response => { this.setState({status: response.status}); },
                    error => { throw error; }
                )
                .then(
                    promises.fetchUser,
                    error => { throw error; }
                )
                .then(
                    response => { this.setState({loading: false, data: response, status: 'connected'}); },
                    error => { throw error; }
                )
                .catch((error) => { 
                    this.setState({loading: false, data: {}, status: 'unknown'});
                    console.warn(error); 
                });
        });
    }
};

Profile.jsx

import {Facebook} from './mixins/Facebook.js';
import {Button} from 'react-bootstrap';

const ProfileHandler = React.createClass({
    mixins: [Facebook],
    componentDidMount() {
        this.checkStatus();
    },
    getInitialState() {
        return {
            status: 'unknown',
            loading: false,
            data: {}
        };
    },
    render() {
        const loading = this.state.loading ? <p>Please wait, profile is loading ...</p> : null;
        const message = this.state.status === 'connected'
            ? (<div>
                Hi {data.name}!
                <Button onClick={this.doLogout}>Logout</Button>
              </div>)
            : (<Button onClick={this.doLogin}>Login</Button>);
        return (
            <div>
                {message}
                {loading}
            </div>
        );
    }
});

Solution 3

ritmatter gave a good answer, but I'll show how I did this a little differently. I wanted to use Facebook's login button rather than my own to trigger the callback for checking login state. The login button might look like this:

<div class="fb-login-button" onlogin="checkLoginState" data-size="medium" data-show-faces="false" data-auto-logout-link="false"></div>

The button has an onlogin attribute, which the jsx parser doesn't support. Using data-onlogin in the react fashion wasn't working, so I just added the button in componentDidMount:

        componentWillMount: function () {
                window['statusChangeCallback'] = this.statusChangeCallback;
                window['checkLoginState'] = this.checkLoginState;
        },
        componentDidMount: function () {
            var s = '<div class="fb-login-button" ' +
                'data-scope="public_profile,email" data-size="large" ' +
                'data-show-faces="false" data-auto-logout-link="true" ' +
                'onlogin="checkLoginState"></div>';

            var div = document.getElementById('social-login-button-facebook')
            div.innerHTML = s;
        },
        componentWillUnmount: function () {
            delete window['statusChangeCallback'];
            delete window['checkLoginState'];
        },
        statusChangeCallback: function(response) {
           console.log(response);
        },
        // Callback for Facebook login button
        checkLoginState: function() {
            console.log('checking login state...');
            FB.getLoginStatus(function(response) {
               statusChangeCallback(response);
            });
        },
        render: function() {

            return (
                <div id='social-login-button-facebook'>

                </div>
            );
        }

All that's left to do is trigger the button upon mounting the component if you want to automatically call checkLoginState.

Solution 4

I post my solution here, as the answers helped me to implement it, but the accepted answer from ritmatter did not work without the

FB.Event.subscribe('auth.statusChange', function(response)

statement from RubyFanatic's answer. Furthermore the "onlogin" confused me a lot. It is simply not required in my eyes.

I want to share it, as this is a c&p solution, here my full code against fb api v2.8 (react-bootstrap can be kicked of course):

/* global FB*/
import React, { Component } from 'react';
import { Grid, Row, Col } from 'react-bootstrap';

export default class Login extends Component {
  constructor(props) {
    super(props);
    this.checkLoginState = this.checkLoginState.bind(this);
    this.handleClick = this.handleClick.bind(this);
    this.testAPI = this.testAPI.bind(this);
    this.statusChangeCallback = this.statusChangeCallback.bind(this);
  }

  componentDidMount() {
    window.fbAsyncInit = function() {
      FB.init({
        appId      : 'YOURAPIKEYHERE',
        cookie     : true,
        xfbml      : true,
        version    : 'v2.8'
      });
      FB.AppEvents.logPageView();
      FB.Event.subscribe('auth.statusChange', function(response) {
        if (response.authResponse) {
          this.checkLoginState();
        } else {
          console.log('---->User cancelled login or did not fully authorize.');
        }
      }.bind(this));
    }.bind(this);

    // Load the SDK asynchronously
    (function(d, s, id) {
      var js, fjs = d.getElementsByTagName(s)[0];
      if (d.getElementById(id)) return;
      js = d.createElement(s); js.id = id;
      js.src = '//connect.facebook.net/en_US/sdk.js';
      fjs.parentNode.insertBefore(js, fjs);
    }(document, 'script', 'facebook-jssdk'));
  }

  // Here we run a very simple test of the Graph API after login is
  // successful.  See statusChangeCallback() for when this call is made.
  testAPI() {
    console.log('Welcome! Fetching your information.... ');
    FB.api('/me', function(response) {
      console.log('Successful login for: ' + response.name);
      document.getElementById('status').innerHTML =
        'Thanks for logging in, ' + response.name + '!';
    });
  }

  // This is called with the results from from FB.getLoginStatus().
  statusChangeCallback(response) {
    if (response.status === 'connected') {
      // Logged into your app and Facebook.
      this.testAPI();
    } else if (response.status === 'not_authorized') {
      // The person is logged into Facebook, but not your app.
      document.getElementById('status').innerHTML = 'Please log ' +
        'into this app.';
    } else {
      // The person is not logged into Facebook, so we're not sure if
      // they are logged into this app or not.
      document.getElementById('status').innerHTML = 'Please log ' +
      'into Facebook.';
    }
  }

  checkLoginState() {
    FB.getLoginStatus(function(response) {
      this.statusChangeCallback(response);
    }.bind(this));
  }

  handleClick() {
    FB.login(this.checkLoginState());
  }

  render() {
    return (
      <main>
        <Grid fluid>
            <h1>
              Facebook Login
            </h1>
        </Grid>
        <Grid>
          <Row>
            <Col xs={12}>
              <a href="#" onClick={this.handleClick} onlogin={this.checkLoginState}>Login</a>
              <div id="status"></div>
            </Col>
          </Row>
        </Grid>
      </main>
    );
  }
}

Solution 5

I was recently working on Facebook Login for one of my project, So after going though many Stackoverflow answers and blog posts. I wrote a minimilastic code using ES6 FatArrow Function Expression (excluding .bind(this)). Hope this code helps you.

https://gist.github.com/ronit-mukherjee/3e933509643a4ab4e80452bb05c1a073

/*global FB*/
import React, {
  Component
} from 'react';

class FacebookLoginButton extends Component {
  componentDidMount() {
    (function(d, s, id) {
      var js, fjs = d.getElementsByTagName(s)[0];
      if (d.getElementById(id)) {
        return;
      }
      js = d.createElement(s);
      js.id = id;
      js.src = "https://connect.facebook.net/en_US/sdk.js";
      fjs.parentNode.insertBefore(js, fjs);
    }(document, 'script', 'facebook-jssdk'));


    window.fbAsyncInit = () => {
      FB.init({
        appId: '9999999999999999', //Change with your Facebook app id
        autoLogAppEvents: true,
        xfbml: true,
        version: 'v3.0'
      });

      FB.Event.subscribe('auth.statusChange', response => {
        if (response.authResponse) {
          this.checkLoginState();
        } else {
          console.log('[FacebookLoginButton] User cancelled login or did not fully authorize.');
        }
      });
    };
  }

  checkLoginState() {
    FB.getLoginStatus(function(response) {
      this.statusChangeCallback(response);
    }.bind(this));
  }

  login() {
    FB.login(this.checkLoginState(), {
      scope: 'email'
    });
  }

  statusChangeCallback(response) {
    if (response.status === 'connected') {
      this.testAPI();
    } else if (response.status === 'not_authorized') {
      console.log("[FacebookLoginButton] Person is logged into Facebook but not your app");
    } else {
      console.log("[FacebookLoginButton] Person is not logged into Facebook");
    }
  }

  testAPI() {
    FB.api('/me', function(response) {
      console.log('[FacebookLoginButton] Successful login for: ', response);
    });
  }

  render() {
    return ( <
      button className = "btn btn-block btn-fb"
      onClick = {
        () => this.login()
      } >
      <
      i className = "fa fa-facebook" / > Connect with Facebook <
      /button>
    )
  }
}

export default FacebookLoginButton;
Share:
57,984

Related videos on Youtube

ritmatter
Author by

ritmatter

Currently a Software Engineer on Cloud IoT Core, Google's service for managing and ingesting data from fleets of devices. I build features and infrastructure for the platform (C++). Previously, I was a Software Engineer on the Google Docs, Sheets, and Slides family, developing features for the suite. Skills include: -Backend API Dev (C++ and Java): From current experience on Cloud IoT Core and previous experience with end-to-end features on Docs. -Frontend Web Dev (JavaScript): From experience on Docs web client -Managing Production Software: From experience monitoring production jobs and improving deployment processes.

Updated on March 25, 2022

Comments

  • ritmatter
    ritmatter over 2 years

    I'm working on using Facebook's Javascript SDK for authentication. I've been able to import the SDK properly and put a Like button on my page. But, the facebook login button has to be wrapped in the tag:

    <fb:login-button/>
    

    I currently have all of the code from the Facebook Login tutorial pasted into my index.html, the only html file in my project, which houses the React application. But, I need to put the last part, which has the actual login button, into my React component. When I tried to do that, I got the following error:

    ReactifyError: /Users/ritmatter/reps/js/components/Signup.jsx: Parse Error: Line 82: Unexpected end of input while parsing file: /Users/ritmatter/reps/js/components/Signup.jsx
    sdk.js:61 The "fb-root" div has not been created, auto-creating
    ping?client_id=894010190618709&domain=localhost&origin=1&redirect_uri=http%3A%2F%2Fstatic.ak.facebo…:1 Given URL is not allowed by the Application configuration.: One or more of the given URLs is not allowed by the App's settings.  It must match the Website URL or Canvas URL, or the domain must be a subdomain of one of the App's domains.
    

    How can I get the login button into react?

  • Shih-Min Lee
    Shih-Min Lee about 9 years
    it's working but shouldn't you be doing the auth and stuff from the action creators?
  • Chris G.
    Chris G. almost 9 years
    I am getting: Uncaught TypeError: Cannot read property 'parentNode' of undefined. Any idea? In you question you write "been able to import the SDK properly" do you need to do that and how? Thanks
  • user3430761
    user3430761 almost 9 years
    @ChrisG. Did you find a solution to your problem?
  • ritmatter
    ritmatter almost 9 years
    @Shih-MinLee you make a good point, but I only mean to demonstrate a very stark example of ReactJS with the Facebook API. If you were building a more robust application with Flux architecture, then you would want to move a lot of this logic elsewhere
  • Chris G.
    Chris G. almost 9 years
    Hi user3430761. Nope I did not solve this. I use ES6 code and I am still getting "Uncaught TypeError: Cannot read property 'parentNode' of undefined."
  • WitVault
    WitVault almost 8 years
    There is slight delay in display of facebook login button using this approach. I think loading of facebook api should be moved inside script tag after body.
  • gr3g
    gr3g over 7 years
    I think there's an error : FB.login returns the loginStatus, but you ignore this callback and call getLoginStatus again.
  • Goutham
    Goutham about 7 years
    I tried this module , it is working perfectly while in desktop but callback function is not called while in mobile.
  • martin36
    martin36 almost 7 years
    Thanks! @RubyFanatic This worked for me. But if I need to have the facebook SDK defined in all my components, so that if the user presses 'F5' and updates the page on a page which does not include the "Login" component, the facebook SDK is still defined. Do I need to the init code in all my components or is it a way to only initialize it once?
  • Onno Faber
    Onno Faber over 6 years
    Change FB. to window.FB.
  • metamorph_online
    metamorph_online over 6 years
    Man, you saved me lots of time struggling this:))) I changed Facebook to Component though and all the references from global to "this" but your logic worked as magic!. Thanks a lot!
  • dkonayuki
    dkonayuki over 6 years
    It should be FB.login(this.checkLoginState); , otherwise checkLoginState will be executed prematurely.
  • GA1
    GA1 over 6 years
    This does not work with the official fb login butotn developers.facebook.com/docs/plugins/like-button#configurato‌​r even though it works with a custom button as in the example. Any idea?
  • francis
    francis almost 3 years
    These default props has disableMobileRedirect: false and in the code there's a condition (isMobile && !disableMobileRedirect) to use http redirect or call window.FB.login.
  • Paul Serre
    Paul Serre almost 3 years
    Thanks, really clean indeed.