Authentication with Next.js and Redux - javascript

Here is my situation. I am trying to auth the user on the load and need to run the authentication in the pages/_app.js file. Here is the error useReduxContext.js:24 Uncaught Error: could not find react-redux context value; please ensure the component is wrapped in a <Provider> . Is there a way to run the auth in a wrapped <Provider>. Here is the link to codesandbox
file pages/_app.js
import React, { useEffect } from 'react';
import { Provider, useDispatch } from 'react-redux';
import { auth } from '../lib/firebase.js';
import { getUserLoggedIn } from '../store/user.js';
import configureAppStore from '../store/configureAppStore.js';
import Header from '../components/nav/Header.jsx';
const store = configureAppStore();
function MyApp({ Component, pageProps }) {
const dispatch = useDispatch();
// to check firebase auth state
useEffect(() => {
const unsubscribe = auth.onAuthStateChanged(async (user) => {
if (user) {
const idTokenResult = await user.getIdTokenResult();
dispatch(
getUserLoggedIn({
email: user.email,
token: idTokenResult.token,
})
);
}
});
// cleanup
return () => unsubscribe();
}, []);
return (
<>
<Provider store={store}>
<Header />
<Component {...pageProps} />
</Provider>
</>
);
}
export default MyApp;

A simple solution to this problem could be creating an empty component that runs the auth for you in a useEffect.
import React, { useEffect } from 'react';
import { Provider, useDispatch } from 'react-redux';
import { auth } from '../lib/firebase.js';
import { getUserLoggedIn } from '../store/user.js';
import configureAppStore from '../store/configureAppStore.js';
import Header from '../components/nav/Header.jsx';
const store = configureAppStore();
function MyApp({ Component, pageProps }) {
const AuthComponent = React.memo(() => {
const dispatch = useDispatch();
// to check firebase auth state
useEffect(() => {
const unsubscribe = auth.onAuthStateChanged(async (user) => {
if (user) {
const idTokenResult = await user.getIdTokenResult();
dispatch(
getUserLoggedIn({
email: user.email,
token: idTokenResult.token,
})
);
}
});
// cleanup
return () => unsubscribe();
}, []);
return null;
})
return (
<>
<Provider store={store}>
<AuthComponent />
<Header />
<Component {...pageProps}
/>
</Provider>
</>
);
}
export default MyApp;

Related

useEffect either doesn't have a dependency array, or dependencies changes on every render

Appjs
import { useEffect } from "react";
import { BrowserRouter, Routes, Route } from "react-router-dom";
import "./App.css";
import Header from "./components/Header";
import Home from "./components/Home";
import Login from "./components/Login";
import { getUserAuth } from "./actions";
import { connect } from "react-redux";
function App(props) {
useEffect(() => {
props.getUserAuth();
}, []);
return (
<div className="App">
<BrowserRouter>
<Header />
<Routes>
<Route exact path="/" element={<Login />} />
<Route path="/home" element={<Home />} />
</Routes>
</BrowserRouter>
</div>
);
}
const mapStateToProps = (state) => {
return {};
};
const mapDispatchToProps = (dispatch) => ({
getUserAuth: () => dispatch(getUserAuth()),
});
export default connect(mapStateToProps, mapDispatchToProps)(App);
Next file
import { auth, provider } from "../firebase";
import { signInWithPopup, onAuthStateChanged, signOut } from "firebase/auth";
import { SET_USER } from "./actionType";
export const setUser = (payload) => ({
type: SET_USER,
user: payload,
});
export function signInAPI() {
return (dispatch) => {
signInWithPopup(auth, provider)
.then((payload) => {
console.log(payload.user);
// dispatch(setUser(payload.user));
})
.catch((error) => alert(error.message));
};
}
export function getUserAuth() {
return (dispatch) => {
onAuthStateChanged(auth, (user) => {
console.log(user);
if (user) {
dispatch(setUser(user));
}
});
};
}
export function signOutAPI() {
return (dispatch) => {
signOut(auth)
.then(() => {
dispatch(setUser(null));
})
.catch((error) => {
console.log(error.message);
});
};
}
Is my signInAPI, getUserAuth() and signOutAPI() functions correct?
Getting error react_devtools_backend.js:3973 Warning: Maximum update depth exceeded. This can happen when a component calls setState inside useEffect, but useEffect either doesn't have a dependency array, or one of the dependencies changes on every render.
onAuthStateChanged is a subscription, it's meant to be called in an useEffect hook to establish a subscription, i.e. receive updates, when the authentication state changes. Instead of invoking it in an action invoke it in an useEffect hook. The onAuthStateChanged callback should then dispatch actions to your Redux store to update local state.
Example:
import { onAuthStateChanged } from "firebase/auth";
import { auth } from "../firebase";
useEffect(() => {
// Subscribe to auth state changes when component mounts
const unsubscribe = onAuthStateChanged(auth, (user) => {
console.log(user);
dispatch(setUser(user)); // user is user object or null
});
// Return cleanup function to unsubscribe from firebase auth
return unsubscribe;
}, []);

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

Update Context state before component loads

