im trying to create an api request with the header value, that is received from a context component. However, as soon as the page component is loaded, it throws an Cannot read property '_id' of null exception. Is there a way to run the useEffect function, as soon as the context is loaded?
main component:
import React, { useState, useEffect, useContext } from "react";
import "./overview.scss";
/* COMPONENTS */;
import axios from 'axios';
import { GlobalContext } from '../../components/context/global';
const Overview = () => {
const [bookings, setBookings] = useState([]);
const [loaded, setLoaded] = useState(false);
const [user, setUser] = useContext(GlobalContext);
useEffect(() => {
axios
.get(`/api/v1/bookings/user/${user._id}`)
.then(res => setBookings(res.data))
.catch(err => console.log(err))
.finally(() => setLoaded(true));
}, [user]);
context component:
import React, {useState, useEffect, createContext} from 'react';
import jwt from 'jsonwebtoken';
/* GLOBAL VARIABLES (CLIENT) */
export const GlobalContext = createContext();
export const GlobalProvider = props => {
/* ENVIRONMENT API URL */
const [user, setUser] = useState([]);
useEffect(() => {
const getSession = async () => {
const user = await sessionStorage.getItem('authorization');
setUser(jwt.decode(user));
}
getSession();
}, [])
return (
<GlobalContext.Provider value={[user, setUser]}>
{props.children}
</GlobalContext.Provider>
);
};
The issue here is useEffect is running on mount, and you don't have a user yet. You just need to protect against this scenario
useEffect(() => {
if (!user) return;
// use user._id
},[user])
Naturally, when the Context fetches the user it should force a re-render of your component, and naturally useEffect should re-run as the dependency has changed.
put a condition before rendering you GlobalProvider, for example:
return (
{user.length&&<GlobalContext.Provider value={[user, setUser]}>
{props.children}
</GlobalContext.Provider>}
);
If user is not an array just use this
return (
{user&&<GlobalContext.Provider value={[user, setUser]}>
{props.children}
</GlobalContext.Provider>}
);
Related
I have a user context that loads the user data. I am using that data to send API requests in useEffect. The time lag in the loading of the data is causing an undefined variable in my API request. How do I make the useEffect wait for the context variable to load before sending the request?
This is the UserContext.js:
import { createContext, useState } from "react";
const UserContext = createContext({});
export const UserProvider = ({ children }) => {
const [user, setUser] = useState({});
return (
<UserContext.Provider value={{ user, setUser }}>
{children}
</UserContext.Provider>
);
};
export default UserContext;
This is the custom hook:
import { useContext } from "react";
import UserContext from "../context/UserProvider";
const useUser = () => {
return useContext(UserContext);
};
export default useUser;
And this is the API call in the profile page:
const { user } = useUser();
useEffect(() => {
Axios.get(
`API_URL/${user?.subscription_id}`
).then((res) => {
console.log(res)
});
}, []);
How can I ensure user data is loaded before I make a request throughout my app?
In react, Context APi static data will be Passed to the Children at initial Load. But if you are using asynchronous data in context api, you have to use useEffect and add context value as dependency..
export const UserProvider = ({ children }) => {
const [user, setUser] = useState({});
// Updating Context value Asynchronously..
setTimeout(() => {
setUser("data");
}, [3000]);
return (
<UserContext.Provider value={{ user, setUser }}>
{children}
</UserContext.Provider>
);
};
const { user } = useUser();
useEffect(() => {
// Call api only if user contains data.
if(user != {}) {
Axios.get(
`API_URL/${user?.subscription_id}`
).then((res) => {
console.log(res)
});
}
}, [user]);
I am able to upload the image successfully to Firebase Storage, but when I try to render it back to my dashboard I get a 404 error, file not found. I figure it has to do with the database not uploading the image and then sending it back to my react app. When I reload the page, the image renders. I have an async/await function for setting the userImage. What is the best way to manage this? New to React btw.
import { useState, createContext, useEffect } from "react";
import app from "../firebase";
const UserContext = createContext();
const UserProvider = ({ children }) => {
const [user, setUser] = useState(null);
const [userImg, setUserImage] = useState(null);
console.log("userImg from context", userImg);
const img = async () => {
const imgPath = await app.firebase
.storage()
.ref(`users/${user.uid}/profile.jpg`)
.getDownloadURL();
setUserImage(imgPath);
};
useEffect(() => {
if (user) img();
}, [user]);
useEffect(() => {
app.auth().onAuthStateChanged(setUser);
}, [user]);
return (
<UserContext.Provider value={[user, setUser, userImg, setUserImage]}>
{children}
</UserContext.Provider>
);
};
export { UserContext, UserProvider };
The problem with your code is that the component gets rendered before even the img is retrieved from the server.
So what you can do is have a loading state in your component and until the img isn't received back from the server set the loading state to true.
import { useState, createContext, useEffect } from "react";
import app from "../firebase";
const UserContext = createContext();
const UserProvider = ({ children }) => {
const [loading, setLoading] = useState(false) // <--------Loading state--------
const [user, setUser] = useState(null);
const [userImg, setUserImage] = useState(null);
console.log("userImg from context", userImg);
const img = async () => {
setLoading(true) // Setloading to true
const imgPath = await app.firebase
.storage()
.ref(`users/${user.uid}/profile.jpg`)
.getDownloadURL();
setUserImage(imgPath);
setLoading(false) // <--------setting loading to false after receiving the image-------
};
useEffect(() => {
if (user) img();
}, [user]);
useEffect(() => {
app.auth().onAuthStateChanged(setUser);
}, [user]);
if(loading) {
return (<div>Loading...</div>) // <-----return loading if loading is true----
}
return (
<UserContext.Provider value={[user, setUser, userImg, setUserImage]}>
{children}
</UserContext.Provider>
);
};
export { UserContext, UserProvider };
I have an existing context for products. Where initially I used some mock data as shown below STORE_DATA to render the components. Now I need to replace that mock data and connect to a Node.js api which is available on my local port (created the api I after I created the react-app).
import React, { createContext, useState } from 'react';
import STORE_DATA from '../shop';
export const ProductsContext = createContext();
const ProductsContextProvider = ({ children }) => {
const [products] = useState(STORE_DATA);
return (
<ProductsContext.Provider value={{ products }}>
{
children
}
</ProductsContext.Provider>
);
}
export default ProductsContextProvider;
Just created a helper.js file witht he following to fetch the data:
import {useEffect} from "react";
const fetchData = () => {
return fetch("https://localhost:8081/products") <<tested on postman and works fine.
.then((response) => response.json())
.then((data) => console.log('Fetching Data:',data));
}
How to replace the mock data on the context file and use this fetchData() using useEffect within the context? What code should change?
Tried the following, but didn't work, can't even print the console.log:
import React, { createContext, useState, useEffect } from 'react';
import { fetchData } from '../helpers';
export const ProductsContext = createContext();
const ProductsContextProvider = ({ children }) => {
const [products, setProducts] = useState(null);
useEffect(() => {
setProducts(fetchData());
}, []);
return (
<ProductsContext.Provider value={{ products }}>
{
children
}
</ProductsContext.Provider>
);
}
export default ProductsContextProvider;
The issue was that it was returning the following error (explained):
net::ERR_SSL_PROTOCOL_ERROR (on chrome)
Solution: Use http:// instead of https:// in the URL's in the following code:
const fetchData = () => {
return fetch("http://localhost:8081/products")
.then((response) => response.json())
.then((data) => console.log('Fetching Data:',data));
}
I am following an article: https://dev.to/mongopark/learn-react-hooks-and-context-api-by-building-a-recipe-search-app-2-1g3o. In it, he uses useState and useEffect inside his created Context. When I try to do the same. I get errors sayin, " Line 10:28: React Hook "useState" is called in function "dataProvider" which is neither a React function component or a custom React Hook function react-hooks/rules-of-hooks" for all the hooks. I would like to just know what I am doing wrong here.
import React, {useState, useEffect, createContext} from 'react';
import { headerFooter } from '../../api/Api';
import {setHeaderFooter} from '../../functions/generalFunctions'
import {grabUserInventory} from '../../api/Api'
import {getAllOrgs} from '../../api/Api'
const dataContext = createContext()
const dataProvider = (props) => {
const [data, setData] =useState("")
const [combined, setCombined] =useState(0)
const [inventory, setInventory] =useState([])
const [notes, setNotes] =useState([])
const [orgs, setOrgs] =useState([])
const [renderedData, setRenderedData]=useState([])
const [progress,setProgress] = useState(true)
useEffect(()=>{
console.log(props.match.params.token)
headerFooter(props.match.params.token)
.then(res=>{
setData(res)
setHeaderFooter(res.header,res.footer)
return grabUserInventory(res.user_id)
.then(data=>{
setInventory(data)
setRenderedData(data)
setProgress(false)
return getAllOrgs()
.then(data=>{
var outputData = data.map( Object.values );
setOrgs(outputData)
})
})
})
.catch(err=>console.error(err))
}, []);
return (
<dataContext.Provider value={{
data,
setData,
combined,
setCombined,
inventory,
setInventory,
orgs,
setOrgs,
renderedData,
setRenderedData,
progress,
setProgress
}}>
{props.children}
</dataContext.Provider>
);
}
export { dataProvider, dataContext }
I have a mutation which I use to login on my application. I want to rerun that login mutation every 5 minutes to check for updates to their profile. useQuery has a pollingInterval options but useMutation does not.
I tried using a hook to run the mutation on an interval but that doesn't really work because the useAuth hook is used in multiple components at the same time so it ends up creating an interval for each component.
function useInterval(callback: () => any, delay: number | null) {
const savedCallback = useRef<any>();
useEffect(() => {
savedCallback.current = callback;
}, [callback]);
useEffect(() => {
function tick() {
savedCallback.current();
}
if (delay !== null) {
const id = setInterval(tick, delay);
return () => clearInterval(id);
}
}, [delay]);
}
function useAuth(){
const [login,{data:loginData} = useMutation(gql`
...
`);
useInterval(() => login(Cookies.get(TOKEN_NAME),1000*60*5);
return login;
}
function App(){
const login = useAuth();
useEffect(() => {
login(Cookies.get(TOKEN_NAME));
},[login]);
...
}
What you want to do is create a context with a Provider that sits high up in your react component tree. Here is the documentation, context docs.
First you need to create the provider, probably in the same location you create the useAuth hook.
import React, {useContext, createContext} from 'react';
const AuthContext = createContext();
function AuthProvider({ children }) {
const [login,{data:loginData} = useMutation(gql`
...
`);
useInterval(() => login(Cookies.get(TOKEN_NAME),1000*60*5);
return (
<AuthContext.Provider value={login}>{children}</AuthContext.Provider>
)
}
function useAuth(){
const context = useContext(AuthContext);
return context;
}
Then change the App definition like so,
function App() {
return (
...
<AuthProvider>
...
</AuthProvider>
...
);
}
By placing it in a provider (that is high in the tree), the useEffect will likely only run on app load and when the useInterval triggers. If the code is just in a functional hook, it can change frequently (component mount/unmount, prop changes, etc).