setAuth is not a function at handleSubmit - javascript

hello I''m trying to use Auth in login page for chicking if the user is logged in or not, the front end keep sending "getAuth is not a function at handleSubmit", the backEnd working as it supposed to.
login.js
import { useRef, useState, useEffect } from 'react';
import axios from 'axios'
import UseAuth from '../hooks/useAuth';
import {Link , useNavigate , useLocation} from 'react-router-dom'
const Login = () => {
const {setAuth}= UseAuth()
const navigate = useNavigate()
const location = useLocation()
// console.log(location.state.from.pathname)
const from = location.state?.from?.pathname || '/';
const userRef = useRef();
const errRef = useRef();
const [user, setUser] = useState('');
const [pwd, setPwd] = useState('');
const [errMsg, setErrMsg] = useState('');
useEffect(() => {
userRef.current.focus();
}, [])
useEffect(() => {
setErrMsg('');
}, [user, pwd])
const handleSubmit = async (e) => {
e.preventDefault();
try {
const response = await axios.post('http://localhost:4000/login',
JSON.stringify({ user, pwd }),
{
headers: { 'Content-Type': 'application/json' },
withCredentials: true
}
);
const accessToken = response?.data?.accessToken;
const roles = response?.data?.roles;
setAuth({ user, pwd, roles, accessToken });
setUser('');
setPwd('');
navigate(from, {replace:true})
} catch (err) {
console.log(err)
if (!err?.response) {
setErrMsg('No Server Response');
} else if (err.response?.status === 400) {
setErrMsg('Missing Username or Password');
} else if (err.response?.status === 401) {
setErrMsg('Unauthorized');
} else {
setErrMsg('Login Failed');
}
errRef.current.focus();
}
}
return (
<>
<section>
<p ref={errRef} className={errMsg ? "errmsg" : "offscreen"} aria-live="assertive">{errMsg}</p>
<h1>Sign In</h1>
<form onSubmit={handleSubmit}>
<label htmlFor="username">Username:</label>
<input
type="text"
id="username"
ref={userRef}
autoComplete="off"
onChange={(e) => setUser(e.target.value)}
value={user}
required
/>
<label htmlFor="password">Password:</label>
<input
type="password"
id="password"
onChange={(e) => setPwd(e.target.value)}
value={pwd}
required
/>
<button>Sign In</button>
</form>
<p>
Need an Account?<br />
<Link to ={'/register'} className="line">
{/*put router link here*/}
Sign Up
</Link>
</p>
</section>
</>
)
}
export default Login
authProvider.js
this function is to store the data so I can use it later
import { createContext, useState } from "react";
const AuthContext = createContext({});
export const AuthProvider = ({ children }) => {
const [auth, setAuth] = useState({});
return (
<AuthContext.Provider value={{ auth , setAuth }}>
{children}
</AuthContext.Provider>
)
}
export default AuthContext;
useAuth.js hook
I use this function in other pages to pass the data to the authProvider.js
import { useContext } from "react";
import AuthContext from "../context/authProvider";
const UseAuth = () => {
return useContext(AuthContext);
}
export default UseAuth;

I'm guessing you need to destructure your object in Login
const { setAuth } = UseAuth()
Because your hook is providing an object that contains both auth and setAuth

I've resolved the issue the only thing that I had to do is destructure the UseAuth component: setAuth.setAuth({ user, pwd, roles, accessToken })
Because I'm storing the auth and the setAuth method in UseAuth which saved in setAuth variable

The function is not recognised probably because you have not wrapped the application with the AuthProvider context in index.js!
Eg:
import { AuthProvider } from './services/AuthProvider';
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<Router>
<AuthProvider>
<Routes>
<Route path="/*" element={<App />} />
</Routes>
</AuthProvider>
</Router>
);

Related

Unable to prevent user to go back to login page after successfully logging in [duplicate]

