export const LoginContext = React.createContext();
export const DetailsContext = React.createContext();
function App() {
const username = localStorage.getItem("bankDetails");
const [userDetails, setUserDetails] = useState({});
const [isValid, setisValid] = useState(false);
useEffect(() => {
if (username !== null) {
Axios.post("http://localhost:3001/userDetails", {
username: username,
}).then((res) => {
if (res.data.err) {
console.log("err");
} else {
setUserDetails(res.data.details[0]);
setisValid(true);
}
});
}
}, []);
return (
<LoginContext.Provider value={{ isValid, setisValid }}>
<DetailsContext.Provider value={{ userDetails, setUserDetails }}>
<Router>
<Routes>
<Route ... />
</Routes>
</Router>
</DetailsContext.Provider>
</LoginContext.Provider>
);
}
export default App;
Transactions.js
function Transactions() {
const { isValid } = useContext(LoginContext);
const { userDetails, setUserDetails } = useContext(DetailsContext);
const [allDetails, setAllDetails] = useState([]);
const [transactions, setTransactions] = useState([]);
useEffect(() => {
console.log(userDetails);
Axios.get("http://localhost:3001/transactTo").then((rest) => {
setAllDetails(rest.data);
});
// setTransactions(JSON.parse(userDetails.transactions));
}, [userDetails]);
return isValid ? <h1>Valid</h1> : <h1>Not Valid</h1>
}
export default Transactions;
The userDetails logs an empty object first and data object after re-render but after uncommenting the setTransactions(JSON.parse(userDetails.transactions)) part it only logs an empty object and then an error stating: Unexpected token u in JSON at position 0. It only happens on page refresh and not when I navigate from another page.
Also tried adding second effect but it didn't helped:
useEffect(() => {
setTransactions(JSON.parse(userDetails.transactions));
}, [allDetails]);
It is an empty object because API requests are asynchronous. It is a normal thing.
Unexpected token u in JSON at position 0 this means that userDetails.transactions isn't a json. It's probably undefined that's why u
useEffect(() => {
// try returning when the property is `undefined`
if (!userDetails.transations) return;
setTransactions(JSON.parse(userDetails.transactions));
}, [allDetails]);
Related
The bounty expires in 7 days. Answers to this question are eligible for a +500 reputation bounty.
Ludwig is looking for an answer from a reputable source.
I have a context provider in my app:
export const FormContext = createContext<IFormContext | null>(null);
function FormProvider({ caseNumber, children, ...props }: PropsWithChildren<IFormProviderContextProps>) {
const {
data: { caseNumber, taxDocuments, roles },
api,
} = useApiData();
const [error, setError] = useState<string>(null);
const [searchParams, setSearchParams] = useSearchParams();
const activeStep = searchParams.get("step");
const setActiveStep = useCallback((x: number) => {
searchParams.delete("steg");
setSearchParams([...searchParams.entries(), ["step", Object.keys(STEPS).find((k) => STEPS[k] === x)]]);
}, []);
useEffect(() => {
const abortController = new AbortController();
if (case) api.getPersons(case, abortController.signal).catch((error) => setError(error.message));
return () => {
abortController.abort();
};
}, [case]);
useEffect(() => {
const abortController = new AbortController();
if (activeStep === Stepper.INCOME) {
api.getTaxDocuments(abortController.signal).catch((error) => setError(error.message));
}
return () => {
abortController.abort();
};
}, [activeStep]);
useEffect(() => {
const abortController = new AbortController();
api.getCase(caseNumber, abortController.signal).catch((error) => setError(error.message));
}
return () => {
abortController.abort();
};
}, []);
return (
<FormContex.Provider value={{ taxDocuments, case, roles, activeStep, setActiveStep, error, ...props }}>
{children}
</FormContex.Provider>
);
}
I am using this FormProvider as a wrapper for my FormPage:
<React.StrictMode>
<BrowserRouter>
<Routes>
<Route path="/:caseNumber" element={<FormWrapper />} />
<Route path="/" element={<div>Hello world</div>} />
</Routes>
</BrowserRouter>
</React.StrictMode>
function FormWrapper() {
const { caseNumber } = useParams<{ caseNumber?: string }>();
return (
<FormProvider caseNumber={caseNumber}>
<FormPage />
</FormProvider>
);
}
In my FormPage I display components based on the activeStep that I get from FromProvider
export default function FormWrapper({ activeStep, ...props }: FormWrapperProps) {
const renderForm = useMemo(() => {
switch (activeStep) {
case Stepper.TIMELINE:
return <Timeline {...props} />;
case Stepper.INCOME:
return <Income {...props} />;
case Stepper.RESIDENCY:
return <Residency {...props} />;
case Stepper.SUMMARY:
return <Summary {...props} />;
default:
return <Timeline {...props} />;
}
}, [activeStep]);
return <Suspense fallback={<Loader size="3xlarge" title="loading..." />}>{renderForm}</Suspense>;
}
What I would like to do is to implement an abort controller if component gets unmounted to stop the fetch request and state update. I have tried that with implementing it inside useEffect functions of the FormProvider. But, that is repetitive and would like to make some kind of function or a hook that would set the abort controller to every request. I am not sure how to do that with the current setup, where I have my api calls defined in useApiData() hook which looks like this:
export const useApiData = () => {
const [case, setCase] = useState<CaseDto>(null);
const [taxDocuments, setTaxDocuments] = useState<TaxDocumentsResponse[]>([]);
const [roles, setRoles] = useState<IRoleUi[]>([]);
const getCase = async (caseNumber: string, signal?: AbortSignal) => {
const case = await CASE_API.case.findMetadataForCase(caseNumber, { signal });
setCase(case.data);
};
const getPersons = async (case: CaseDto, signal?: AbortSignal) => {
const personPromises = case.roles.map((role) =>
PERSON_API.information.getPersonPost(
{ id: role.id },
{ signal }
)
);
const [...persons] = await Promise.all([...personPromises]);
const roles = persons.map((person) => {
const role = case.roles.find((role) => role.id === person.data.id);
if (!role) throw new Error(PERSON_NOT_FOUND);
return { ...role, ...person.data };
});
setRoles(roles);
};
const getTaxDocuments = async (signal?: AbortSignal) => {
const taxDocumentsDtoPromises = [getFullYear() - 1, getFullYear() - 2, getFullYear() - 3].map((year) =>
TAX_API.integration.getTaxDocument(
{
year: year.toString(),
filter: "",
personId: "123",
},
{ signal }
)
);
const [taxDocument1, taxDocument2, taxDocument3] = await Promise.all([...taxDocumentsDtoPromises]);
setTaxDocuments([taxDocument1.data, taxDocument2.data, taxDocument3.data]);
};
const api = {
getCase,
getPersons,
getTaxDocuments,
};
const data = {
case,
roles,
taxDocuments,
};
return { data, api };
}
As I said I would like to be able to call api without having to define abort controller in every useEffect hook, but I am not sure how to achieve some like this for example:
apiWithAbortController.getCase(caseNumber).catch((error) => setError(error.message))}
I have tried with using a custom hook like this:
export const useAbortController = () => {
const abortControllerRef = useRef<AbortController>();
useEffect(() => {
return () => abortControllerRef.current?.abort();
}, []);
const getSignal = useCallback(() => {
if (!abortControllerRef.current) {
abortControllerRef.current = new AbortController();
}
return abortControllerRef.current.signal;
}, []);
return getSignal;
};
That I was using like this in my useApiData:
const signalAbort = useAbortController();
const getCase = async (caseNumber: string) => {
const case = await CASE_API.case.findMetadataForCase(caseNumber, { signal: signalAbort() });
setCase(case.data);
};
But, that didn't work, with that setup none of the fetch calls were made.
I have following bit of code where <AuthContext> provide authentication objects and methods, <ProtectedRoute> component which guards the authentication required routes.
But the problem is when I login and refresh inside authenticated page, user object fetched from the useAuth hook returns null, but if I use next/link it works fine and user object is preserved.
AuthContext.tsx
const AuthContext = createContext<any>({})
export const useAuth = () => useContext(AuthContext)
type Props = {
children: React.ReactNode
}
const LOGIN = gql`...`
export const AuthContextProvider = ({ children }: Props) => {
const [user, setUser] = useState<object | null>(null)
const [loginUser, { data, loading, error }] = useMutation(LOGIN)
const router = useRouter()
useEffect(() => {
// in case of first login set user and token
// push user to route he belongs
if (data != null) {
if (data.authenticate.jwt !== null) {
console.log('Setting user with JWT decoding...')
const decoded = jwt_decode(data.authenticate.jwt)
setUser({
role: decoded.role,
id: decoded.id,
})
localStorage.setItem('token', data.authenticate.jwt)
}
}
const token = localStorage.getItem('token')
// if token is present set the user object
if (token !== null) {
console.log('Token present')
const decoded = jwt_decode(token)
// console.log(user)
console.log(`Decoded token : ${JSON.stringify(decoded)}`)
setUser({
role: decoded.role,
id: decoded.id,
})
} else {
setUser(null)
}
}, [data])
const login = async (username: string, password: string) => {
loginUser({
variables: {
username,
password,
},
})
}
const logOut = async () => {
console.log('Logging out ...')
setUser(null)
data = null
localStorage.removeItem('token')
}
// if (error) return <p>Submission error! ${error.message}</p>
return (
<AuthContext.Provider value={{ user, login, logOut }}>
{error || loading ? null : children}
</AuthContext.Provider>
)
}
ProtectedRoute.tsx
const PUBLIC_PATHS = ['/']
const ADMIN_ROUTES = [...]
const SUPERVISOR_ROUTES = [...]
export function ProtectedRoute({ children }: { children: React.ReactNode }) {
const { user } = useAuth()
const router = useRouter()
const [authorized, setAuthorized] = useState(false)
useEffect(() => {
console.log(`Current User : ${JSON.stringify(user)}`)
const isAdminRoute = ADMIN_ROUTES.includes(router.pathname)
const isSupervisorRoute = SUPERVISOR_ROUTES.includes(router.pathname)
// if an token is present send user to
// authorized route
if (user !== null) {
// #ts-ignore
if (user.role === 1 && isAdminRoute) {
setAuthorized(true)
console.log(`Pushing to ${router.pathname}`)
router.push(router.pathname)
// #ts-ignore
} else if (user.role === 2 && isSupervisorRoute) {
setAuthorized(true)
console.log(`Pushing to ${router.pathname}`)
router.push(router.pathname)
} else {
console.log(`Invalid role! user: ${JSON.stringify(user)}`)
}
} else {
setAuthorized(false)
console.log('Sending you to login page')
// "/" have the login page
router.push('/')
}
}, [user]) // router is avoided from deps to avoid infinite loop
return <>{authorized && children}</>
}
_app.tsx
export type NextPageWithLayout<P = {}, IP = P> = NextPage<P, IP> & {
getLayout?: (page: ReactElement) => ReactNode
}
type AppPropsWithLayout = AppProps & {
Component: NextPageWithLayout
}
const noAuthRequired = ['/']
function App({ Component, pageProps }: AppPropsWithLayout) {
const getLayout = Component.getLayout ?? ((page) => page)
const router = useRouter()
return (
<ApolloProvider client={CLIENT}>
<AuthContextProvider>
{getLayout(
noAuthRequired.includes(router.pathname) ? (
<Component {...pageProps} />
) : (
<ProtectedRoute>
<Component {...pageProps} />
</ProtectedRoute>
),
)}
</AuthContextProvider>
</ApolloProvider>
)
}
export default App
Current possible workaround is to read JWT in the ProtectedRoute to get user information.
I there anyway to preserve user object on page refresh?
I have an issue where when my user is a new user both my create profile and create characters to FireBase Realtime database are not loading before my user profile page renders. I understand that useEffects run after the render. But after user profile and characters are created in the database I don't have the issue. I can log off and refresh my app, sign in and everything loads in time. Here is m code. I've tried writing my functions inside the useEffect several different ways and I get the same results every time. I saw one post where someone using a .then() but that doesn't appear to work in my situation. I rather not use any additional add-ins like AXIOs or other packages. I feel like there has to be a way to do this with the native built in tools of React and Firebase. Any advice is much appreciated. Edit: Here is my layout.
App.js
<AuthProvider>
<DBProvider>
<Switch>
<PrivateRoute path="/profile" component={ProfileBar} />
<PrivateRoute path="/update-profile" component={UpdateProfile} />
<Route path="/login" component={Login} />
<Route path="/signup" component={Signup} />
<Route path="/forgot-password" component={ForgotPassword} />
</Switch>
</DBProvider>
</AuthProvider>
</Router>
AuthContext.js
import React, { useContext, useState, useEffect } from 'react'
import { auth} from '../firebase'
const AuthContext = React.createContext()
export function useAuth() {
return useContext(AuthContext)
}
export function AuthProvider({ children }) {
const [currentUser, setCurrentUser] = useState()
const [loading, setLoading] = useState(true)
function signup(email, password, displayName) {
let promise = new Promise ((resolve, reject) => {
auth.createUserWithEmailAndPassword(email, password)
.then((ref) => {
ref.user.updateProfile({
displayName: displayName
});
resolve(ref);
})
.catch((error) => reject(error));
})
return promise
}
useEffect(() => {
const unsubscribe = auth.onAuthStateChanged(user => {
setCurrentUser(user)
setLoading(false)
})
return unsubscribe
}, [])
}
DBContext.js
import { db } from '../firebase'
import { useAuth } from './AuthContext'
import React, { useState, useEffect, useContext } from 'react'
const DBContext = React.createContext() // React Database FireStore .DB
export function useDB() {
useContext(DBContext);
}
export function DBProvider({ children }) {
const [profileData, setProfileData] = useState()
const [loading, setLoading] = useState(true)
const { currentUser } = useAuth()
function checkCurrentUser(){
if(currentUser){
checkProfile()
}
if(!currentUser){
setLoading(false)
console.log("No current user logged in.")
}
}
function checkProfile(){
db.ref(`users/` + currentUser.uid + `/profile`)
.on('value', (snapshot) => {
const data = snapshot.val()
if(data == null){
console.log(data, "New user... Generating profile!")
createUserProfile()
}
if(data){
getProfile()
}
});
}
function createUserProfile(){
let profile = {}
profile.gameMaster = false
profile.editor = false
profile.email = currentUser.email
profile.displayName = currentUser.displayName
db.ref('users/' + currentUser.uid).set({
profile
}).then(() =>{
getProfile()
})
}
function getProfile(){
db.ref(`users/` + currentUser.uid + `/profile`)
.on('value', (snapshot) => {
const profile = snapshot.val()
setLoading(false)
setProfileData(profile)
console.log("Profile set to State from Database.")
})
}
useEffect(() => {
checkCurrentUser()
},[])
}
Profile.js
<Switch>
<CharacterProvider>
<Route path={`${match.path}/characters`} component={CharacterSheets} />
<Route path={`${match.path}/journal`} component={Journal} />
<Route path={`${match.path}/game_charts`} component={GameCharts} />
<Route path={`${match.path}/game_rules`} component={GameRules} />
</CharacterProvider>
</Switch>
CharacterContext.js
useEffect(() => {
const ref = db.ref(`users/` + currentUser.uid + `/characters`)
ref.on('value', snapshot => {
const data = snapshot.val()
if(data){
console.log("Setting Characters to State from Database.")
setCharacters(JSON.parse(data))
setLoading(false)
}
if(data == null){
console.log("Setting Characters to State from template.")
setCharacters(characterTemplate)
setLoading(false)
}
})
return () => ref.off();
}, [])
useEffect(() => {
if(characters){
db.ref(`users/` + currentUser.uid).child("/characters").set(JSON.stringify(characters))
}
console.log("Data saved to firebase.")
}, [characters])
CharacterCards.js
import { useCharacter } from '../../../contexts/CharacterContext'
import CharacterCard from './CharacterCard'
import CharacterCardEdit from '../../ProfileContainer/CharacterEdit/CharacterCardEdit'
import SuccessRoller from '../CharacterComponents/SuccessRoller/SuccessRoller'
export default function CharacterCards() {
const { handleCharacterAdd, characters, selectedCharacter, selectedCharacterSuccessRoller } = useCharacter()
return (
<div>
<div className="add_button-container">
<button onClick={handleCharacterAdd} className="add_button-main" >Add Character</button>
</div>
<div className="parent-container">
<div>
{characters?.map(character => {
return (
<CharacterCard key={character.id} {...character} />
)
})
}
</div>
<div>
{selectedCharacter && <CharacterCardEdit character={selectedCharacter} />}
{selectedCharacterSuccessRoller && <SuccessRoller character={selectedCharacterSuccessRoller} />}
</div>
</div>
</div>
)
}
Because your code is sharded out into many functions for readability, there are a lot of listeners that are created but don't get cleaned up. In particular great care needs to be taken with .on listeners as they may be re-fired (you could use .once() to help with this). An example of this bug is in checkProfile() which listens to the user's profile, then calls getProfile() which also listens to the profile. Each time the profile is added, another call to getProfile() is made, adding yet another listener. Plus, each of the listeners in checkProfile() and getProfile() aren't ever cleaned up.
I've made a number of assumptions about your code structure and untangled it so you can read and understand it top-to-bottom. This is especially important when working with React hooks as their order matters.
// firebase.js
import firebase from "firebase/app";
import "firebase/auth";
import "firebase/database";
firebase.initializeApp({ /* ... */ });
const auth = firebase.auth();
const db = firebase.database();
export {
firebase,
auth,
db
}
// AuthContext.js
import { auth } from "./firebase";
import React, { useContext, useEffect, useState } from "react";
const AuthContext = React.createContext();
export default AuthContext;
export function useAuth() { // <- this is an assumption
return useContext(AuthContext);
}
async function signup(email, password, avatarName) {
const userCredential = await auth.createUserWithEmailAndPassword(email, password);
await userCredential.user.updateProfile({
displayName: avatarName
});
return userCredential;
}
export function AuthProvider(props) {
const [currentUser, setCurrentUser] = useState();
const [loading, setLoading] = useState(true);
useEffect(() => auth.onAuthStateChanged(user => {
setCurrentUser(user)
setLoading(false)
}), []);
return (
<AuthContext.Provider
value={{
currentUser,
loading,
signup
}}
>
{props.children}
</AuthContext.Provider>
);
}
// DBContext.js
import { db } from "./firebase";
import { useAuth } from "./AuthContext";
import React, { useEffect, useState } from "react";
const DBContext = React.createContext();
export default DBContext;
export function DBProvider(props) {
const [profileData, setProfileData] = useState();
const [loading, setLoading] = useState(true);
const { currentUser, loading: loadingUser } = useAuth();
useEffect(() => {
if (loadingUser) {
return; // still initializing, do nothing.
}
if (currentUser === null) {
// no user signed in!
setProfileData(null);
return;
}
// user is logged in
const profileRef = db.ref(`users/` + currentUser.uid + `/profile`);
const listener = profileRef.on('value', snapshot => {
if (!snapshot.exists()) {
// didn't find a profile for this user
snapshot.ref
.set({ // <- this will refire this listener (if successful) with the below data
gameMaster: false,
editor: false,
email: currentUser.email,
displayName: currentUser.displayName
})
.catch((error) => console.error("Failed to initialize default profile", error));
return;
}
setProfileData(snapshot.val());
setLoading(false);
});
return () => profileRef.off('value', listener); // <- cleans up listener
}, [currentUser, loadingUser]);
return (
<DBContext.Provider
value={{
profileData,
loading
}}
>
{props.children}
</DBContext.Provider>
);
}
// CharacterContext.js
import { db } from "./firebase";
import { useAuth } from "./AuthContext";
import React, { useEffect, useState } from "react";
const CharacterContext = React.createContext();
export default CharacterContext;
export function CharacterProvider(props) {
const { currentUser, loading: loadingUser } = useAuth();
const [characters, setCharacters] = useState();
const [loading, setLoading] = useState(true);
useEffect(() => {
if (loadingUser) {
return; // still initializing, do nothing.
}
if (!currentUser) {
// no user signed in!
setCharacters(null);
return;
}
const charactersRef = db.ref(`users/${currentUser.uid}/characters`);
const listener = charactersRef.on('value', snapshot => {
if (!snapshot.exists()) {
// no character data found, create from template
snapshot.ref
.set(DEFAULT_CHARACTERS); // <- this will refire this listener (if successful)
.catch((error) => console.error("Failed to initialize default characters", error));
return;
}
setCharacters(JSON.parse(snapshot.val()));
setLoading(false);
});
return () => charactersRef.off('value', listener);
}, [currentUser, loadingUser]);
return (
<CharacterContext.Provider
value={{
characters,
loading
}}
>
{props.children}
</CharacterContext.Provider>
);
}
I have a component in which I'm calling my custom hook.
The custom hook looks like this:
import { useQuery } from 'react-apollo';
export function useSubscription() {
const { loading, error, data } = useQuery(GET_SUBSCRIPTION_BY_ID)
if (loading) return false
if (error) return null
return data
}
And then the component I'm using it in that causes the error is:
export default function Form(props) {
const router = useRouter();
let theSub = useSubscription();
if (theSub === false) {
return (
<Spinner />
)
} // else I'll have the data after this point so can use it.
useEffect(() => {
if (!isDeleted && Object.keys(router.query).length !== 0 && router.query.constructor === Object) {
setNewForm(false);
const fetchData = async () => {
// Axios to fetch data
};
fetchData();
}
}, [router.query]);
// Form Base States
const [newForm, setNewForm] = useState(true);
const [activeToast, setActiveToast] = useState(false);
// Form Change Methods
const handleUrlChange = useCallback((value) => setUrl(value), []);
const handleSubmit = useCallback(async (_event) => {
// Submit Form Code
}, [url, selectedDuration, included, excluded]);
return (
<Frame>
My FORM
</Frame>
)
}
Any ideas?
You can use useEffect for calling hook at the first time of rendering component.
export default function Form(props) {
const router = useRouter();
const [theSub, setTheSub] = useState(null);
useEffect(() => { setTheSub(useSubscription()); }, []);
if (theSub === false) {
return (
<Spinner />
)
} // else I'll have the data after this point so can use it.
// I have some other states being set and used related to the form e.g:
// const [whole, setWhole] = useState(true);
return (... The form ...)
I'm using useContext & useReducer from Hooks & AsyncStorage.setItem in useEffect to save the data whenever state gets updated. On app reload I want to make sure to get saved data using AsyncStorage.getItem and add it to the initial state.
I tried to add init function with async as the third property to useReducer but still, it's not replacing initial data with received data. Please go through below code and help.
Thank you in advance!
Current code where I can save data to AsyncStorage
const [user, dispatch] = useReducer(userReducer, {});
useEffect(() => {
AsyncStorage.setItem('user', JSON.stringify(user))
}, [user]);
return(
<UserContext.Provider value={{user,dispatch}}>
{props.children}
</UserContext.Provider>
);
}
Below is the code I tried, but unable save existing data as initial data.
const getUser = async function() {
const userData = await AsyncStorage.getItem('user')
console.log("parse");
console.log(userData);
console.log("parsed data");
console.log(JSON.parse(userData));
return userData ? JSON.parse(userData) : {};
}
export const UserContext = createContext();
const UserContextProvider = (props) => {
const [user, dispatch] = useReducer(userReducer, {}, getUser);
useEffect(() => {
console.log("context");
console.log(JSON.stringify(user));
AsyncStorage.setItem('user', JSON.stringify(user))
}, [user]);
return(
<UserContext.Provider value={{user,dispatch}}>
{props.children}
</UserContext.Provider>
);
}
Thank you!
Updated and working code based on below suggestion and with minor changes.
const getUser = async () => {
try {
const user = await AsyncStorage.getItem('user')
return user ? JSON.parse(user) : {};
} catch (e) {
console.log('Failed to fetch the data from storage');
}
}
export const UserContext = createContext();
const UserContextProvider = (props) => {
const [user, dispatch] = useReducer(userReducer, {});
// Loading initial Satte
useEffect(() => {
async function fetchUser() {
const user = await getUser();
dispatch({type: 'ADD_USER', user});
}
fetchUser();
}, []);
// Update AsyncStorage when user is updated
useEffect(() => {
// This check is required to avoid initial writing to asyncStorage
if(user) {
AsyncStorage.setItem('user', JSON.stringify(user))
}
}, [user]);
return(
<UserContext.Provider value={{user,dispatch}}>
{props.children}
</UserContext.Provider>
);
}
Your initial state to useReducer needs to synchronous. Since asyncStorage is not a synchronous API you can't actually pass the value as initialState
You however can make use of useEffect loading state like below
const getUser = async () => {
try {
const user = await AsyncStorage.getItem('user')
return user ? JSON.parse(user) : {};
} catch (e) {
console.log('Failed to fetch the data from storage');
}
}
export const UserContext = createContext();
const UserContextProvider = (props) => {
const [isLoading, setIsLoading] = useState(true);
const [user, dispatch] = useReducer(userReducer, {});
// Loading initial Satte
useEffect(() => {
async function fetchUser() {
const user = await getUser();
setIsLoading(false);
dispatch({type: 'INIT_REDUCER', user}); // You must handle initReducer in reducer
}
fetchUser();
}, []);
useEffect(() => {
if(user) {
// This check is required to avoid initial writing to asyncStorage
console.log("context");
console.log(JSON.stringify(user));
AsyncStorage.setItem('user', JSON.stringify(user))
}
}, [user]);
if(isLoading) {
return (
<View>
<Text>Loading...</Text>
</View>
);
}
return(
<UserContext.Provider value={{user,dispatch}}>
{props.children}
</UserContext.Provider>
);
}