Understanding props of a wrapped component

Viewed 24

I am confused about the export magic in the following code from this tutorial (available at GitHub):

import React, { Component } from 'react';
import withFirebaseAuth from 'react-with-firebase-auth'
import firebase from 'firebase/app';
import 'firebase/auth';
import firebaseConfig from './firebaseConfig';
import logo from './logo.svg';
import './App.css';

const firebaseApp = firebase.initializeApp(firebaseConfig);

class App extends Component {
  render() {
    
    const {
      user,
      signOut,
      signInWithGoogle,
    } = this.props;

    if (user) {
      console.log(user.uid);
    }

    return (
      <div className="App">
        <header className="App-header">
          <img src={logo} className="App-logo" alt="logo" />
          {
            user
              ? <p>Hello, {user.displayName}</p>
              : <p>Please sign in.</p>
          }

          {
            user
              ? <button onClick={signOut}>Sign out</button>
              : <button onClick={signInWithGoogle}>Sign in with Google</button>
          }
        </header>
      </div>
    );
  }
}

const firebaseAppAuth = firebaseApp.auth();

const providers = {
  googleProvider: new firebase.auth.GoogleAuthProvider(),
};

export default withFirebaseAuth({
  providers,
  firebaseAppAuth,
})(App);

I do not understand (1) how come App.props has new members and (2) how come these new members are not undefined at the beginning of App.render(). In more detail:

  1. According to the documentation, I understand that the call to withFireBaseAuth() in export returns a function, which, when called with App as an argument, returns a component. However, it is a new component. So, how come props get added to App?
  2. The call to withFireBaseAuth() appears after the definition of App, so how come App.props.user is not undefined when it is used in App.render()?
1 Answers
  1. According to the documentation, I understand that the call to withFireBaseAuth() in export returns a function, which, when called with App as an argument, returns a component. However, it is a new component. So, how come props get added to App?

withFirebaseAuth is a Higher Order Component, which is really just a specialized higher order function that consumes a React component, imbues it with extra behavior (props, etc...) and returns a new Component.

A very simple HOC may look like this:

const withMyHOC = Component => props => (
  <Component {...props} extraProp="extraProp" />
);

and to use, const MyComponentWithExtraProp = withMyHOC(MyComponent);.

The trick here is that HOCs return React components, i.e. they receive props, and then pass them forward (spread operator) to the component being decorated.

HOCs can also be written to take additional arguments.

const withMyHOC = (arg1, arg2) => Component => props => {
  useEffect(() => {
    console.log('rendered', arg2);
  });
  return (
    <Component {...props} extraProp="extraProp" arg={arg1} />
  )
};

and to use, const MyComponentWithExtraProp = withMyHOC("test")(MyComponent);.

withFirebaseAuth appears to take a configuration argument, create the auth provider, and inject user, signOut, and signInWithGoogle as props into the component being decorated, e.g. App.

  1. The call to withFireBaseAuth() appears after the definition of App, so how come App.props.user is not undefined when it is used in App.render()?

Remember, these are just the component declarations. App component is declared in the file, and then decorated with withFirebaseAuth and then default exported.

export default withFirebaseAuth({
  providers,
  firebaseAppAuth,
})(App);

Elsewhere App (really the decorated one) is default imported and rendered by React.

import App from '../path/to/app';

ReactDOM.render(
  <App />,
  document.getElementById("root")
);

Notice here that we don't need to pass any props, but that user, signOut, and signInWithGoogle will be injected by withFirebaseAuth.

Related