This question already has an answer here:
React-router v6 private route not working well
(1 answer)
Closed 5 months ago.
I have two problems with my react app, and I think they are related to each other:
I can't prevent user to go back to login page.
When I am in playground page which is a protected page and I refresh the page it redirects me to login page.
I think the problems are related to the ProtectedRoute component. Here is my code:
firebase.utils.js:
import { initializeApp } from "firebase/app";
import {
getAuth,
createUserWithEmailAndPassword,
signInWithEmailAndPassword,
signOut,
onAuthStateChanged,
} from "firebase/auth";
import { getFirestore, doc, getDoc, setDoc } from "firebase/firestore";
// Web app's Firebase configuration
const firebaseConfig = {
apiKey: "XXXXXXX",
authDomain: "XXXXXXX",
projectId: "XXXXXXX",
storageBucket: "XXXXXXX",
messagingSenderId: "XXXXXXX",
appId: "XXXXXXX",
measurementId: "XXXXXXX",
};
const firebaseApp = initializeApp(firebaseConfig);
export const auth = getAuth();
export const db = getFirestore();
export const createAuthUserWithEmailAndPassword = async (email, password) => {
if (!email || !password) return;
return await createUserWithEmailAndPassword(auth, email, password);
};
export const signInAuthUserWithEmailAndPassword = async (email, password) => {
if (!email || !password) return;
return await signInWithEmailAndPassword(auth, email, password);
};
export const signOutUser = async () => await signOut(auth);
export const onAuthStateChangedListener = (callback) =>
onAuthStateChanged(auth, callback);
sign-in-forms.jsx:
import { useState } from "react";
import { Link, useNavigate } from "react-router-dom";
import { Container, Form, Button } from "react-bootstrap";
import { ToastContainer, toast } from "react-toastify";
import { signInAuthUserWithEmailAndPassword } from "../../utils/firebase/firebase.utils";
import loginImg from "../../login.png";
import "./sign-in-form.scss";
import "react-toastify/dist/ReactToastify.css";
const defaultFormFields = {
email: "",
password: "",
};
const SignInForm = () => {
const [formFields, setFormFields] = useState(defaultFormFields);
const { email, password } = formFields;
const navigate = useNavigate();
const resetFormFields = () => {
setFormFields(defaultFormFields);
};
const hadleSubmit = async (event) => {
event.preventDefault();
try {
await signInAuthUserWithEmailAndPassword(email, password);
resetFormFields();
navigate("/playground");
} catch (error) {
switch (error.code) {
case "auth/wrong-password":
toast.error("Incorrect password for email", {
position: toast.POSITION.TOP_CENTER,
});
break;
case "auth/user-not-found":
toast.error("No user associated with this email", {
position: toast.POSITION.TOP_CENTER,
});
break;
default:
console.log(error);
}
}
};
const handleChange = (event) => {
const { name, value } = event.target;
setFormFields({ ...formFields, [name]: value });
};
return (
<Container id="main-container" className="d-grid h-100">
<ToastContainer />
<Form
id="sign-in-form"
className="text-center w-300"
onSubmit={hadleSubmit}
>
<img className="mb-3 mt-3 sign-in-img" src={loginImg} alt="login" />
<Form.Group className="mb-3" controlId="sign-in-email-address">
<Form.Control
className="position-relative"
type="email"
size="lg"
placeholder="Email"
autoComplete="username"
onChange={handleChange}
name="email"
value={email}
/>
</Form.Group>
<Form.Group className="mb-3" controlId="sign-in-password">
<Form.Control
className="position-relative"
type="password"
size="lg"
placeholder="Password"
onChange={handleChange}
name="password"
value={password}
/>
</Form.Group>
<div className="mb-3">
<Link to="/login/reset">Reset password</Link>
</div>
<div className="d-grid">
<Button variant="primary" size="lg" type="submit">
Login
</Button>
</div>
</Form>
</Container>
);
};
export default SignInForm;
protectedRoute.jsx - I included the navbar (Navigate component) here because the navbar must appear only on protected pages:
import { useContext } from "react";
import { Navigate, Outlet } from "react-router-dom";
import { UserContext } from "../../../contexts/user.context";
import Navigation from "../../../routes/navigation/navigation";
const ProtectedRoute = () => {
const { currentUser } = useContext(UserContext);
if (!currentUser) return <Navigate to="/auth" />;
return (
<div>
<Navigation />
<Outlet />
</div>
);
};
export default ProtectedRoute;
user-context.jsx:
import { createContext, useState, useEffect } from "react";
import { onAuthStateChangedListener } from "../utils/firebase/firebase.utils";
export const UserContext = createContext({
currentUser: null,
setCurrentUser: () => null,
});
export const UserProvider = ({ children }) => {
const [currentUser, setCurrentUser] = useState(null);
const value = { currentUser, setCurrentUser };
useEffect(() => {
const unsubscribe = onAuthStateChangedListener((user) => {
console.log(user);
setCurrentUser(user);
});
return unsubscribe;
}, []);
return <UserContext.Provider value={value}>{children}</UserContext.Provider>;
};
App.js:
import { Routes, Route, Navigate } from "react-router-dom";
import Authentication from "./routes/authentication/authentication";
import ProtectedRoute from "./components/common/protected-route/protectedRoute";
import Playground from "./routes/playground/playground";
import PageNotFound from "./components/common/page-not-found/pageNotFound";
function App() {
return (
<div>
<Routes>
<Route path="/" exact element={<Navigate to="/auth" />} />
<Route element={<ProtectedRoute />}>
<Route path="/playground" element={<Playground />} />
</Route>
<Route path="/auth" element={<Authentication />} />
<Route path="/not-found" element={<PageNotFound />} />
<Route path="*" element={<Navigate to="/not-found" />} />
</Routes>
</div>
);
}
export default App;
What am I doing wrong? Any help is appreciated. Thanks!
If you want to prevent your logged in user from going to login page, add a check in SignInForm:
const { currentUser } = useContext(UserContext);
// If there is a user redirect to whatever page you want:
if (currentUser) return <Navigate to="/playground" />;
return <div>Actual login page content</div>
And for your user to stay logged in after a page refresh the easiet way is to use localStorge like so:
import { createContext, useEffect, useState } from "react";
import { onAuthStateChangedListener } from "../utils/firebase/firebase.utils";
export const UserContext = createContext({
currentUser: null,
setCurrentUser: () => null,
});
export const UserProvider = ({ children }) => {
const [currentUser, setCurrentUser] = useState(
localStorage.getItem("user") ? JSON.parse(localStorage.getItem("user")) : null
);
useEffect(() => {
const unsubscribe = onAuthStateChangedListener((user) => {
console.log(user);
localStorage.setItem(JSON.stringify(user));
setCurrentUser(user);
});
return unsubscribe;
}, []);
return (
<UserContext.Provider value={(currentUser, setCurrentUser)}>{children}</UserContext.Provider>
);
};

