React - Call class function from another component - 'Not a function' - reactjs

I have an 'Auth' class with a 'signIn()' function that I'm trying to call from a 'Login' component, that returns the error 'not a function' when it attempts to use the callback in 'Login'.
I've tried referring to the Auth class/service via use of a prop (like this.props.auth.signIn()), and directly as via a call to the class in the Login component (like this.auth), but both result in the same error.
I'm trying to follow the method used in the official documentation for Auth0's react samples at https://github.com/auth0-samples/auth0-react-samples/blob/embedded-login/02-Custom-Login-Form/src/Login/Login.js.
Auth.js:
import auth0 from 'auth0-js';
import { auth_config } from './auth0-config';
import createHistory from 'history';
export default class Auth {
auth0 = new auth0.WebAuth({
...
})
constructor() {
this.signIn = this.signIn.bind(this);
}
signIn(username, password) {
this.auth0.login(
{
realm: auth_config.dbConnectionName,
username,
password
},
(err, authResult) => {
if (err) {
console.error('Authentication error at \'services/auth/Auth.js\' - signIn() method:', err);
alert(`Error: ${err.description}. Check the console for further details.`);
}
}
)
}
Login.js:
import React from 'react';
import ReactDOM from 'react-dom';
import Auth from 'services/auth/Auth.js';
import { Dimmer, Modal, Header, Form, Button } from 'semantic-ui-react';
export default class LoginComponent extends React.Component {
constructor() {
super();
this.state = {
showModal: true,
username: null,
password: null,
authorised: false
}
}
componentDidMount() {
console.log('Login.props:', this.props);
}
hide = () => {
console.log('LoginComponent.hide()');
this.setState({
showModal: false
})
}
getLoginCredentials() {
return {
email: ReactDOM.findDOMNode(this.refs.email).value,
password: ReactDOM.findDOMNode(this.refs.password).value
}
}
signIn() {
// console.log('LoginComponent.signIn(\'' + this.state.username + '\', \'' + this.state.password + '\')');
const user = this.getLoginCredentials();
this.props.auth.signIn(user.email, user.password);
}

You will have to create the object of the class and then using that object you can call the function inside your Login.js,
signIn() {
const user = this.getLoginCredentials();
let signinObj = new Auth();
signinObj.signIn(user.email, user.password);
}
Edit: In the github page, in router.js file you can see the statment,
<Route path="/login" render={(props) => <Login auth={auth} {...props} />} />
auth is the object of this class which is passed to Login class as props.

Related

React Hooks: How can I turn my AuthUserRole (Role Based Auth) Component (HOC) into a React Hook?

My Goal:
I have a component AuthUserRole.tsx that accepts a component and and a userRole (Admin, Manager, Employee). If the userRole is allowed, it returns the wrapped component. If the userRole is NOT allowed, the HOC redirects the user to the Home Screen. This all works.
My Question:
Is it possible/more efficient/more modern of a practice to convert this HOC into a Custom React Hook? Below is how the current AuthUserRole HOC works + the AuthUserRole component.
How AuthUserRole.tsx Is Used:
// Imports: Components
import Account from './pages/account/Account';
// Restricted Routes
const RestrictedAccount = AuthUserRole(Account, ['Admin', 'Manager', 'Employee']);
// React App
const App: React.FC = (): JSX.Element => {
return (
<div className="App">
<Route path="/account" component={RestrictedAccount} />
</div>
);
};
AuthUserRole.tsx:
// Imports: Dependencies
import React, { useEffect } from 'react';
import { Redirect, RouteProps } from 'react-router-dom';
// import { useSelector } from 'react-redux';
// Imports: TypeScript Types
import { ReduxState, UserRole } from '../../types/types';
// Component: Auth (User Role)
const AuthUserRole = (WrappedComponent: React.FC<RouteProps>, allowedRoles: Array<UserRole>) => {
// With Auth User Role
return class WithAuthUserRole extends React.Component {
// FIX LATER: HARDCODED FOR NOW
state = {
user: {
role: 'Admin',
// role: 'Manager',
// role: 'Employee',
},
};
render() {
// User Role
const { role } = this.state.user;
// Allowed Route
if (allowedRoles.includes(role as UserRole)) {
return <WrappedComponent {...this.props} />;
}
// Forbidden Route
else {
return (
<Redirect
to={{
pathname: '/',
state: {
// from: props.location,
error: 'Forbidden: Not Authorized',
}
}}
/>
);
}
}
};
};
// Exports
export default AuthUserRole;

Can I use a react HOC in this way without future pitfalls

I learn ReactJs and have a design Composition question about ReactJs higher order component (HOC).
In the code below App.jsx I use this withAuthentication HOC that initializes app core processes. This HOC value is not used in the App.js. Therefore I must suppress all withAuthentication HOC render callbaks and I do that in the shouldComponentUpdate by returning false.
(I use this HOC in many other places to the get HOC's value but not in App.jsx)
File App.jsx:
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { compose } from 'recompose';
import { getAlbumData } from './redux/albumData/albumData.actions';
import { getMetaData } from './redux/albumMetaData/albumMetaData.actions';
import Header from './components/structure/Header';
import Content from './components/structure/Content';
import Footer from './components/structure/Footer';
import { withAuthentication } from './session';
import './styles/index.css';
class App extends Component {
componentDidMount() {
const { getMeta, getAlbum } = this.props;
getMeta();
getAlbum();
}
shouldComponentUpdate() {
// suppress render for now boilerplate, since withAuthentication
// wrapper is only used for initialization. App don't need the value
return false;
}
render() {
return (
<div>
<Header />
<Content />
<Footer />
</div>
);
}
}
const mapDispatchToProps = dispatch => ({
getMeta: () => dispatch(getMetaData()),
getAlbum: () => dispatch(getAlbumData()),
});
export default compose(connect(null, mapDispatchToProps), withAuthentication)(App);
The HOC rwapper WithAuthentication below is a standard HOC that render Component(App) when changes are made to Firebase user Document, like user-role changes, user auth-state changes..
File WithAuthentication .jsx
import React from 'react';
import { connect } from 'react-redux';
import { compose } from 'recompose';
import AuthUserContext from './context';
import { withFirebase } from '../firebase';
import * as ROLES from '../constants/roles';
import { setCurrentUser, startUserListener } from '../redux/userData/user.actions';
import { selectUserSlice } from '../redux/userData/user.selectors';
const WithAuthentication = Component => {
class withAuthentication extends React.Component {
constructor() {
super();
this.state = {
authUser: JSON.parse(localStorage.getItem('authUser')),
};
}
componentDidMount() {
const { firebase, setUser, startUserListen } = this.props;
this.authListener = firebase.onAuthUserListener(
authUser => {
this.setState({ authUser });
setUser(authUser);
startUserListen();
},
() => {
localStorage.removeItem('authUser');
this.setState({ authUser: null });
const roles = [];
roles.push(ROLES.ANON);
firebase
.doSignInAnonymously()
.then(authUser => {
if (process.env.NODE_ENV !== 'production')
console.log(`Sucessfully signed in to Firebase Anonymously with UID: ${firebase.getCurrentUserUid()}`);
firebase.doLogEvent('login', { method: 'Anonymous' });
firebase
.userDoc(authUser.user.uid)
.set({
displayName: `User-${authUser.user.uid.substring(0, 6)}`,
roles,
date: firebase.fieldValue.serverTimestamp(),
})
.then(() => {
console.log('New user saved to Firestore!');
})
.catch(error => {
console.log(`Could not save user to Firestore! ${error.code}`);
});
})
.catch(error => {
console.error(`Failed to sign in to Firebase: ${error.code} - ${error.message}`);
});
},
);
}
componentWillUnmount() {
this.authListener();
}
render() {
const { currentUser } = this.props;
let { authUser } = this.state;
// ALl changes to user object will trigger an update
if (currentUser) authUser = currentUser;
return (
<AuthUserContext.Provider value={authUser}>
<Component {...this.props} />
</AuthUserContext.Provider>
);
}
}
withAuthentication.whyDidYouRender = true;
const mapDispatchToProps = dispatch => ({
setUser: authUser => dispatch(setCurrentUser(authUser)),
startUserListen: () => dispatch(startUserListener()),
});
const mapStateToProps = state => {
return {
currentUser: selectUserSlice(state),
};
};
return compose(connect(mapStateToProps, mapDispatchToProps), withFirebase)(withAuthentication);
};
export default WithAuthentication;
My question is will this hit me later with problems or is this ok to do it like this?
I know a HOC is not suppose to be used like this. The WithAuthentication is taking care of Authentication against Firebase and then render on all user object changes both local and from Firestore listener snapshot.
This HOC is used in many other places correctly but App.jsx only need to initialize the HOC and never use it's service.
My question is will this hit me later with problems or is this ok to do it like this?

When and where to check on a Firebase user in React

I am trying to figure out how to use Firebase.
I have a config with an auth listener:
onAuthUserListener(next, fallback) {
// onUserDataListener(next, fallback) {
return this.auth.onAuthStateChanged(authUser => {
if (!authUser) {
// user not logged in, call fallback handler
fallback();
return;
}
this.user(authUser.uid).get()
.then(snapshot => {
let snapshotData = snapshot.data();
let userData = {
...snapshotData, // snapshotData first so it doesn't override information from authUser object
uid: authUser.uid,
email: authUser.email,
emailVerified: authUser.emailVerifed,
providerData: authUser.providerData
};
setTimeout(() => next(userData), 0); // escapes this Promise's error handler
})
.catch(err => {
// TODO: Handle error?
console.error('An error occured -> ', err.code ? err.code + ': ' + err.message : (err.message || err));
setTimeout(fallback, 0); // escapes this Promise's error handler
});
});
}
// ... other methods ...
// }
I have read the documentation about creating a listener to see if there is an authUser and have got this authentication listener plugged in.
import React from 'react';
import { AuthUserContext } from '../Session/Index';
import { withFirebase } from '../Firebase/Index';
const withAuthentication = Component => {
class WithAuthentication extends React.Component {
constructor(props) {
super(props);
this.state = {
authUser: null,
};
}
componentDidMount() {
this.listener = this.props.firebase.auth.onAuthStateChanged(
authUser => {
authUser
? this.setState({ authUser })
: this.setState({ authUser: null });
},
);
}
componentWillUnmount() {
this.listener();
};
render() {
return (
<AuthUserContext.Provider value={this.state.authUser}>
<Component {...this.props} />
</AuthUserContext.Provider>
);
}
}
return withFirebase(WithAuthentication);
};
export default withAuthentication;
Then in the consumer component I have:
import React from 'react';
import {
BrowserRouter as Router,
Route,
Link,
Switch,
useRouteMatch,
} from 'react-router-dom';
import * as ROUTES from '../../constants/Routes';
import { compose } from 'recompose';
import { Divider, Layout, Card, Tabs, Typography, Menu, Breadcrumb, Icon } from 'antd';
import { withFirebase } from '../Firebase/Index';
import { AuthUserContext, withAuthorization, withEmailVerification } from '../Session/Index';
const { Title, Text } = Typography
const { TabPane } = Tabs;
const { Header, Content, Footer, Sider } = Layout;
const { SubMenu } = Menu;
class Dashboard extends React.Component {
state = {
collapsed: false,
loading: false,
};
onCollapse = collapsed => {
console.log(collapsed);
this.setState({ collapsed });
};
render() {
return (
<AuthUserContext.Consumer>
{ authUser => (
<div>
<Text style={{ float: 'right', color: "#fff"}}>
{/*
{
this.props.firebase.db.collection('users').doc(authUser.uid).get()
.then(doc => {
console.log( doc.data().name
)
})
}
*/}
</div>
)}
</AuthUserContext.Consumer>
);
}
}
export default withFirebase(Dashboard);
It works fine the first time the page is loaded.
However, on a page refresh, the system is slower than the code and returns null error messages that say:
TypeError: Cannot read property 'uid' of null (anonymous function)
I have seen this article which proposes solutions for Angular.
I can't find a way to implement this so that it works in react.
The article suggests:
firebase.auth().onAuthStateChanged( user =>; {
if (user) { this.userId = user.uid }
});
So, in my listener I tried putting if in front of authUser - but that doesn't seem to be an approach that works.
Any advice on what to try next to make a listener that lets firebase load the user before it runs the check?
Try react-with-firebase-auth this library.
This library makes a withFirebaseAuth() function available to you.
import * as React from 'react';
import * as firebase from 'firebase/app';
import 'firebase/auth';
import withFirebaseAuth, { WrappedComponentProps } from 'react-with-firebase-auth';
import firebaseConfig from './firebaseConfig';
const firebaseApp = firebase.initializeApp(firebaseConfig);
const App = ({
/** These props are provided by withFirebaseAuth HOC */
signInWithEmailAndPassword,
createUserWithEmailAndPassword,
signInWithGoogle,
signInWithFacebook,
signInWithGithub,
signInWithTwitter,
signInAnonymously,
signOut,
setError,
user,
error,
loading,
}: WrappedComponentProps) => (
<React.Fragment>
{
user
? <h1>Hello, {user.displayName}</h1>
: <h1>Log in</h1>
}
{
user
? <button onClick={signOut}>Sign out</button>
: <button onClick={signInWithGoogle}>Sign in with Google</button>
}
{
loading && <h2>Loading..</h2>
}
</React.Fragment>
);
const firebaseAppAuth = firebaseApp.auth();
/** See the signature above to find out the available providers */
const providers = {
googleProvider: new firebase.auth.GoogleAuthProvider(),
};
/** providers can be customised as per the Firebase documentation on auth providers **/
providers.googleProvider.setCustomParameters({hd:"mycompany.com"});
/** Wrap it */
export default withFirebaseAuth({
providers,
firebaseAppAuth,
})(App);

Amplify+react: hosted ui with federated identity provider

I would like to use the hosted ui with amplify in react.
The authentication should be done using federated identity provider, which is working correctly.
But I don't know, how to write the react part.
I found in amplify tutorial: that is should be possible with this sample:
// OAuthButton.js
import { withOAuth } from 'aws-amplify-react';
import React, { Component } from 'react';
class OAuthButton extends Component {
render() {
return (
<button onClick={this.props.OAuthSignIn}>
Sign in with provider's account
</button>
)
}
}
export default withOAuth(OAuthButton);
// App.js
import React, { Component } from 'react';
import './App.css';
import OAuthButton from './OAuthButton';
import Amplify, { Auth, Hub } from 'aws-amplify';
import awsconfig from './aws-exports'; // your Amplify configuration
// your Cognito Hosted UI configuration
const oauth = {
domain: 'your_cognito_domain',
scope: ['phone', 'email', 'profile', 'openid', 'aws.cognito.signin.user.admin'],
redirectSignIn: 'http://localhost:3000/',
redirectSignOut: 'http://localhost:3000/',
responseType: 'code' // or 'token', note that REFRESH token will only be generated when the responseType is code
};
Amplify.configure(awsconfig);
Auth.configure({ oauth });
class App extends Component {
constructor(props) {
super(props);
this.signOut = this.signOut.bind(this);
// let the Hub module listen on Auth events
Hub.listen('auth', (data) => {
switch (data.payload.event) {
case 'signIn':
this.setState({authState: 'signedIn', authData: data.payload.data});
break;
case 'signIn_failure':
this.setState({authState: 'signIn', authData: null, authError: data.payload.data});
break;
default:
break;
}
});
this.state = {
authState: 'loading',
authData: null,
authError: null
}
}
componentDidMount() {
console.log('on component mount');
// check the current user when the App component is loaded
Auth.currentAuthenticatedUser().then(user => {
console.log(user);
this.setState({authState: 'signedIn'});
}).catch(e => {
console.log(e);
this.setState({authState: 'signIn'});
});
}
signOut() {
Auth.signOut().then(() => {
this.setState({authState: 'signIn'});
}).catch(e => {
console.log(e);
});
}
render() {
const { authState } = this.state;
return (
<div className="App">
{authState === 'loading' && (<div>loading...</div>)}
{authState === 'signIn' && <OAuthButton/>}
{authState === 'signedIn' && <button onClick={this.signOut}>Sign out</button>}
</div>
);
}
}
export default App;
In this form it is not working.
QA:
how it should looks like the OAuthSignIn method?
Because when I used the window.open method to redirect to the hosted ui url, my react app doesn't know that the user was authenticated, how the code should looks like, that the app could recognize that the login flow was finished successfully?

App not re-rendering on history.push when run with jest

I'm trying to test my LoginForm component using jest and react-testing-library. When the login form is submitted successfully, my handleLoginSuccess function is supposed to set the 'user' item on localStorage and navigate the user back to the home page using history.push(). This works in my browser in the dev environment, but when I render the component using Jest and mock out the API, localStorage gets updated but the navigation to '/' doesn't happen.
I've tried setting localStorage before calling history.push(). I'm not sure what is responsible for re-rendering in this case, and why it works in dev but not test.
Login.test.jsx
import 'babel-polyfill'
import React from 'react'
import {withRouter} from 'react-router'
import {Router} from 'react-router-dom'
import {createMemoryHistory} from 'history'
import {render, fireEvent} from '#testing-library/react'
import Login from '../../pages/Login'
import API from '../../util/api'
jest.mock('../../util/api')
function renderWithRouter(
ui,
{route = '/', history = createMemoryHistory({initialEntries: [route]})} = {},
) {
return {
...render(<Router history={history}>{ui}</Router>),
// adding `history` to the returned utilities to allow us
// to reference it in our tests (just try to avoid using
// this to test implementation details).
history,
}
}
describe('When a user submits the login button', () => {
test('it allows the user to login', async () => {
const fakeUserResponse = {'status': 200, 'data': { 'user': 'Leo' } }
API.mockImplementation(() => {
return {
post: () => {
return Promise.resolve(fakeUserResponse)
}
}
})
const route = '/arbitrary-route'
const {getByLabelText, getByText, findByText} = renderWithRouter(<Login />, {route})
fireEvent.change(getByLabelText(/email/i), {target: {value: 'email#gmail.com '}})
fireEvent.change(getByLabelText(/password/i), {target: {value: 'Foobar123'}})
fireEvent.click(getByText(/Log in/i))
const logout = await findByText(/Log Out/i)
expect(JSON.parse(window.localStorage.getItem('vector-user'))).toEqual(fakeUserResponse.data.user)
})
})
relevant parts of LoginForm.jsx
class LoginForm extends React.Component {
constructor(props) {
super(props);
this.state = {
disableActions: false,
formErrors: null,
};
}
handleLoginSuccess = () => {
const { loginSuccessCallback, redirectOnLogin, history } = { ...this.props };
if (loginSuccessCallback) {
loginSuccessCallback();
} else {
history.push('/');
}
}
loginUser = ({ user }) => {
localStorage.setItem('vector-user', JSON.stringify(user));
}
handleLoginResponse = (response) => {
if (response.status !== 200) {
this.handleResponseErrors(response.errors);
} else {
this.loginUser(response.data);
this.handleLoginSuccess();
}
}
handleLoginSubmit = (event) => {
event.preventDefault();
const {
disableActions, email, password
} = { ...this.state };
if (disableActions === true) {
return false;
}
const validator = new Validator();
if (!validator.validateForm(event.target)) {
this.handleResponseErrors(validator.errors);
return false;
}
this.setState(prevState => ({ ...prevState, disableActions: true }));
new API().post('login', { email, password }).then(this.handleLoginResponse);
return true;
}
}
Login.jsx
import React from 'react';
import { withRouter, Link } from 'react-router-dom';
import PropTypes from 'prop-types';
import LoginForm from '../components/LoginForm';
class Login extends React.Component {
constructor({ location }) {
super();
const originalRequest = location.state && location.state.originalRequest;
this.state = {
originalRequest
};
}
render() {
const { originalRequest } = { ...this.state };
return (
<div>
<h1>Login</h1>
<LoginForm redirectOnLogin={originalRequest && originalRequest.pathname} />
<Link to="/forgot">Forgot your password?</Link>
</div>
);
}
}
Login.propTypes = {
location: PropTypes.shape({
state: PropTypes.shape({
originalRequest: PropTypes.shape({
pathname: PropTypes.string
})
})
})
};
export default withRouter(Login);
Currently the await findByText() times out.
I think that's because in your tests you're not rendering any Route components. Without those react-router has no way to know what to render when the route changes. It will always render Login.

Resources