I am trying to use context with my Gatsby project. I have successfully implemented this in my previous project and I have copied the code over to my new project and it's not working as intended.
This is my context.js file:
import React, { useContext, useState } from "react";
const defaultState = {
isLoggedIn: false,
};
const AuthContext = React.createContext();
export function useAuth() {
return useContext(AuthContext);
}
export function AuthProvider({ children }) {
const [isLoggedIn, setIsLoggedIn] = useState(false);
function toggle() {
console.log("BOO!");
}
const value = {
isLoggedIn,
setIsLoggedIn,
};
return <AuthContext.Provider value={value}>{children}</AuthContext.Provider>;
}
This is my app.js file:
import React from "react";
import { Router } from "#reach/router";
import IndexPage from "./index";
import ProjectPage from "./project";
import { AuthProvider } from "../contexts/context";
const App = () => (
<AuthProvider>
<Router basepath="/app">
<IndexPage path="/" component={IndexPage} />
<ProjectPage path="/project" component={ProjectPage} />
</Router>
</AuthProvider>
);
export default App;
This is my index.js file:
import React, { useContext } from "react";
import { Link } from "gatsby";
import { useAuth } from "../contexts/context";
import { AuthContext } from "../contexts/context";
const IndexPage = () => {
console.log(useAuth())
return (
<div className="w-40 h-40 bg-red-400">
{/*<Link to="/project">to projects</Link>*/}
<div>Click me to toggle: uh</div>
</div>
);
};
export default IndexPage;
useAuth() should return the desired components and functions but instead is always returning undefined. I have looked over my previous code as well as snippets on stack overflow and I can't seem to find the correct fix.
The following includes code that successfully built and executed:
Original context.js
import '#stripe/stripe-js'
/* Functionality */
import React, { useContext, useEffect, useState } from "react";
import { navigate } from "#reach/router";
import firebase from 'gatsby-plugin-firebase';
import { useLocalStorage } from 'react-use';
const AuthContext = React.createContext()
export function useAuth() {
return useContext(AuthContext)
}
export function AuthProvider({ children }) {
const [isLoggedIn, setIsLoggedIn] = useState(false)
const [isLoading, setIsLoading] = useLocalStorage("loading", false);
// Sign In
const signInWithRedirect = (source) => {
let provider;
switch(source) {
case 'Google':
provider = new firebase.auth.GoogleAuthProvider()
break;
case 'Github':
provider = new firebase.auth.GithubAuthProvider()
break;
default:
break;
}
setIsLoading(true)
firebase.auth().setPersistence(firebase.auth.Auth.Persistence.SESSION)
.then(() => {
// Existing and future Auth states are now persisted in the current
// session only. Closing the window would clear any existing state even
// If a user forgets to sign out.
// ...
// New sign-in will be persisted with session persistence.
return firebase.auth().signInWithRedirect(provider)
})
.catch((error) => {
// Handle Errors here.
let errorCode = error.code;
let errorMessage = error.message;
});
}
// Sign Out
const signOut = () => {
firebase.auth().signOut().then(() => {
// Sign-out successful.
setIsLoggedIn(false)
navigate('/app/login')
}).catch((error) => {
// An error happened.
});
}
useEffect(() => {
firebase.auth().onAuthStateChanged((user) => {
try {
// If user is authenticated
if (!!user) {
// Fetch firestore document reference
var docRef = firebase.firestore().collection("study_guide_customers").doc(user.uid)
docRef.get().then((doc) => {
console.log('checking doc')
// If the document doesn't exist, create it and add to the firestore database
if (!doc.exists) {
console.log('inside customer')
const customer = {
customerCreationTimestamp: firebase.firestore.Timestamp.now(),
username: user.displayName,
email: user.email
}
firebase.firestore().collection("study_guide_customers").doc(user.uid).set(customer)
.then(() => {
// After docuement for user is created, set login status
setIsLoggedIn(!!user)
setIsLoading(false)
})
.catch((error) => {
console.error("Error writing document: ", error);
});
// If document for user exists, set login status
} else {
setIsLoggedIn(!!user)
setIsLoading(false)
}
})
}
} catch {
console.log('Error checking firestore existence and logging in...')
}
})
}, [isLoggedIn, isLoading, setIsLoading, setIsLoggedIn])
const value = {
signOut,
isLoggedIn,
isLoading,
setIsLoading,
setIsLoggedIn,
signInWithRedirect,
}
return (
<AuthContext.Provider value={value}>
{children}
</AuthContext.Provider>
)
}
Original app.js
/* Stripe Security */
import '#stripe/stripe-js'
/* Functionality */
import React from "react"
import { Router } from "#reach/router"
import PrivateRoute from "../components/PrivateRoute"
import Profile from "../components/Profile"
import Login from "../components/Login"
import Projects from "../components/Projects"
import IndexPage from "./index"
import NotFoundPage from './404'
import { AuthProvider } from "../contexts/context"
const App = () => (
<AuthProvider>
<Router basepath="/app">
<PrivateRoute path="/profile" component={Profile} />
<Login path="/login" component={Login}/>
<IndexPage path="/" component={IndexPage}/>
<Projects path="/projects" component={Projects} />
</Router>
</AuthProvider>
)
export default App
Original index.js
/* Stripe Security */
import '#stripe/stripe-js'
/* Functionality */
import * as React from "react"
import IndexContact from "../components/Index/Contact"
import IndexSelectedProjects from "../components/Index/SelectedProjects"
import IndexFeaturedProjects from "../components/Index/FeaturedProjects"
import IndexFooter from "../components/Index/Footer"
import IndexStudyGuide from "../components/Index/StudyGuide"
import IndexNavbar from "../components/Index/Navbar"
import IndexHeader from "../components/Index/Header"
import IndexAbout from '../components/Index/About'
import IndexExperience from '../components/Index/Experience'
import { useMount } from 'react-use';
const IndexPage = () => {
useMount(() => localStorage.setItem('loading', false));
return (
<>
<IndexNavbar />
<IndexHeader />
<IndexAbout />
<IndexExperience />
<IndexFeaturedProjects />
<IndexSelectedProjects />
<IndexStudyGuide />
<IndexContact />
<IndexFooter />
</>
)
}
export default IndexPage
Then in any component I could simply use the following code to access the context
import { useAuth } from "../contexts/context"
const { isLoggedIn, signInWithRedirect, isLoading } = useAuth()
Child components are mounted before parent. Fix your context.js file to add a default value for isLoggedIn state:
const defaultState = {
isLoggedIn: false,
setIsLoggedIn: () => {}
};
const AuthContext = React.createContext(defaultState);
Your defaultState should also include default methods for any parts of the context you wish to work with.
Related
I'm attempting to move my data fetching to custom hooks off of the component file for better code organization and I'm having issues with the hook not working when used in conjunction with my context.
This is my AuthContext.tsx file
import React, { createContext, useEffect, useState } from 'react';
import { onAuthStateChanged } from 'firebase/auth';
import { auth } from '../firebase.config';
export const UserContext = createContext<any>({});
export const AuthContextProvider = ({ children }: any) => {
const [currentUser, setCurrentUser] = useState<unknown | null>({});
useEffect(() => {
const unsub = onAuthStateChanged(auth, (user) => {
setCurrentUser(user);
});
return () => {
unsub();
};
}, []);
return <UserContext.Provider value={{ currentUser }}>{children}</UserContext.Provider>;
};
This is my useRooms.ts file (my hook)
import * as React from 'react';
import { collection, query, where, getDocs, QueryDocumentSnapshot, DocumentData } from 'firebase/firestore';
import { db } from '../firebase.config';
import { UserContext } from '../context/AuthContext';
import { RoomsResult } from './types';
export default function useRooms(): RoomsResult {
const [userRooms, setUserRooms] = React.useState<string[] | null>(null);
const [isLoading, setIsLoading] = React.useState(true);
const { currentUser } = React.useContext(UserContext);
const userRoomsQuery = query(collection(db, 'rooms'), where('user', 'array-contains', currentUser.uid));
const fetchUserRoomsData = async () => {
setIsLoading(true);
const userRoomsDocs = await getDocs(userRoomsQuery);
setUserRooms(userRoomsDocs.docs.map((doc: QueryDocumentSnapshot<DocumentData>) => doc.id));
setIsLoading(false);
};
console.log(userRooms);
return { rooms: userRooms, loading: isLoading, fetchUserRoomsData };
}
The error I recieve is:
As for those file names seen in the error they are all from my App.tsx file here
import React, { useContext } from 'react';
import { BrowserRouter, Routes, Route, Navigate } from 'react-router-dom';
import { UserContext } from './context/AuthContext';
import { SignUpForm, LogInForm, ChatRooms } from './components/exporter';
type Styles = {
wrapper: string;
};
function App() {
const styles: Styles = {
wrapper:
'bg-purple-200 h-[100vh] w-[100vw] grid grid-cols [minmax(100px,_250px)_1fr_minmax(150px,_250px)] grid-rows-[85%_minmax(50px,_350px)] absolute',
};
const { currentUser } = useContext(UserContext);
const ProtectedRoute = ({ children }: any) => {
if (!currentUser) {
return <Navigate to="/login" />;
} else return children;
};
return (
<BrowserRouter>
<Routes>
<Route
path="/"
element={
<ProtectedRoute>
<div id="portal-container">
<ChatRooms />
</div>
</ProtectedRoute>
}
></Route>
<Route path="login" element={<LogInForm />} />
<Route path="signup" element={<SignUpForm />} />
<Route path="*" element={<Navigate to="/" replace />} />
</Routes>
</BrowserRouter>
);
}
export default App;
The hook itself works as long as I do not use the line const { currentUser } = React.useContext(UserContext); inside the useRooms.ts file, but soon as I do I receive the error in picutre. I place the use of the custom Hook useRooms inside of my <ChatRooms/> component which can be seen is rendered as the most nested element in App.tsx. Any idea as to what is causing this and why ?
I managed to fix the issue by setting the default state of currentUser in AuthContextProvider to null instead of {}
Unfortunately this makes it so that when I refresh my web page it now forgets the logged in user. But this may be intended behavior until I check the Firebase Auth docs.
The code works but console log shows it renders twice. I want to reduce unnecessary rerenders/API fetching. Solutions tried include async-await, try-catch, useMemo, React.Memo, StrictMode in index.js (just quadruples the console log entries).
I prepared a codesandbox.
This is the console log:
Console logs twice every time
In the sandbox I use a fake API compared to my actual project code which uses Axios. The outcome is the same.
SearchContext (Context Provider) fetches from API once, then BlogList (Context Consumer) fetches again and console logs the second time.
React Version: 18.2.0
SearchContext.js
import React, { createContext, useState, useEffect, useMemo, useCallback } from 'react';
import axios from 'axios';
const SearchContext = createContext();
export const SearchContextProvider = ({ children }) => {
const [blog, setBlog] = useState([]);
const value = useMemo(() => ([ blog, setBlog ]), [blog]);
// const value = React.memo(() => ([ blog, setBlog ]), [blog]);
// I try 'useMemo, React.memo' (above):
// I try 'try-catch':
// const retrieveBlog = () => {
// try {
// const response = axios.get(`${process.env.REACT_APP_API_URL}/api/v2/pages/?type=blog.Blog&fields=image,description`)
// .then(response => {
// setBlog(response.data.items);
// console.log('thisiscontextprovider(blog):', blog)
// })
// }
// catch (err) {
// console.log(err);
// }
// }
// useEffect(() => {
// retrieveBlog();
// }, []);
// I try 'useCallback, if, async-await, try-catch' :
const retrieveBlog = useCallback(async () => {
if (blog.length === 0){
try {
const response = await axios.get(`${process.env.REACT_APP_API_URL}/api/v2/pages/?type=blog.Blog&fields=image,description`)
.then(response => {
setBlog(response.data.items);
console.log('thisiscontextprovider(blog):', blog)
})
}
catch (err) {
console.log(err);
}
}
})
useEffect(() => {
retrieveBlog()
}, []);
// I try 'async-await':
// useEffect(() => {
// async function retrieveBlog() {
// try {
// const response = await axios.get(`${process.env.REACT_APP_API_URL}/api/v2/pages/?type=blog.Blog&fields=image,description`)
// .then(response => {
// setBlog(response.data.items);
// console.log('thisiscontextprovider(blog):', blog)
// })
// }
// catch (err) {
// console.log(err);
// }
// }
// retrieveBlog()
// }, []);
return (
<div>
<SearchContext.Provider value={value}>
{/* <SearchContext.Provider value={[blog, setBlog]}> */}
{children}
{console.log('from context provider return:', blog)}
</SearchContext.Provider>
</div>
);
};
export default SearchContext;
BlogList.js
import React, { useContext, useCallback, useEffect } from 'react';
import styled from 'styled-components';
import SearchContext from "../contexts/SearchContext";
import { SearchContextProvider } from "../contexts/SearchContext";
const BlogCard = styled.div`
`;
const BlogList = () => {
const [blog, setBlog] = useContext(SearchContext);
return (
<div>
<SearchContextProvider>
BlogList
<BlogCard>
{blog.map((blog) => (
<li key={blog.id}>
{blog.title}
</li>
))}
</BlogCard>
</SearchContextProvider>
</div>
);
};
export default BlogList;
App.js
import React from "react";
import { Route, Routes } from "react-router-dom";
import styled from "styled-components";
import BlogList from "./components/BlogList";
import { SearchContextProvider } from "./contexts/SearchContext";
const Wrapper = styled.h1``;
function App() {
return (
<Wrapper>
<p>
This CodeSandbox is for my question on StackOverflow. Console logs
twice.
</p>
<SearchContextProvider>
<Routes>
<Route exact path="" element={<BlogList />} />
{/* <Route exact path="/blog" element={<BlogList />} /> */}
</Routes>
</SearchContextProvider>
</Wrapper>
);
}
export default App;
index.js
import React from 'react';
import ReactDOM from 'react-dom/client';
import { BrowserRouter } from "react-router-dom";
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<BrowserRouter>
<App />
</BrowserRouter>
);
reportWebVitals();
I am keen to understand why this happens, rather than just solving it.
I am new to context api. I am trying to use multiple context.
I have created a UserContext and GlobalContext which I'm trying to use in the app.js but as soon as I put UserContext I get the blank page.
here is my app.js
import { useEffect } from 'react';
import { GlobalProvider } from './context/GlobalState'
import './App.css'
import { Home } from './pages/Home';
import { Login } from './pages/Login';
import { Register } from './pages/Register';
import axios from 'axios';
import { BrowserRouter, Routes, Route } from 'react-router-dom';
import { UserProvider } from './context/UserContext';
const App = () => {
return (
<UserProvider>
<GlobalProvider>
<BrowserRouter>
<Routes>
<Route path='/login' element={<Login />} />
<Route path='/register' element={<Register />} />
<Route path='/home' element={<Home />} />
</Routes>
</BrowserRouter>
</GlobalProvider>
</UserProvider>
)
}
export default App;
Here is my UserContext.js
import axios from "axios";
import { createContext, useReducer } from "react";
import { userReducer } from "./AppReducer";
// Initial State
const initialState = {
isAuthenticated: false
}
// Creating Context
export const UserContext = createContext(initialState)
UserContext.displayName = "UserContext"
// Creating Provider
export const UserProvider = ({ childern }) => {
const [state, dispatch] = useReducer(userReducer, initialState);
// Actions
async function authenticate(user) {
try {
const config = {
Headers: {
'Content-Type': 'application/json'
}
}
const hasToken = res.data.token === undefined ? false : res.data.token;
dispatch({
type: 'AUTHENTICATE_USER',
payload: hasToken
})
const res = await axios.post('https://localhost:7188/api/user/authenticate', user, config);
if (res.data.token !== undefined) {
console.log("No token")
}
else {
console.log(res.data.token);
}
localStorage.setItem('jwt', res.data.token);
console.log(localStorage.getItem('jwt'));
} catch (error) {
console.log(error.response);
}
}
return (
<UserContext.Provider value={
{
isAuthenticated: state.isAuthenticated,
authenticate
}
}>
{childern}
</UserContext.Provider>
)
}
Here is AppReducer.js
export const userReducer = (state, action) => {
switch (action.type) {
case 'AUTHENTICATE_USER':
return {
...state,
isAuthenticated : action.payload
}
default:
return state;
}
}
When I start the server I'm getting blank page
I'm currently attempting to build an 'AuthContext' so I can use it in various screens and pass the data down.
I thought I'd built it right.. But when I try to call one of the functions in my Provider, it's throwing a component exception, stating 'element type is invalid: expected a string or a class/function but got undefined'.
Here is the context file:
import React, { useState, useContext } from 'react';
import { navigate } from '../navigationRef';
import { Magic } from '#magic-sdk/react-native';
const m = new Magic('api key');
export const AuthContext = React.createContext();
export const AuthProvider = ({ children }) => {
const [user, setUser] = useState([]);
const userSignedIn = async () => {
// Call Magic logged in
const loggedIn = await m.user.isLoggedIn();
// If user logged in, save details to user, and redirect to dashboard
if (loggedIn === true) {
const { issuer, email } = await m.user.getMetaData();
setUser([issuer, email])
navigate('authorisedFlow')
// If user not logged in, redirect to login flow
} else {
navigate('loginFlow')
}
};
const signIn = () => {
};
const signUp = () => {
};
const logOut = () => {
};
return (
<AuthContext.Provider value={{ user, userSignedIn, signIn, signUp, logOut }}>
{ children }
</AuthContext.Provider>
)
}
And here is the component which is attempting to use the context:
import React, { useContext, useEffect } from 'react';
import { View, StyleSheet, ActivityIndicator } from 'react-native';
import AuthContext from '../context/AuthContext';
const LoadingScreen = ({ navigation }) => {
const { userSignedIn } = useContext(AuthContext)
useEffect(() => {
userSignedIn()
}, [])
return (
<View style={styles.mainView}>
<ActivityIndicator style={styles.indicator} />
</View>
)
}
And finally, here is my app.js file (cut most of it out due to length, but wanted to show Provider):
import { Provider as AuthProvider } from './src/context/AuthContext';
const App = createAppContainer(switchNavigator)
export default () => {
return (
<AuthProvider>
<App />
</AuthProvider>
)
};
Can anyone see what's going wrong here?
You exported your AuthContext as a named-export ... but you're importing a default-export
import AuthContext from '../context/AuthContext'; // <--- Here
const LoadingScreen = ({ navigation }) => {};
Instead...
import { AuthContext} from '../context/AuthContext';
Same goes for this one as well...
import { Provider as AuthProvider } from './src/context/AuthContext';
Which should be
import { AuthContext: { Provider as AuthProvider } } from './src/context/AuthContext';
OR
import { AuthContext } from './src/context/AuthContext';
return (
<AuthContext.Provider>
<App />
</AuthContext.Provider>
)
I'm trying to control the page using react-hook, react-router-dom, redux.
The login screen is implemented and the code that tries to switch to the main page when the login is successful is written.
So I used history.push of react-router.
However, history.push only changes the browser url, not the actual information displayed on the page.
The code is shown below.
In brief code explanation,
The id and password are put into redux and get as getelementbyuId and sent as form data.
The login function works normally.
history.push ('/') written to "userUpdateUserInfo" doesn't work at all.
Only the url of the browser changes, not the main page.
App.tsx
import React from 'react';
import { Route, Switch } from 'react-router-dom';
import { Main, Login } from './pages';
import './css/App.css';
const App: React.FC = () => {
return (
<div>
<div className="App-contents-area">
<Switch>
<Route exact path="/" component={Login} />
<Route exact path="/main" component={Main} />
{/* <Redirect path="*" to="/" /> */}
</Switch>
</div>
</div>
);
}
export default App;
LoginPage.tsx
import React from 'react';
import { Login } from 'Component';
function LoginPage() {
return (
<Login />
);
}
export default LoginPage;
Login.tsx (components)
import React from 'react';
import {
LoginTitle, LoginAvatar, LoginUserId, LoginUserPassword, LoginButton
} from '..';
import '../../css/Login.css';
function Login() {
return (
<div className="Login">
<div className="Login-form-data">
<LoginTitle /> // code skip
<LoginAvatar /> // code skip
<LoginUserId /> // code skip
<LoginUserPassword /> // code skip
<LoginButton />
</div>
</div>
);
}
export default Login;
LoginButton.tsx (components)
import React from 'react';
import { useUpdateUserInfo } from 'Hook';
function LoginButton() {
const { handleLogin } = useUpdateUserInfo(); // custom hook
return (
<div className="LoginButton">
<button className="LoginButton-button" onClick={handleLogin}>Login</button>
</div>
);
}
export default LoginButton;
userUpdateUserInfo.tsx (custom hook)
import { useCallback } from 'react';
import { useSelector, useDispatch } from 'react-redux';
import { RootState } from 'Store/modules';
import { updateUserInfo } from 'Store/modules/user';
import { userLoginStatus } from 'Store/modules/login';
import { msg } from 'Lang';
import {
axiosPost, history,
_ShowFail, _ShowSuccess, _ShowSelect
} from 'Module';
export default function useUpdateUserInfo () {
const { id, name, tel, email } = useSelector((state: RootState) => state.user);
let { isLogin } = useSelector((state: RootState) => state.login);
const dispatch = useDispatch();
const handleLogin = useCallback(async () => {
try {
const userId: string = (document.getElementById('LoginUserId-id') as HTMLInputElement).value.trim();
const userPw: string = (document.getElementById('LoginUserPassword-password') as HTMLInputElement).value.trim();
if (!userId.length) { return _ShowFail(msg.pleaseInputUserId); }
if (!userPw.length) { return _ShowFail(msg.pleaseInputUserPassword); }
const formData: FormData = new FormData();
formData.append('userId', userId);
formData.append('userPw', userPw);
const url = '/login/check-login-info';
const config = {
headers: {
'Content-Type': 'multipart/form-data',
},
};
const res = await axiosPost(url, formData, config);
if (res.data.res) {
_ShowSuccess('login success');
const userInfo = {
id: res.data.rows[0].id,
name: res.data.rows[0].name,
email: res.data.rows[0].email,
tel: res.data.rows[0].tel,
};
isLogin = true;
/**************************/
history.push('/main'); // now working
/**************************/
dispatch(updateUserInfo(userInfo));
dispatch(userLoginStatus({ isLogin }));
}
else {
_ShowFail('login fail');
isLogin = false;
dispatch(updateUserInfo({ id, email, name, tel }));
dispatch(userLoginStatus({ isLogin }));
}
}
catch (error) {
_ShowFail(error.message);
}
}, [dispatch]);
return { handleLogin };
};
MainPage.tsx
import React from 'react';
function MainPage() {
return (
<div>
<h2>MainPage!!</h2>
</div>
);
}
export default MainPage;
history.tsx
import { createBrowserHistory } from 'history'
export default createBrowserHistory();
Since last night, I have been suffering from this problem.
How can I change the content of the page?
In App.js your Routes suppose to be inside < Router > < /Router >,
https://reacttraining.com/react-router/web/api/Router
(unless you wrap it on index.js that not including here and App is imported inside there)?