Changing the state of a context within an if-else in JavaScript

I tried making a global state by using a Context and have the following for my AuthProvider.js file
import { createContext, useState } from "react";
const AuthContext = createContext({});
export const AuthProvider = ({ children }) => {
const [auth, setAuth] = useState({});
return (
<AuthContext.Provider value={{ auth, setAuth }}>
{children}
</AuthContext.Provider>
)
}
export default AuthContext;
I want to be able to change its boolean state within an if-else instead of a button with an onCLick event in the tutorial but it shows an error in the console: setAuth is not a function
function Home() {
const setAuth = useContext(AuthProvider);
const [usernameReg, setUsernameReg] = useState("");
const [passwordReg, setPasswordReg] = useState("");
const [username, setUsername] = useState("");
const [password, setPassword] = useState("");
const [loginStatus, setloginStatus] = useState("");
const uLogin = document.getElementById('userLogin');
const pLogin = document.getElementById('passwordLogin');
const register = () => {
if (usernameReg === "" || passwordReg === ""){
alert("Fields can't be blank");
return;
}
Axios.post('http://localhost:3001/register', {
username: usernameReg,
password: passwordReg,
}).then((response) => {
console.log(response);
});
}
const login = () => {
Axios.post('http://localhost:3001/login', {
username: username,
password: password,
}).then((response) => {
if (response.data.message){
setloginStatus(response.data.message);
alert(response.data.message);
}
else{
setloginStatus(response.data[0].username);
setAuth(true); <====================================== here
window.location.href = "/map";
}
console.log(response.data);
});
}
return (
<div className="Home">
<h1>Register</h1>
<label>Username: </label>
<input type="text"
onChange={(e) => {
setUsernameReg(e.target.value)
}}/>
<label><p></p>Password: </label>
<input type="text"
onChange={(e)=> {
setPasswordReg(e.target.value)
}}/>
<p></p>
<button onClick={register}> Register </button>
<h1>--------------------------------------------------------</h1>
{/*<form id="loginForm">*/}
<h1>Log In</h1>
<input id="userLogin" type="text" placeholder="Username"
onChange={(e) => {
setUsername(e.target.value)
}}
/>
<p></p>
<input id="passwordLogin" type="password" placeholder="Password"
onChange={(e) => {
setPassword(e.target.value)
}}
/>
<p></p>
<button onClick={login}> Log In </button>
{/*</form>*/}
<h1>{loginStatus}</h1>
</div>
)
}
my App.js is below
import React, { useState } from "react";
import { BrowserRouter,Route, Routes} from 'react-router-dom';
import './App.css';
import Home from "./Pages/Home";
import MapPage from "./Pages/MapPage.js";
import ProtectedRoutes from "./ProtectedRoutes";
import { AuthProvider } from "./components/AuthProvider"
function App() {
const [auth, setAuth ] = useState(false);
//const [auth, setAuth] = useState(false);
return (
<BrowserRouter>
<Routes element ={<AuthProvider />} value={{auth, setAuth}}>
<Route element ={<ProtectedRoutes />}>
<Route element={<MapPage />} path="/map" exact/>
</Route>
<Route path="/" element={<Home />}/>
</Routes>
</BrowserRouter>
);
}
export default App;
index.js below
import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';
import { AuthProvider } from './components/AuthProvider'
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<AuthProvider>
<App />
</AuthProvider>
);
// If you want to start measuring performance in your app, pass a function
// to log results (for example: reportWebVitals(console.log))
// or send to an analytics endpoint. Learn more: https://bitly/CRA-vitals
reportWebVitals();
I'm very new to JavaScript and I've only been following along tutorials for the functions or features I need for the project. Is there a way for this?
I'm also guessing that the code for the context file is overkill for a simple Boolean state.
You probably want to check the context docs again, and try to understand how and why you set them up, and how to access them. You tried accessing the context by passing the provider as a param to the useContext hook. This is wrong, you need to pass the Context itself. You also called the return from the context setAuth, but the return is the context itself. I urge you to read the documentation thourougly. However, this is probably what you wanted:
type AuthContextType = {
auth: boolean;
setAuth: Dispatch<SetStateAction<boolean>>;
};
const AuthContext = createContext<AuthContextType | undefined>(undefined);
export const AuthProvider = ({ children }: PropsWithChildren<{}>) => {
const [auth, setAuth] = useState(false);
return (
<AuthContext.Provider value={{ auth, setAuth }}>
{children}
</AuthContext.Provider>
);
};
const useAuthContext = () => {
const ctx = useContext(AuthContext);
if (!ctx) throw new Error("No AuthContext Provider found");
return ctx // as AuthContextType<...> if you need the context to be generic and to infer generics.
};
function Home() {
const { auth, setAuth } = useAuthContext();
...
}

