Edit:
I was not using
import Link from 'next/link';
in my Header.tsx component.
Now it works.
Don't know what I am doing wrong right here.
I try to make global state (to indicate if user is logged in or not) that just flows through the pages and I try to do it with react's hook useContext.
It is not working like how I would like to make it work. When I toggleLogged and go to another page, the context has default value and not the changed one.
I think the problem I am facing is something really small or a fundemantal thing that I just can't see.
Here is how my UserContext.ts file looks:
import { createContext, useContext } from 'react';
type userContextType = {
isLogged: boolean;
toggleLogged: () => void;
};
const userContextDefault: userContextType = {
isLogged: false,
toggleLogged: () => {},
};
export const UserContext = createContext(userContextDefault);
export function useUserContext() {
return useContext(UserContext);
}
Here is my Layout.tsx:
import React, { useState } from 'react';
import Header from './Header';
const Layout = (props: any) => {
const { children } = props;
return (
<div className='container'>
<Header />
{children}
</div>
);
};
export default Layout;
And lastly here is my _app.tsx:
import type { AppContext, AppProps } from 'next/app';
import Layout from '../components/Layout';
import { useState } from 'react';
import { UserContext } from '../components/UserContext';
import '../styles/globals.css';
const MyApp = ({ Component, pageProps }: AppProps) => {
const [isLogged, setIsLogged] = useState(true);
const toggleLogged = () => {
setIsLogged((isLogged) => !isLogged);
};
return (
<UserContext.Provider value={{ isLogged, toggleLogged }}>
<Layout>
<Component {...pageProps} />;
</Layout>
</UserContext.Provider>
);
};
export default MyApp;
Thanks for any help in advance.
You need to return the provider in the context, and then reference the instance in _app.js.
Here's my AuthContext (as an example). Don't worry about the specific code, but use my implementation as the foundation, and you'll be up and running in no time!
import { createContext, useContext, useEffect, useState } from 'react'
import { auth } from '../firebase'
const AuthContext = createContext()
export function useAuth() {
return useContext(AuthContext)
}
export function AuthProvider({ children }) {
const [currentUser, setCurrentUser] = useState()
const [loading, setLoading] = useState(true)
function login(email, password) {
return auth.signInWithEmailAndPassword(email, password)
}
function signOut() {
return auth.signOut();
}
function signUp(email, password) {
return auth.createUserWithEmailAndPassword(email, password)
}
function getUser() {
return auth.currentUser
}
function isAdmin() {
return auth.currentUser.getIdTokenResult()
.then((idTokenResult) => {
if (!!idTokenResult.claims.admin) {
return true
} else {
return false
}
})
}
function isEditor() {
}
useEffect(() => {
const unsubscribe = auth.onAuthStateChanged(user => {
setCurrentUser(user)
setLoading(false)
})
return unsubscribe
}, [])
const value = {
currentUser,
getUser,
login,
signOut,
signUp
}
return (
<AuthContext.Provider value={value}>
{ !loading && children }
</AuthContext.Provider>
)
}
My _app.js file:
import '../styles/globals.scss'
import { motion, AnimatePresence } from 'framer-motion'
import { useRouter } from 'next/router'
import Header from '../components/Header'
import Footer from '../components/Footer'
import { AuthProvider } from '../contexts/AuthContext'
import { CartProvider } from '../contexts/CartContext'
import { ThemeProvider } from '#material-ui/core'
import theme from '../styles/theme'
export default function App({ Component, pageProps }) {
const router = useRouter()
return(
<AnimatePresence exitBeforeEnter>
<CartProvider>
<AuthProvider>
<ThemeProvider theme={theme}>
<Header />
<motion.div key={router.pathname} className="main">
<Component { ...pageProps } />
<Footer />
</motion.div>
</ThemeProvider>
</AuthProvider>
</CartProvider>
</AnimatePresence>
)
}
Related
When I try to use createContext() the console gives me this error:
App.js:6
Uncaught TypeError: Cannot destructure property 'consoleLogFunction' of '(0 , react__WEBPACK_IMPORTED_MODULE_1__.useContext)(...)' as it is null.
I've seen others asking questions about this here in Stack Overflow but I can't find a solution.
GlobalContext.js
import React from 'react'
import { createContext } from 'react'
export const AppContext = createContext();
function GlobalContext() {
const consoleLogFunction = () => {
console.log("ok")
}
return (
<AppContext.Provider value={{consoleLogFunction}}></AppContext.Provider>
)
}
export default GlobalContext
App.js
import "./index.css";
import { useContext, useEffect } from "react";
import { AppContext } from "./components/GlobalContext";
function App() {
const { consoleLogFunction } = useContext(AppContext);
useEffect(() => {
consoleLogFunction();
}, []);
return (
<AppContext>
<div>home</div>
</AppContext>
);
}
export default App;
You don't need to export 'AppContext', creating the provider and exporting that is good enough.
Try this, I've also made a couple of modifications to make it easier to use the context later:
GlobalContext.js
import React from 'react'
import { createContext, useContext } from 'react'
const AppContext = createContext();
function GlobalContext({ children }) {
const consoleLogFunction = () => {
console.log("ok")
}
return (
<AppContext.Provider value={{consoleLogFunction}}>
{ children }
</AppContext.Provider>
)
}
export default GlobalContext;
// This is a helper
export const useGlobalContext = () => useContext(AppContext);
Home.js
import { useEffect } from "react";
import { useGlobalContext } from "./components/GlobalContext";
export const Home = ({ children }) => {
const { consoleLogFunction } = useGlobalContext();
useEffect(() => {
consoleLogFunction();
}, []);
return(
<div>home</div>
)
};
App.js
import "./index.css";
import { useEffect } from "react";
import GlobalContext from "./components/GlobalContext"
import { Home } from "./components/Home";
function App() {
return(
<GlobalContext>
<Home />
</GlobalContext>
);
}
export default App;
hello man the problem is because App component is not wrapped inside GlobalContext . and in the GlobalContext component you should handle the children prop.
it will work when doing it like this example :
import { useEffect, useContext, createContext } from "react";
import "./styles.css";
export const AppContext = createContext();
function GlobalContext(props) {
const consoleLogFunction = () => {
console.log("ok");
};
return (
<AppContext.Provider value={{ consoleLogFunction }}>
{props.children}
</AppContext.Provider>
);
}
const Home = () => {
const { consoleLogFunction } = useContext(AppContext);
useEffect(() => {
consoleLogFunction();
}, []);
return <div>home</div>;
};
export default function App() {
return (
<GlobalContext>
<Home />
</GlobalContext>
);
}
Hope this help.
I'm using mobile-detect library and this tutorial:
link
/src/utiles/isMobile.tsx:
import MobileDetect from "mobile-detect";
import { GetServerSidePropsContext } from "next";
export const getIsSsrMobile = (context: GetServerSidePropsContext) => {
const md = new MobileDetect(context.req.headers["user-agent"] as string);
return Boolean(md.mobile());
};
/src/hooks/useIsMobile.js:
import { useContext } from "react";
import { IsSsrMobileContext } from "../contexts/isSsrMobileContext";
export const useIsMobile = () => {
const isSsrMobile = useContext(IsSsrMobileContext);
return isSsrMobile;
};
/src/contexts/isSsrMobileContext.js:
import { createContext } from "react";
export const IsSsrMobileContext = createContext(false);
my `_app.tsx`:
import { IsSsrMobileContext } from "./../src/contexts/isSsrMobileContext";
function MyApp({ Component, pageProps }: AppProps <{ isSsrMobile: boolean }>): JSX.Element {
return (
<IsSsrMobileContext.Provider value={pageProps.isSsrMobile}>
<Provider store={store}>
<Component {...pageProps} />
</Provider>
</IsSsrMobileContext.Provider>
);
}
export default MyApp;
inside my pages/index.tsx:
console.log("useIsMobile",useIsMobile())
It shows null.
I'm having problem with privateRoute it should be redirecting to the Home page after signin but it keeps redirecting to '/signin' page. everything is working fine if I remove PrivateRoute im not sure I'm beginner with react and it's my first time using firebase auth is there any way to fix this problem I'm sorry my code is messy
import React, {useEffect, useState} from 'react'
import firebase from 'firebase'
import StyledFirebaseAuth from 'react-firebaseui/FirebaseAuth'
import { Redirect, useHistory } from 'react-router-dom';
import axios from 'axios';
import { useAuth } from "../context/AuthContext";
import { Route, withRouter } from 'react-router-dom';
var uiconfig = {
signInFlow: 'popup',
signInSuccessUrl:'/',
signInOptions : [
firebase.auth.EmailAuthProvider.PROVIDER_ID],
callbacks: {
signInSuccessWithAuthResult: function(authResult, redirectUrl) {
return true;
}
}
};
const signOut = () => {
firebase.auth().signOut().then(function(){
return console.log('signout');
}).catch(() => {
console.error("error signout");
})
return <Redirect to='/signin'></Redirect>
}
var Cuser = null
const PrivateRoute = ({component: Component, ...rest}) => {
const [user, setUser] = useState(false)
const {currentUser} = useAuth()
console.log(Cuser)
return (
<Route {...rest} render={props => {
if(Cuser){
return <Component {...props}></Component>
} else {
return <Redirect to="/signin" />
}
}} ></Route>)
}
const Signup = () => {
const {history} = useHistory()
useEffect(() => {
const authOberver = firebase.auth().onAuthStateChanged((user) => {
setUser(user)
});
return authOberver
})
const [user, setUser] = useState(null)
if(user){
axios.post("http://localhost:4000/notes", {
email: user.email,
})
Cuser = true
}
return (
<>
<div>Signin / Register</div>
<StyledFirebaseAuth uiConfig={uiconfig} firebaseAuth={firebase.auth()}></StyledFirebaseAuth>
</>
)
}
export {signOut}
export default Signup;
export {PrivateRoute};
I tried to create a separate file for current user and using currentUser to check if the user is logged in or not but still happing the same problem
import React, { useContext, useEffect, useState } from "react";
import { auth } from "../firebase";
import firebase from "firebase";
import axios from 'axios'
import StyledFirebaseAuth from 'react-firebaseui/StyledFirebaseAuth'
const AuthContext = React.createContext();
export function useAuth() {
return useContext(AuthContext);
}
export function AuthProvider({ children }) {
const [currentUser, setCurrentUser] = useState();
useEffect(() => {
const unsubscribed = auth.onAuthStateChanged((user) => {
setCurrentUser(user);
})
return unsubscribed
}, []);
const value = {
currentUser,
};
return <AuthContext.Provider value={value}>{children}</AuthContext.Provider>;
}
I think the problem is that you are using the variable Cuser to decide what component to render. So even if Cuser is set to true after signup, when the component gets re-rendered, it's value gets back to being null which is why it goes to else {return <Redirect to="/signin" />} block.
You can modify you AuthContext to store the login status in state. In below code, lets say cUser:
export function AuthProvider({ children }) {
const [currentUser, setCurrentUser] = useState();
cont [cUser, setCUser] = useState(false);
useEffect(() => {
const unsubscribed = auth.onAuthStateChanged((user) => {
setCurrentUser(user);
})
return unsubscribed
}, []);
const value = {
currentUser,
cUser: cUser,
setCUser: setCUser,
};
return <AuthContext.Provider value={value}>{children}</AuthContext.Provider>;
}
And then use setCUser to set and check the value
import React, {useEffect, useState} from 'react'
import firebase from 'firebase'
import StyledFirebaseAuth from 'react-firebaseui/FirebaseAuth'
import { Redirect, useHistory } from 'react-router-dom';
import axios from 'axios';
import { useAuth } from "../context/AuthContext";
import { Route, withRouter } from 'react-router-dom';
var uiconfig = {
signInFlow: 'popup',
signInSuccessUrl:'/',
signInOptions : [
firebase.auth.EmailAuthProvider.PROVIDER_ID],
callbacks: {
signInSuccessWithAuthResult: function(authResult, redirectUrl) {
return true;
}
}
};
const signOut = () => {
firebase.auth().signOut().then(function(){
setCUser(false);
return console.log('signout');
}).catch(() => {
console.error("error signout");
})
return <Redirect to='/signin'></Redirect>
}
var Cuser = null
const PrivateRoute = ({component: Component, ...rest}) => {
const [user, setUser] = useState(false)
const {currentUser, cUser, setCUser} = useAuth()
console.log(Cuser)
return (
<Route {...rest} render={props => {
if(cUser){
return <Component {...props}></Component>
} else {
return <Redirect to="/signin" />
}
}} ></Route>)
}
const Signup = () => {
const {history} = useHistory()
useEffect(() => {
const authOberver = firebase.auth().onAuthStateChanged((user) => {
setUser(user)
});
return authOberver
})
const [user, setUser] = useState(null)
if(user){
axios.post("http://localhost:4000/notes", {
email: user.email,
})
setCUser(true);
}
return (
<>
<div>Signin / Register</div>
<StyledFirebaseAuth uiConfig={uiconfig} firebaseAuth={firebase.auth()}></StyledFirebaseAuth>
</>
)
}
export {signOut}
export default Signup;
export {PrivateRoute};
I have a small React hook that is wrapping a class component and I am trying to pass a state from this hook to the class using useEffect but I keep getting the followwing error;
TypeError: Object(...) is not a function or its return value is not iterable on the following line;
const [{}, dispatch] = useStateValue();
I use the same line in another hook elsewhere in my app so I am not sure why this one is throwing this particular error
This is the complete component code.
If anyone has any ideas why this may not be working I would be most appreciative to hear them.
Thank you.
Component
import React, { useEffect } from 'react';
import App from 'next/app';
import firebase from 'firebase';
import { useStateValue, StateProvider } from '../state';
import { firebaseConfig } from '../constants';
import { initialState, reducer } from '../reducer';
if (!firebase.apps.length) {
firebase.initializeApp(firebaseConfig);
}
function AppWrapper(Component) {
return function WrappedComponent(props) {
const [{}, dispatch] = useStateValue();
useEffect(() => {
console.log('Moiunted');
firebase.auth().onAuthStateChanged((user) => {
dispatch({
type: 'userLogin',
currentUser: user
})
});
}), [];
return <Component {...props} />
}
}
class MyApp extends App {
render() {
const { Component, pageProps } = this.props;
return (
<StateProvider
initialState={initialState}
reducer={reducer}
>
<Component {...pageProps} />
</StateProvider>
);
}
}
export default AppWrapper(MyApp)
State function
import React, { createContext, useContext, useReducer } from 'react';
export const StateContext = createContext();
export const StateProvider = ({reducer, initialState, children}) => (
<StateContext.Provider
value={useReducer(reducer, initialState)}
>
{children}
</StateContext.Provider>
);
export const useStateValue = () => useContext(StateContext);
I need to use dispatch Context API methods in _app.js.
The main limitation is that I use React hooks along with Context API, since _app.js is a Class, I can't use hooks within it.
My code:
// store.js
import React, { createContext, useContext, useReducer } from "react";
import mainReducer from "../store/reducers";
const AppStateContext = createContext();
const AppDispatchContext = createContext();
const initialState = {
filters: {
diet: {
selected: []
}
}
};
const useAppState = () => useContext(AppStateContext);
const useAppDispatch = () => useContext(AppDispatchContext);
const useApp = () => [useAppState(), useAppDispatch()];
const AppProvider = ({ children }) => {
const [state, dispatch] = useReducer(mainReducer, initialState);
return (
<AppStateContext.Provider value={state}>
<AppDispatchContext.Provider value={dispatch}>
{children}
</AppDispatchContext.Provider>
</AppStateContext.Provider>
);
};
export { AppProvider, useAppState, useAppDispatch, useApp };
// _app.js
import App from "next/app";
import React from "react";
import { AppProvider } from "../store";
class MyApp extends App {
componentDidMount() {
/***********************************/
// HERE I WOULD LIKE TO USE DISPATCH
/***********************************/
}
render() {
const { Component, router, pageProps } = this.props;
return (
<AppProvider>
<Component {...pageProps} />
</AppProvider>
);
}
}
export default MyApp;
If you really want to use hooks, then just put a wrapper around _app.js like this:
import React from 'react'
import App from 'next/app'
function MyComponent({ children }) {
// You can use hooks here
return <>{children}</>
}
class MyApp extends App {
render() {
const { Component, pageProps } = this.props
return (
<MyComponent>
<Component {...pageProps} />
</MyComponent>
)
}
}
export default MyApp