Here I am using react-router and using conditionals for multiple routes. I am using context to store my state. I want to load my data from localStorage to the state before my App.js is loaded as based on that it tells on which routes should I navigate to.
userContext.js
import React, { useState, createContext, useEffect } from "react";
export const userContext = createContext();
export const UserProvider = (props) => {
const [user, setUser] = useState(null);
const loadFromLocalStorage = () => {
try {
const serializedState = localStorage.getItem("state");
if (serializedState == null) return undefined;
return JSON.parse(serializedState);
} catch (err) {
console.log(err);
return null;
}
};
useEffect(() => {
const state = loadFromLocalStorage();
console.log("state: ", state);
setUser(state);
}, []);
return (
<userContext.Provider value={[user, setUser]}>
{props.children}
</userContext.Provider>
);
};
export default userContext;
App.js
import React, { useContext, useEffect } from "react";
import { Switch, Route, Redirect } from "react-router";
import userContext from "./context/userContext";
const App = () => {
var routes = null;
const [user, setUser] = useContext(userContext);
console.log("app: ", user);
console.log(routes);
if (user == null) {
routes = (
<div>
<Switch>
<Route path="/login" component={SignUp} />
<Redirect to="/login"></Redirect>
</Switch>
</div>
);
} else {
routes = (
<div>
<Switch>
<Route exact path="/news" component={NewsList} />
<Redirect to="/news"></Redirect>
</Switch>
</div>
);
}
return <div className="App">{routes}</div>;
};
export default App;
Inside your UserProvider, don't render the children until you've loaded data from localstorage. This will require some extra state, but nothing too bad, and this is a common pattern for asynchronous context/state:
export const UserProvider = (props) => {
const [isReady, setIsReady] = useState(false);
// other code removed for brevity
useEffect(() => {
const state = loadFromLocalStorage();
setIsReady(true);
}, [])
return (
<userContext.Provider value={[user, setUser]}>
{isReady ? props.children : null}
</userContext.Provider>
);
}

How can I access my context API state in react app.js file

Hi I am managing my react app state with Context API, and I am able to destructure of the state and functions of my Context in all files except my app.js file.
Here I created a reusable file called 'createDataContext' to prevent writing duplicate code when creating a Context and Provider for each Context.
import React, { createContext, useReducer } from 'react';
export default (reducer, actions, initialState) => {
const Context = createContext();
const Provider = ({ children }) => {
const [state, dispatch] = useReducer(reducer, initialState);
const boundActions = {};
for(let key in actions){
boundActions[key] = actions[key](dispatch);
}
return (
<Context.Provider value={{ state, ...boundActions }}>
{ children }
</Context.Provider>
)
}
return { Context, Provider }
}
Here is my AuthContext which uses createDataContext file
import createDataContext from "./createDataContext";
import api from '../api/api'
const authReducer = (state, action) => {
switch(action.type){
case 'signin':
return { errorMessage: '', token: action.payload };
case 'add_error':
return { ...state, errorMessage: action.payload };
default:
return state;
}
}
const login = dispatch => {
return async (email, password) => {
try {
const res = await api.post('/users/login', { email, password });
localStorage.setItem('token', res.data.token);
dispatch({ type: 'signin', payload: res.data.token });
}catch(err){
dispatch({ type: 'add_error', payload: 'signin failed' })
}
}
}
export const { Context, Provider } = createDataContext(authReducer, { login }, {
token: null,
errorMessage: ''
});
I am able to use the context and destructor off the state and function like login in my pages and components but unable to do so in my app.js file.
import React, { useContext } from 'react';
import Home from './pages/home/Home';
import Login from './pages/login/Login';
import Profile from './pages/profile/Profile';
import Register from './pages/register/Register';
import { BrowserRouter as Router, Redirect, Route, Switch } from 'react-router-dom';
import Navbar from './components/navbar/Navbar';
import { Provider as AuthProvider } from './context/AuthContext';
import { Context } from './context/AuthContext';
function App() {
const { state, login } = useContext(Context);
return (
<AuthProvider>
<Router>
<Navbar />
<Switch>
<Route exact path="/">
<Home />
</Route>
<Route path="/login">
<Login />
</Route>
<Route path="/register">
<Register />
</Route>
<Route path="/profile/:id">
<Profile />
</Route>
</Switch>
</Router>
</AuthProvider>
);
}
export default App;
I am getting error TypeError: Cannot destructure property 'state' of 'Object(...)(...)' as it is undefined. Can someone please point out what I'm or where I'm going wrong. Thanks.
You cannot use the context in the same component where it is being provided. To get around this create another new component that is a wrapper for the rest of the children of the Provider
// AppWrapper.jsx
import React from 'react'
import App from './app'
import { Provider as AuthProvider } from './context/AuthContext'
const AppWrapper = () => {
return (
<AuthProvider>
<App />
</AuthProvider>
)
}
default export AppWrapper
Then remove the <AuthProvider> from your app.js component, and wherever you were calling <App /> in the tree, call the <AppWrapper /> instead. Then inside of the App you can use the Context like you are trying to do.

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