I try to lay out a match for receiving a name from url but I receive a match undefinde

I encountered such an error match undefind. I'm taking an old course on React did everything as shown in the lesson but I get an error why. i don't understand why match undefinde. Maybe you need to pick up the match in another way or somehow pass it ??
import React from "react";
import { BrowserRouter, Route, Routes } from 'react-router-dom'
import { NavBar } from "./components/NavBar";
import { Home } from './pages/Home'
import { About } from './pages/About'
import { Profile } from './pages/Profile'
import { Alert } from "./components/Alert";
import { AlertState } from "./context/alert/AlertState";
import { GithubState } from "./context/github/GithunState";
function App() {
return (
<GithubState>
<AlertState>
<BrowserRouter>
<NavBar />
<div className="container pt-4">
<Alert alert={{text: 'Test Alert'}} />
<Routes>
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
<Route path="/profile/:name" element={<Profile />} />
</Routes>
</div>
</BrowserRouter>
</AlertState>
</GithubState>
);
}
export default App;
import React from 'react'
import { useContext, useEffect } from 'react';
import { GithubContext } from '../context/github/githubContex';
export const Profile = ({match}) => {
// const github = useContext(GithubContext)
// const name = match.params.name
// useEffect(() => {
// github.getUser()
// github.getRepos(name)
// }, [])
console.log('asd',match);
return(
<div>
<h1>Profile page</h1>
</div>
)
}
import React, {useReducer} from "react"
import axios from 'axios'
import { CLEAR_USERS, GET_REPOS, GET_USER, SEARCH_USERS, SET_LOADING } from "../types"
import { GithubContext } from "./githubContex"
import { githubReducer } from "./githubReducer"
const CLIENT_ID = process.env.REACT_APP_CLIENT_ID
const CLIENT_SECRET = process.env.REACT_APP_CLIENT_SECRET
const withCreads = url => {
return `${url}client_id=${CLIENT_ID}&client_secret=${CLIENT_SECRET}`
}
export const GithubState = ({children}) => {
const initialState = {
user: {},
users: [],
loading: false,
repos: []
}
const [state, dispatch] = useReducer(githubReducer, initialState)
const search = async value => {
setLoading()
const response = await axios.get(
withCreads(`https://api.github.com/search/users?q=${value}&`)
)
dispatch({
type: SEARCH_USERS,
payload: response.data.items
})
}
const getUser = async name => {
setLoading()
const response = await axios.get(
withCreads(`https://api.github.com/users/users/${name}?`)
)
dispatch({
type: GET_USER,
payload: response.data
})
}
const getRepos = async name => {
setLoading()
const response = await axios.get(
withCreads(`https://api.github.com/users/users/${name}/repos?per_page=5&`)
)
dispatch({
type: GET_REPOS,
payload: response.data
})
}
const clearUsers = () => dispatch({type: CLEAR_USERS})
const setLoading = () => dispatch({type: SET_LOADING})
const {user, users, repos, loading} = state
return (
<GithubContext.Provider value={{
setLoading, search, getUser, getRepos, clearUsers,
user, users, repos, loading
}}>
{children}
</GithubContext.Provider>
)
}
link to Github https://github.com/Eater228/React-Hooks
Check your package.json file and if you are using an older version of react-router-dom please use the latest version.
match prop should be passed down from the Route component and it will reflect the correct data as you are using react-router-dom.
Update
You are using element prop for rendering component and that's not the correct one. You should replace that element with component and it will work.
Update
Please consider using useParams hook instead of that match prop.
https://reactrouter.com/docs/en/v6/getting-started/overview#reading-url-parameters

