I'm attempting to import a function defined in a 'context' file so I can access the user data across the whole app easily.
When importing it, and calling it in my 'useEffect' however, it's claiming it isn't a function - when it clearly is.
For reference, here is the component which I'm calling the context function from:
import { Context } from '../context/authContext';
const LoadingScreen = ({ navigation }) => {
const { checkSignedIn } = useContext(Context)
useEffect(() => {
checkSignedIn()
}, [])
return (
<View style={styles.mainView}>
<ActivityIndicator style={styles.indicator} />
</View>
)
}
Here is the context file itself (in full):
import createDataContext from './createDataContext';
import { navigate } from '../navigationRef';
import { Magic } from '#magic-sdk/react-native';
const m = new Magic('API key');
const authReducer = (state, reducer) => {
switch (action.type) {
case 'checkComplete':
return { ...state, userDetails: action.payload };
default:
return state;
}
};
const checkSignedIn = dispatch => {
return async () => {
// Set variable which calls Magic to check if signed in
const checkMagic = await m.user.isLoggedIn();
// If user signed in, redirect to dashboard and save details in state
if (checkMagic === true) {
const { issuer, email } = await m.user.getMetaData();
dispatch({
type: 'checkComplete',
payload: [issuer, email]
});
navigate('authorisedFlow');
// If user not signed in, redirect to welcome page and set state as null
}
else {
navigate('loginFlow');
}
};
};
export const { Context, Provider } = createDataContext( authReducer, { checkSignedIn }, { userDetails: null } );
Here is the 'helper' function called 'createDataContext' which iterates over the dispatch and defines it without having to repeat create context code:
import React, { useReducer } from 'react';
export default (reducer, actions, defaultValue) => {
const Context = React.createContext();
const Provider = ({ children }) => {
const [state, dispatch] = useReducer(reducer, defaultValue);
const boundActions = {};
for (let key in actions) {
boundActions[key] = actions[key].dispatch;
}
return (
<Context.Provider value={{ state, ...boundActions }}>
{children}
</Context.Provider>
)
};
return { Context, Provider }
};
I don't think the navigate helper is necessary to show here, but happy to put in if needed. It seems to be more of a problem with the function itself!
If anybody can point out what I'm doing wrong here it'd be much appreciated!
Please let me know if any of that is unclear.
Related
I am trying to build a functionality in ReacJs (MERN) app, for my navbar link to '/admin',
to be only visible when user.isAdmin === true.
So my navbar (just an excerpt) looks the follwing:
Navbar
import { useAuthContext } from '../hooks/useAuthContext'
import { useIsAdmin } from '../hooks/useAdmin'
const Navbar = () => {
const [nav, setNav] = useState(false)
const {logout} = useLogout()
const {user} = useAuthContext()
const isAdmin = useIsAdmin()
//and on return
{ isAdmin ? <Link to='/admin'><li className='p-4 hover:text-[#00df9a] transition-all duration-500'>Admin {user.name}</li></Link> : <div></div> }
I as well made a function to get the user.isAdmin from AuthContext
useAdmin
import { useEffect, useState } from 'react';
import { useAuthContext} from '../hooks/useAuthContext';
export function useIsAdmin() {
const { user } = useAuthContext();
const [isAdmin, setIsAdmin] = useState(null);
useEffect(() => {
if (user) {
setIsAdmin(user.isAdmin && user);
}
}, [user]);
useEffect(() => {
if (!user || user === null) {
setIsAdmin(null);
}
}, [user]);
return isAdmin;
}
And this works okay, normal user does not see the /admin link, and user.isAdmin does.
However, the problem starts when I try to logout the user.isAdmin, then I receive "Uncaught TypeError: user is null"
as user changes back to Object { user: null }. On contrary,
I do not have that error, if I log out regular user, although it comes back to Object { user: null } as well.
I have tried working on my hook/function with no result, but I am guessing there is some problem with my authContext and global context for user.
So for reference my authContext file and logout.
Any hints and tips would be much appreciated.
AuthContext
import { createContext, useReducer, useEffect } from 'react'
export const AuthContext = createContext()
export const authReducer = (state, action) => {
switch (action.type) {
case 'LOGIN':
return { user: action.payload }
case 'LOGOUT':
window.localStorage.clear()
return {user: null}
case 'DELETE_USER':
return {...state, user: state.user.filter(u =>
u.id !== action.payload
)}
default:
return state
}
}
export const AuthContextProvider = ({ children }) => {
const [state, dispatch] = useReducer(authReducer, {
user: null
})
useEffect(() => {
const user = JSON.parse(localStorage.getItem('user'))
if (user) {
dispatch({ type: 'LOGIN', payload: user })
}
}, [])
console.log('AuthContext state:', state)
return (
<AuthContext.Provider value={{ ...state, dispatch }}>
{ children }
</AuthContext.Provider>
)
}
useLogout
import { useAuthContext } from './useAuthContext'
export const useLogout = () => {
const { dispatch } = useAuthContext()
const logout = () => {
// remove user from local storage
localStorage.removeItem('user')
// dispatch logout action
dispatch({ type: 'LOGOUT' })
}
return { logout }
}
Well, i forgot to add user to my if check in navbar. After this change, all works like a charm.
Navbar
{ isAdmin && user ? <Link to='/admin'><li className='p-4 hover:text-[#00df9a] transition-all duration-500'>Admin {user.name}</li></Link> : <div></div> }
First of all, I am new with react. It has been two weeks since I am working with it
I am trying to use "realm-web" with react (web). I would like to write realm provider in order to access realmApp everywhere in my application. However my attempts didn't go well. The application is giving " Uncaught (in promise) RangeError: Maximum call stack size exceeded". But beside this error, I am sure there is more to be handled.
Here is my implementation:
const RealmProvider: React.ForwardRefRenderFunction<HTMLElement, PropTypes> = ({ app: realmApp, store, children }) => {
let realm = useTypedSelector(store => store.realm);
let app = useRef<Realm.App>();
const credentials = React.useMemo(() => Realm.Credentials.apiKey(process.env.REACT_APP_REALM_APP_API_KEY!), []);
useEffect(() => {
app.current = realmApp
console.log(realmApp)
if (app.current.currentUser == null) {
loadingRealmApp()
app.current.logIn(credentials)
.then((res) => store.dispatch(storeRealmApp(res)))
//.catch(reason => console.log("RealmError", reason));
}
//Specify how to clean up after this effect:
return function cleanup() {
app.current?.currentUser?.logOut().then(() =>
store.dispatch(unloadRealmApp())
);
};
}, [realmApp, store]);
return (
<>
{ React.Children.only(children)}
</>
);
}
export default RealmProvider;
Reducer
// Actions
const STORE_REALM_APP = 'atlas/realm/STORE_REALM_APP'
const STORE_REALM_APP_ERROR = 'atlas/realm/STORE_USER_ERROR'
const UNLOAD_REALM_APP = 'atlas/realm/UNLOAD_REALM_APP'
const LOADING_REALM_APP = 'atlas/realm/LOADING_REALM_APP'
type State = {
//realmApp?: Realm.App
user?: Realm.User
isLoadingRealmApp: boolean
}
const initialState: State = {
//realmApp: undefined,
user: undefined,
isLoadingRealmApp: false
};
type RealmAction = {
payload: Realm.User
} & Action;
// Reducer
const realmReducer = function (state: State = initialState, action: RealmAction): State {
switch (action.type) {
case STORE_REALM_APP:
return {
...state,
isLoadingRealmApp: false,
user: action.payload
}
case LOADING_REALM_APP:
return {
...state,
isLoadingRealmApp: true
}
case STORE_REALM_APP_ERROR:
case UNLOAD_REALM_APP:
return {
...state,
user: undefined,
isLoadingRealmApp: false
}
default:
return state
}
}
export default realmReducer;
export function storeRealmApp(app: Realm.User) {
console.log("storeRealmApp", app)
return {
type: STORE_REALM_APP,
payload: app
}
}
export function loadingRealmApp() {
return {
type: LOADING_REALM_APP
}
}
export function storeRealmAppError(reason: any) {
return {
type: STORE_REALM_APP_ERROR,
payload: reason
}
}
export function unloadRealmApp() {
return {
type: UNLOAD_REALM_APP
}
}
Realm.ts
const realmApp: Realm.App = new Realm.App({ id: process.env.REACT_APP_REALM_APP_ID!, app: { name: "Atlas" } });
//const mongodb = realmApp.currentUser!.mongoClient("mongodb-atlas")
const useMongodb = () => {
const user = useTypedSelector(store => store.realm.user);
console.log(user)
return user!.mongoClient("mongodb-atlas");
}
export { realmApp, useMongodb }
I guess I am doing something wrong, or this is not the way of doing this. I need some help :)
In case someone needs it, I figure it out.
import React from "react";
import * as Realm from "realm-web";
type ContextType = {
currentUser: Realm.User,
logIn: (credentials: Realm.Credentials) => void
logOut: () => void
} & Realm.App
const RealmAppContext = React.createContext<ContextType>({
} as any);
export const useRealmApp = () => {
const app = React.useContext(RealmAppContext);
if (!app) {
throw new Error(
`You must call useRealmApp() inside of a <RealmAppProvider />`
);
}
return app;
};
export const useMongodb = () => {
const app = React.useContext<ContextType>(RealmAppContext);
const mongodb = app.currentUser.mongoClient("mongodb-atlas")
if (!mongodb) {
throw new Error(
`You must call useRealmApp() inside of a <RealmAppProvider />`
);
}
return mongodb;
};
type PropTypes = {
appId: string,
//children: JSX.Element
}
export const RealmAppProvider: React.ForwardRefRenderFunction<HTMLElement, PropTypes> = ({ appId, children }) => {
const [app, setApp] = React.useState(new Realm.App(appId));
React.useEffect(() => {
setApp(new Realm.App(appId));
}, [appId]);
// Wrap the Realm.App object's user state with React state
const [currentUser, setCurrentUser] = React.useState(app.currentUser);
async function logIn(credentials: Realm.Credentials) {
await app.logIn(credentials);
// If successful, app.currentUser is the user that just logged in
setCurrentUser(app.currentUser);
}
async function logOut() {
// Log out the currently active user
await app.currentUser?.logOut();
// If another user was logged in too, they're now the current user.
// Otherwise, app.currentUser is null.
setCurrentUser(app.currentUser);
}
const wrapped = { ...app, currentUser, logIn, logOut };
return (
<RealmAppContext.Provider value={wrapped as any}>
{children}
</RealmAppContext.Provider>
);
};
AuthContext.tsx
import createDataContext from './createDataContext';
import serverApi from '../api/server';
const authReducer = ({state, action}: any) => {
switch(action.type){
default:
return state;
}
};
const signup = () => {
return async ({email, password}: any) => {
try{
const response = await serverApi.post('/signup', {email, password});
console.log(response.data)
}catch(err){
console.log(err.message);
}
};
}
const signin = ({dispatch}:any) => {
return ({email, password}: any) => { };
}
const signout = ({dispatch}: any) => {
return () => {};
}
export const {Provider, Context} = createDataContext(
authReducer,
{signin, signout, signup},
{isSignedIn: false}
);
createDataContext
import React, { useReducer } from 'react';
export default ({reducer, actions, defaultValue}: any) => {
const Context = React.createContext();
const Provider = ({ children }: any) => {
const [state, dispatch] = useReducer(reducer, defaultValue);
const boundActions: any = {};
for (let key in actions) {
boundActions[key] = actions[key](dispatch);
}
return (
<Context.Provider value={{ state, ...boundActions }}>
{children}
</Context.Provider>
);
};
return { Context, Provider };
}
I copy the code from a video tutorial where react native app has been developed with js extension. But the project I am working on has tsx extension i.e. TypeScript.
How to convert the above code so it will work in my typescript react native mobile app?
({reducer, actions, defaultValue}: any) is expecting one argument with three properties. But when you call it, you are passing three separate arguments. So you want (reducer: any, actions: any, defaultValue: any). Likewise, a reducer takes two arguments so you want authReducer = (state: any, action: any) =>, and so on for a bunch of your functions.
Now we want to get rid of all the any and use actual types! Some of those types we can import from react and others we will define ourselves.
The part that's tricky is getting your context to know the types for your specific action creators and what arguments each one requires. You want this so that you can get autocomplete suggestions for the actions and so you can know if you are calling them improperly. But that requires more advanced typescript like generics and mapped types so just copy and paste this and don't worry too much.
import React, { useReducer, FunctionComponent, Reducer, Dispatch } from 'react';
interface DataState {
isSignedIn: boolean;
// add any other properties here
}
interface SignInProps {
email: string;
password: string;
}
// you can change this
// it is common to use a type for `Action` that is a union of your specific actions
interface Action {
type: string;
payload: any;
}
// this is where I am getting tricky
type BoundActions<T> = {
[K in keyof T]: T[K] extends (d: Dispatch<Action>) => infer R ? R : never
}
type ContextValue<T> = {
state: DataState;
} & BoundActions<T>
export const createDataContext = <T extends {}>(reducer: Reducer<DataState, Action>, actions: T, defaultValue: DataState) => {
// context needs a defaultValue
const Context = React.createContext({state: defaultValue} as ContextValue<T>);
// type of children is known by assigning the type FunctionComponent to Provider
const Provider: FunctionComponent = ({ children }) => {
const [state, dispatch] = useReducer(reducer, defaultValue);
const boundActions = {} as BoundActions<T>;
for (let key in actions) {
// #ts-ignore - I don't want to make a confusing mess so just ignore this
boundActions[key] = actions[key](dispatch);
}
return (
<Context.Provider value={{ state, ...boundActions }}>
{children}
</Context.Provider>
);
};
return { Context, Provider };
}
const authReducer = (state: DataState, action: Action): DataState => {
switch (action.type) {
default:
return state;
}
};
const signup = (dispatch: Dispatch<Action>) => {
return async ({ email, password }: SignInProps) => {
try {
const response = await serverApi.post('/signup', { email, password });
console.log(response.data)
} catch (err) {
console.log(err.message);
}
};
}
const signin = (dispatch: Dispatch<Action>) => {
return ({ email, password }: SignInProps) => { };
}
const signout = (dispatch: Dispatch<Action>) => {
return () => { };
}
export const { Provider, Context } = createDataContext(
authReducer,
{ signin, signout, signup },
{ isSignedIn: false }
);
The point of doing all that is to get intellisense and type checking when you consume the context.
import React, { useContext } from 'react';
import { Provider, Context } from .... // your path
const SampleComponent = () => {
// knows all of the properties available on the context
const {state, signin, signout, signup} = useContext(Context);
const handleClick = () => {
// knows that these need email and password
signin({email: '', password: ''});
signup({email: '', password: ''});
// knows that this one is ok to call with no args
signout();
}
return (
<div>{state.isSignedIn ? "Signed In!" : "Not Signed In"}</div>
)
}
const SampleApp = () => (
<Provider>
<SampleComponent/>
</Provider>
)
I'm attempting to create an Auth context file which, upon app load, checks if a user is signed in.
To do this, I'm using a 'helper' function which allows me to import the initialisation of the context and just build upon that with additional functions which authorise a user.
However, upon every app load, the Context is returning as 'undefined', and it says 'evaluating _useContext.trySignIn'.
For reference, here is my Context file:
import createDataContext from './createDataContext';
import { AsyncStorage } from 'react-native';
import { navigate } from '../navigationRef';
import { Magic } from '#magic-sdk/react-native';
const m = new Magic('API_key');
const authReducer = (state, reducer) => {
switch (action.type) {
default:
return state;
}
};
const trySignIn = dispatch => async () => {
const isLoggedIn = await m.user.isLoggedIn();
if (isLoggedIn === true) {
navigate('Dashboard');
} else {
navigate('loginFlow');
}
};
export const { Provider, Context } = createDataContext (
authReducer,
{ trySignIn },
{ isLoggedIn: null }
);
Here is my 'createDataContext' file:
import React, { useReducer } from 'react';
export default (reducer, actions, defaultValue) => {
const Context = React.createContext();
const Provider = ({ children }) => {
const [state, dispatch] = useReducer(reducer, defaultValue);
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 navigation file:
import { NavigationActions } from 'react-navigation';
let navigator;
export const setNavigator = (nav) => {
navigation = nav;
};
export const navigate = (routeName, params) => {
navigator.dispatch(
NavigationActions.navigate({
routeName, params
})
);
};
And finally, here is my component attempting to use my context:
import React, { useEffect, useContext } from 'react';
import { View, Text, StyleSheet, ActivityIndicator } from 'react-native';
import { Context } from '../context/AuthContext';
const LoadingScreen = () => {
const { trySignIn } = useContext(Context);
useEffect(() => {
trySignIn();
}, [])
return (
<View style={styles.mainView}>
<ActivityIndicator style={styles.indicator} />
</View>
)
}
Can anyone see why my context would be returning as 'undefined' in my component?
I was going over the updated Stephen Girder react-native course and saw that he used a single createDataContext file so that his Reducer has access to Context and Provider.
his createDataContext file looks like this:
import React, {useReducer} from 'react';
export default (reducer, actions, initialState) => {
const Context = React.createContext(reducer, initialState);
console.log('show me the initial state: ', initialState)
// actions === {addBlogPost: (dispatch) => { return ()=> {} }}
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};
};
BlogPostContext
import createDataContext from './createDataContext';
const blogReducer = (state, action) => {
console.log('show me the state inside the reducer: ', action);
switch (action.type) {
case 'add_blogpost':
return [
...state,
{
id: Math.floor(Math.random() * 999),
title: action.payload.title,
content: action.payload.content,
},
];
case 'delete_blogpost':
return state.filter(blogPost => blogPost.id !== action.payload);
case 'edit_blogpost':
return state.map(blogPost => {
return blogPost.id === action.payload.id ? action.payload : blogPost;
});
default:
return state;
}
};
const addBlogPost = dispatch => {
return (title, content, callback) => {
console.log("inside addBlogpost", title, content);
dispatch({
type: "add_blogpost",
payload: { title, content }
});
callback();
};
};
const deleteBlogPost = dispatch => {
return id => {
dispatch({type: 'delete_blogpost', payload: id});
};
};
const editBlogPost = dispatch => {
return (id, title, content) => {
dispatch({
type: "edit_blogpost",
payload: { id, title, content }
});
};
};
export const {Context, Provider} = createDataContext(
blogReducer,
{addBlogPost, deleteBlogPost, editBlogPost},
[],
);
Methods that need to be accessed on a particular page are simply de-destructured on the page that they're needed:
import React, {useContext} from 'react';
import {StyleSheet} from 'react-native';
import {Context} from '../context/BlogContext';
import BlogPostForm from '../components/BlogPostForm';
const CreateScreen = ({navigation}) => {
const {addBlogPost} = useContext(Context);
return (
<BlogPostForm
onSubmit={(title, content) => {
addBlogPost(title, content, () => navigation.navigate('Homepage'));
}}
/>
);
};
const styles = StyleSheet.create({});
export default CreateScreen;
I would like to create a second Context page that contains a different reducer, methods, and most importantly initialState object.
However, I will NOT be able to access the state of the second Context page because in the createDataContext only passes state as a value to a provider.
<Context.Provider value={{state, ...boundActions}}>
That only grants me access to the first Context(BlogContext). How can I access the state of the second Context page given how createDataContext file is at the moment? Would I need to create a second createDataContext or do I need to define individual states when I pass the state in the Context.Provider?