react-js testing library unable to test useState hook and asynchronous axios signIn function

I cant seem to get the test for my reactjs login component to work.
I am using react-testing-library only and not Enzyme.
I want to be able test 3 things:
That the signIn is clicked once and the axios ajax call uses the mocked ajax
That I can set a value using useState and check the value was set
That the page rendered after a successful login has the text welcome
None of the above is working. I have been working through different issues eg this expect(signIn).toHaveBeenCalledTimes(1) returns:
Expected number of calls: 1
Received number of calls: 0
Other times it throws Error: Unable to find the "window" object for the given node. for this:
const button = screen.getByRole('button', {name: /submit/i})
Here is what Login.test.js looks like
/**
* #jest-environment jsdom
*/
import React from 'react';
import { screen, render, waitFor, fireEvent, getAllByLabelText } from '#testing-library/react';
import {renderHook} from '#testing-library/react-hooks'
import userEvent from '#testing-library/user-event';
import { act } from "react-dom/test-utils";
import Login from "../components/auth/login"
import Index from "../components/index"
import {userContext} from "../utils/context/UserContext"
import axios from 'axios';
const user = {user: {} }
const isLoggedIn = false
const setIsLoggedIn = jest.fn()
const setUser = jest.fn()
const setState = jest.fn();
jest.mock('axios');
describe("Login", () => {
afterEach(() => {
jest.clearAllMocks();
});
test('ajax login', async () => {
const {container} = render(
<userContext.Provider value={ {user, isLoggedIn, setIsLoggedIn, setUser} } >
<Login />
</userContext.Provider>
)
axios.post.mockImplementationOnce(() => Promise.resolve({
status: 200,
data: {user: {name: 'Rony', email: 'rony#example.com' } }
}))
setIsLoggedIn.mockReturnValue(true)
const signIn = jest.spyOn(React, "useState")
signIn.mockImplementation(isLoggedIn => [isLoggedIn, setIsLoggedIn]);
const email = container.querySelector('input[name=email]')
const password = container.querySelector('input[name=password]')
const button = screen.getByRole('button', {name: /submit/i})
await act(async () => {
userEvent.type('email', 'abc#yahoo.com')
userEvent.type('password', 'abcddef')
userEvent.click(button)
});
expect(signIn).toHaveBeenCalledTimes(1)
expect(setIsLoggedIn).toEqual(true);
expect(screen.getByText(/welcome/i)).resolves.toBeInTheDocument
})
})
The login component is shown below:
import React, {useState, useEffect, useContext} from 'react';
import PropTypes from 'prop-types';
import Cookies from 'js-cookie';
import axios from 'axios'
import {userContext} from "../../utils/context/UserContext"
export const Login = (props) => {
const {user, setIsLoggedIn, isLoggedIn, setUser} = useContext(userContext)
const token = Cookies.get('csrf_token') || props.csrf_token || ''
const[loginData, setLoginData] = useState({email: '', password: '' })
const[csrf_token, setCsrfToken] = useState('')
useEffect(() => {
setCsrfToken(token)
}, [])
const handleChange = (event) => {
const {name, value} = event.target
setLoginData({...loginData, [name]: value})
}
const signIn = (event) => {
event.preventDefault();
let headers = {
headers: {'Content-Type': 'application/json',
'X-CSRF-TOKEN': csrf_token
}
}
let url = "/api/v1/sessions"
axios.post(url, {user: loginData}, headers)
.then(response => {
setLoginData({email: '', password: ''})
setIsLoggedIn(true)
setUser(response.data.user)
})
.catch(function (error) {
setIsLoggedIn(false)
})
}
return(
<React.Fragment>
<div> </div>
<form onSubmit={signIn}>
<h3>Login</h3>
<div className="mb-3 form-group col-xs-4">
<label htmlFor="exampleInputEmail1" className="form-label">Email address</label>
<input type="email" name="email" className="form-control" id="exampleInputEmail1"
value={loginData.email} onChange={handleChange} aria-describedby="emailHelp" />
<div id="emailHelp" className="form-text">We'll never share your email with anyone else.</div>
</div>
<div className="mb-3">
<label htmlFor="exampleInputPassword1" className="form-label">Password</label>
<input type="password" name="password" className="form-control" id="exampleInputPassword1"
value={loginData.password} onChange={handleChange} />
</div>
<div className="mb-3 form-check">
<input type="checkbox" className="form-check-input" id="exampleCheck1" />
<label className="form-check-label" htmlFor="exampleCheck1">Check me out</label>
</div>
<button name="submit" type="submit" className="btn btn-primary" onClick={signIn} >Submit</button>
</form>
</React.Fragment>
)
}
UserContext.js
import React from 'react';
export const userContext = React.createContext(undefined)
The index.js
import React, {useState, useEffect, useContext} from 'react';
import PropTypes from 'prop-types';
import Cookies from 'js-cookie';
import axios from 'axios'
import {userContext} from "../../utils/context/UserContext"
import { BrowserRouter as Router, Route, Switch } from "react-router-dom";
import Login from "../components/auth/login";
export default const Index = (props) => {
let form_token = Cookies.get('csrf_token') || ''
let check_login = props.isLoggedIn || false
let check_user = props.user || ""
const[isLoggedIn, setIsLoggedIn] = useState(check_login)
const[user, setUser] = useState(check_user)
const[isError, setIsError] = useState('')
useEffect(() => {
setCsrfToken(form_token)
}, [isLoggedIn, csrf_token, user])
return (
<userContext.Provider value={{user, setUser, isLoggedIn, setIsLoggedIn}}>
<Router>
<React.Fragment>
<div>
<Switch>
<Route exact path='/'>
</Route>
<Route path='/login'>
<Login />
</Route>
</Switch>
</div>
</React.Fragment>
</Router>
</userContext.Provider>
)
}
You should not mock React's useState function, and you cannot test redirection because you are rendering only the Login component in your test.
Beside this I don't thinks you can mock a function defined in a functional component (signIn), and even if it was possible you should not test internal implementation, but only what a real user whould see : here, you can test that the inputs have the correct value after the userEvent.type(...).
Just remove the useState and signIn mocks, then in your Login component add a redirection after a successful login. Then you just have to enter user information, click the submit button (as you already do), then you can check that history.push have been called correctly (see Simplest test for react-router's Link with #testing-library/react).

How to check if Session is alive with firebase + react?

I've added an authorization with firebase, which works completely fine.
It is possible to login to the app, and navigate, but when I use firebase.auth().signOut the onAuthChanged observable is not changed/not triggered.
For correct login/password(400 for the wrong combination) - the session is saved, and I have the user credentials:
import React, {useContext, useEffect} from 'react';
import {ROUTES} from '../../../constants';
import {AuthUserContext} from "../../../session";
import history from '../../../helpers/history';
import {useLocation} from "react-router";
import app from "../../../api/firebase";
const WithAuthorization: React.FC = ({children}) => {
const authUser = useContext(AuthUserContext);
const isLogin = useLocation().pathname === ROUTES.LOGIN;
const pushLogin = () => !isLogin && history.push(ROUTES.LOGIN);
useEffect(() => {
const listener = app.auth().onAuthStateChanged(
(user: any) => {
if(!user) {
pushLogin()
} else {
console.log('Signed in with user');
console.log(user);
}
},
(e: any) => {
console.log(e);
}, () => {
console.log('completed');
});
return listener();
}, [])
return <>
{authUser ? children : pushLogin()}
</>;
}
export default WithAuthorization;
But then, when the application is refreshed, I want to check if the session is alive.
While looking through the docs I've found onAuthChanged observable, which seems pretty straight-forward, but it is actually triggered only when I log in.
After the page is refreshed, or when I trigger signOut - it does nothing.
This is the authorization protection component, that wraps the entire App:
import React, {useContext, useEffect} from 'react';
import {ROUTES} from '../../../constants';
import {AuthUserContext} from "../../../session";
import history from '../../../helpers/history';
import app from "../../../api/firebase";
const WithAuthorization: React.FC = ({children}) => {
const authUser = useContext(AuthUserContext);
const pushLogin = () => history.push(ROUTES.LOGIN);
useEffect(() => {
const listener = app.auth().onAuthStateChanged(
(user: any) => {
if(!user) pushLogin()
},
(e: any) => {
console.log(e);
}, () => {
console.log('completed');
});
return listener();
}, [])
return <>
{authUser ? children : pushLogin()}
</>;
}
export default WithAuthorization;
Am I missing something with the auth protection component or observable?
--- The app structure:
The App component is quite simple:
import React, {useState} from 'react';
import { Route, Switch, useLocation } from 'react-router';
import { Header, WithAuthorization } from './common';
import DeviceSelection from './DeviceSelection';
import PerfectScroll from 'react-perfect-scrollbar';
import NotFound from './NotFound';
import ThankYou from "./Thankyou";
import 'react-perfect-scrollbar/dist/css/styles.css';
import './App.scss';
import {ROUTES} from "../constants";
import Login from "./Login";
import {AuthUserContext} from "../session";
const App = () => {
const {pathname} = useLocation();
const [authUser, setAuthUser] = useState(null as any);
const isThankYou = pathname === ROUTES.THANKYOU;
return (
<AuthUserContext.Provider
value={authUser}
>
<WithAuthorization>
{!isThankYou && <Header authUser={authUser}/>}
</WithAuthorization>
<div className={`${!isThankYou ? 'appScrollContainer' : ''}`}>
<PerfectScroll>
<Switch>
<Route exact path={[ROUTES.ROOT, ROUTES.HOME]} component={() => <WithAuthorization><DeviceSelection/></WithAuthorization>} />
<Route path={ROUTES.THANKYOU} component={() => <WithAuthorization><ThankYou/></WithAuthorization>} />
<Route path={ROUTES.LOGIN} component={() => <Login setAuthUser={(user: any) => setAuthUser(user)} />}/>
<Route path="*" component={NotFound} />
</Switch>
</PerfectScroll>
</div>
</AuthUserContext.Provider>
);
}
export default App;
Signout is coming from a button, inside Header, which is also wrapped in WithAuthorization:
<Button label={'Sign out'} click={() => app.auth().signOut()} />
Login does only one 1 thing, redirects to /home if login was successful:
import React, {useState} from 'react';
import TextInput from "../common/TextInput";
import history from '../../helpers/history';
import {ROUTES} from "../../constants";
import app, {signInWithEmailAndPassword} from "../../api/firebase";
interface Props {
setAuthUser: (user: any) => void,
}
const Login: React.FC<Props> = ({setAuthUser}) => {
const [form, updateForm] = useState({login: '', password: ''});
const authorize = (user: string, password: string) => {
app.auth().setPersistence(app.auth.Auth.Persistence.SESSION)
.then(() => {
return signInWithEmailAndPassword(user, password).then((user: any) => {
if(user) {
setAuthUser(user);
history.push(ROUTES.ROOT);
return user
}
return null
})
})
.catch((e: any) => {
console.log(e);
})
}
return <div className='form'>
<TextInput
type="text"
placeholder='login'
name={'login'}
value={form.login}
label='Login'
onChange={(e) => updateForm({...form, login: e.currentTarget.value})}
/>
<TextInput
type="password"
placeholder='password'
name={'password'}
value={form.password}
label='Password'
onChange={(e) => updateForm({...form, password: e.currentTarget.value})}
/>
<button onClick={() => authorize(form.login, form.password)}>Submit</button>
</div>
}
export default Login;
FIrebase usage itself:
import firebase from 'firebase/app';
import 'firebase/firestore';
import 'firebase/auth';
import {DEV_LOCAL_CONFIG, DEV_REMOTE_CONFIG, ORDERS_COLLECTION} from "./const";
firebase.initializeApp(window.location.hostname !== 'localhost' ? DEV_LOCAL_CONFIG : DEV_REMOTE_CONFIG);
/* ==== Authorization ==== */
const signInWithEmailAndPassword = (email: string, password: string) =>
firebase.auth().signInWithEmailAndPassword(email, password);
const signOut = () => firebase.auth().signOut();
export default firebase;
export {
signInWithEmailAndPassword,
signOut
}
My mistake was with this line only:
return listener();
When I define listener in useEffect, it is unsubscribed immediately.
Should be:
return () => listener()
Other than this, everything works fine.

Categories

Resources