Firebase Storage Context with Storage Data - javascript

I am using Firebase and React and learning how to use React Contexts. I created a context that queries Firebase to get data from there (user's files URL's for example). The context works fine, however, I can't get the context to become asynchronous. I haven't seen any examples of this and am not sure if it's possible. My code is below.
StorageContext.js:
import React, { useContext, useEffect, useState } from 'react';
import { auth } from './FirebaseConfiguration';
import fire from './FirebaseConfig';
import { useAuth } from './AuthContext';
const StorageContext = React.createContext();
export function useStorage() {
return useContext(StorageContext);
}
export function StorageProvider({ children }) {
const { currentUser } = useAuth();
const [fileURLs, setFilesURL] = useState([]);
async function getUserData() {
var storage = fire.storage();
var storageRef = storage.ref(currentUser.uid);
storageRef
.listAll()
.then(function (result) {
result.items.forEach(function (imageRef, i) {
let temp = filesUploaded;
temp.push(imageRef.name);
setFilesUploaded(temp);
});
console.log(filesUploaded);
// console.log(getData(filesUploaded));
getData(filesUploaded);
})
.catch(function (error) {
console.log(error);
});
}
const value = { getUserData };
return (
<StorageContext.Provider value={value}>{children}</StorageContext.Provider>
);
}
Dashboard.js:
import React, { useState, useEffect } from 'react';
import { useStorage } from './Contexts/StorageContext';
export default function Dashboard() {
const { getUserData } = useStorage();
async function getData() {
await getUserData().then((data) => {
console.log(data);
});
}
useEffect(() => {
getData();
}, []);
return (
<div>
console.log('data');
</div>
The useEffect in Dashbaord.js runs fine, the problem is that getUserData() returns immediately even though it should be waiting until (and thus the .then((data) => { console.log(data) } is empty.
Is it possible to run a Context Asynchronously? Or is there another problem that I am missing?
Thanks

The reason it returns immediately is that you use then and not await. Rewrite your function to this and it should work:
async function getUserData() {
var storage = fire.storage();
var storageRef = storage.ref(currentUser.uid);
const result = await storageRef.listAll();
result.items.forEach(function (imageRef, i) {
let temp = filesUploaded;
temp.push(imageRef.name);
setFilesUploaded(temp);
});
console.log(filesUploaded);
// console.log(getData(filesUploaded));
getData(filesUploaded);
}

Related

Importing async function error: Invalid hook call. Hooks can only be called inside of the body of a function component

All I wanna do is be able to call logic from my geolocationApi file into my react-native components whenever I want, NOT LIKE A HOOK but normal async functions, I'm using a custom hook in the geolocationApi file I'm importing though! (custom hooks handles mobx state updates)
I want to call it like this in my functional components (plain and easy):
import geolocationApi from '#utils/geolocationApi.js'
const getCoords = async () =>
{
let result = await geolocationApi().requestLocationPermissions(true);
};
My geolocationApi file where I have a bunch of functions about geolocation I don't want to crowd my components with.
#utils/geolocationApi.js
import _ from 'lodash';
import Geolocation from 'react-native-geolocation-service';
import { useStore } from '#hooks/use-store';
const geolocationApi = () => {
//Custom hook that handles mobx stores
const root = useStore();
const requestLocationPermissions = async (getCityName = false) =>
{
const auth = await Geolocation.requestAuthorization("whenInUse");
if(auth === "granted")
{
root.mapStore.setLocationEnabled(true);
let coords = await getMe(getCityName);
return coords;
}
else
{
root.mapStore.setLocationEnabled(false);
}
};
const getMe = async () =>
{
Geolocation.getCurrentPosition(
async (position) => {
let results = await onSuccess(position.coords);
return results;
},
(error) => {
console.log(error.code, error.message);
},
{ enableHighAccuracy: true, timeout: 15000, maximumAge: 10000 }
);
};
/*const onSuccess = async () => {}*/
};
export default geolocationApi;
This can't be that hard!
If I remove export default geolocationApi and instead add export const geolocationApi at the top I get:
geolocationApi.default.requestLocationPermissions is not a function
You cannot use hooks outside React components. You can pass down the state to your function
import geolocationApi from '#utils/geolocationApi.js'
const getCoords = async (root) =>
{
let result = await geolocationApi(root).requestLocationPermissions(true);
};
Then instead of using useStore()
import _ from 'lodash';
import Geolocation from 'react-native-geolocation-service';
import { useStore } from '#hooks/use-store';
// pass the root from top
const geolocationApi = (root) => {
// your logic
return {
requestLocationPermissions,
getMe
}
}
Then somewhere in your component tree, ( an example with useEffect )
import getCoords from 'path'
const MyComp = () => {
const root = useStore();
useEffect(() => {
getCoords(root)
}, [root])
}
As you said, geolocationApi is a regular function, not a React component/hook. So, it isn't inside the React lifecycle to handle hooks inside of it.
You can use the Dependency Injection concept to fix it.
Make geolocationApi clearly dependent on your store.
const geolocationApi = (store) => {
Then you pass the store instance to it.
const getCoords = async (store) =>
{
let result = await geolocationApi(store).requestLocationPermissions(true);
};
Whoever React component calls the getCoords can pass the store to it.
//...
const root = useStore();
getCoords(root);
//...

Using react + hooks, how can i call/use "navigate()" after a async redux dispatch properly?

When using "await" on "dispatch(saveItem(item))" it's not supposed to have any effct ,
meanwhile if i don't use the "await" both functions will run in the same time resulting a saved item but not a component rerender.
Although the state changes in the redux store the view doesn't,
whilst using the await actually waits for the dispatch to complete and then runs the navigation.
My main question is how to properly navigate after a redux dispatch?
import { useEffect, useRef, useState } from 'react';
import { useDispatch } from 'react-redux';
import { useNavigate, useParams } from 'react-router-dom';
import { useForm } from '../hooks/useForm';
import { getById } from '../services/itemService';
import { saveItem } from '../store/actions/itemActions';
export function ItemEdit() {
const dispatch = useDispatch();
const navigate = useNavigate();
const [item, handleChange, setItem] = useForm(null);
const itemId = useParams().id;
useEffect(async () => {
await loadItem();
}, []);
const loadItem = async () => {
try {
const item = await getById(itemId)
setItem(item);
} catch(err) {
setErrMsg(err.name + ': ' + err.message);
}
};
const onSaveItem = async (ev) => {
ev.preventDefault();
await dispatch(saveItem(item));
navigate('/item')
}
return (
<form onSubmit={onSaveItem}>
<button>Save</button>
</form>
);
}
You can try it this way:
dispatch(saveItem(item))
.unwrap()
.then(() => navigate('/item'))
.catch(error => 'handle error')
It works for me.

React hangs when trying to connect to component

For some reason, when trying to connect this component, react simply hangs without giving any error:
import React, { useState, useEffect } from 'react';
import { useHttp } from '../../hooks/http.hooks';
import _ from 'lodash';
import profileImg from '../../img/system/profile/profileImg.svg';
function ProfileLink() {
const { request } = useHttp(); // To get data
const [profile, setProfile] = useState({});
const profileID = JSON.parse(localStorage.getItem('user')).id;
function takeProfileLink() {
const userID = JSON.parse(localStorage.getItem('user')).id;
setProfile({
...profile,
link: `profile/${userID}`
});
}
async function takeProfile() {
const data = await request(`http://localhost:5500/api/auth/get/${profileID}`);
setProfile({
...profile,
picture: _.get(data, 'profile.picture', 'https://i.pinimg.com/originals/0c/3b/3a/0c3b3adb1a7530892e55ef36d3be6cb8.png'),
name: _.get(data, 'profile.name', '')
});
}
async function takeProfilePicture() {
if (profile.picture) {
return `http://localhost:5500/api/upload/image_get/${profile.picture}`;
} else {
return profileImg;
}
}
async function standProfilePicture() {
const link = await takeProfilePicture();
setProfile({
...profile,
pictureLink: link
});
}
useEffect(async() => {
await takeProfile();
takeProfileLink();
}, []);
standProfilePicture();
return (
<a href={profile.link} className="profile-link">
<div className="profile-name">
{profile.name}
</div>
<div className="profile-picture">
<img src={profile.pictureLink} alt="profile picture"/>
</div>
</a>
);
}
export default ProfileLink;
Presumably the problem is with the profile object. Previously, everything was packaged in variables and everything worked, but now I replaced the variables with an object and react just stopped loading.
Try moving the call to standProfilePicture() inside the useEffect hook. A dangling function call may be causing the component re-render indefinitely, thus freezing the page.

React-component loops

There is a react-component:
import React, { useState, useCallback } from 'react';
import { useHttp } from '../../hooks/http.hooks';
function Main() {
const {loading, error, request} = useHttp();
const [news, setNews] = useState(0);
const topNews = useCallback(async function() {
const data = await request('http://localhost:5500/api/news/top/2');
return data;
}, []);
console.log(topNews());
return (
<div>Hello world</div>
);
}
export default Main;
And a custom hook:
import { useState } from 'react';
export const useHttp = () => {
const [loading, setLoading] = useState();
const [error, setError] = useState();
async function request(url, { method = 'GET', body = null, headers = {} } = {}) {
setLoading(true);
try {
const response = await fetch(url, { method, body, headers });
const data = await response.json();
if (!response.ok) {
throw new Error(data.msg || 'unhandled error');
}
return data;
} catch (err) {
setError(err);
} finally {
setLoading(false);
}
}
return { loading, request, error }
}
Starting it throw error:
Error: Too many re-renders. React limits the number of renders to prevent an infinite loop.
And so many Promises in console:
As I understood, when loading is changing Main is rendering, because I added a useCallback() but it's not working. How to get rid of looping right?
Inside your React Component, request should be called inside useEffect instead of useCallback. If you keep it that way, the loop looks like this :
Component render
request is called
request update component states
states change force render, so go back to bullet #2
You should change your code to something like this :
import React, { useState, useEffect } from 'react';
import { useHttp } from '../../hooks/http.hooks';
function Main() {
const {loading, error, request} = useHttp();
const [news, setNews] = useState([]);
useEffect(() => {
const data = await request('http://localhost:5500/api/news/top/2');
setNews(data);
}, [])
console.log(news);
return (
<div>Hello world</div>
);
}
export default Main;
See useEffect for more details and advanced usage.
Move the async function call to useEffect, you currently call it on every render:
function Main() {
const {loading, error, request} = useHttp();
const [news, setNews] = useState(0);
useEffect(() => {
async function topNews() {
const data = await request('http://localhost:5500/api/news/top/2');
return data;
}
setNews(topNews());
}, [])
return (
<div>{JSON.stringify(news,null,2)}</div>
);
}

Not able to use value returned by React hook

I am trying to use a custom hook to make HTTP requests and then use a reducer to update the state in the component.
The hook runs correctly and I can get the response from the request but not able to use the response data in dispatch function.
Below is the code:
HTTP hook:
import React, { Fragment, useState, useEffect, useReducer } from 'react';
import axios from 'axios';
export const useHttpRequest = (initialData, initialURL) => {
const [url, setUrl] = useState(initialURL);
const [isError, setIsError] = useState(false);
useEffect(() => {
const fetchData = async () => {
console.log('in a http hook');
setIsError(false);
try {
const res = await axios(url);
console.log(res);
const responseData = res.data.data.data;
return responseData;
} catch (error) {
setIsError(true);
}
};
fetchData();
}, [url]);
return { isError, setUrl };
};
A function call in the state:
const { isError, setUrl } = useHttpRequest();
const getCategoryData = async () => {
setLoading();
try {
const Data = await setUrl('/api/v1/category');
dispatch({
type: SET_CATEGORYDATA,
payload: Data
});
} catch (err) {}
};
A function call in components, where the function is passed through useContext
useEffect(() => {
getCategoryData();
}, []);
You cannot await on a setState (setUrl) function.
You return in your fetch data which is not used later.
You need to first change your mindset on how you think in react hooks and when you need to use them.
As far as I understand you want to fetch some data from server, update the store on successful retrieval, and show an error when the request fails.
You should do this all the way or don't do this at all. You can put the dispatch in the hook or you can forget about the hook and write a reusable fetchData function and handle setHasError in your component's useEffect.
There are many ways to solve this but this is my preferred solution:
import React, { Fragment, useState, useEffect, useReducer } from 'react';
import axios from 'axios';
export const useHttpRequest = (url, updateStore) => {
const [hasError, setHasError] = useState(false);
const fetchData = async (url) => {
setHasError(false);
try {
const res = await axios(url);
const responseData = res.data.data.data;
updateStore(responseData);
} catch (error) {
setHasError(true);
}
};
useEffect(() => {
if (url) {
fetchData(url);
}
}, [url]);
return { fetchData, hasError };
};
// in case you want to fetch the data on component render
const { fetchData, hasError } = useHttpRequest(url, (data) => dispatch({
type: SET_CATEGORYDATA,
payload: data
}));
// in case you want to fetch it in a callback
const clickButton = () => {
fetchData(someCustomUrl);
}
Finally, you can generalize your dispatchers so you don't need to send the whole function to the hook and only send the dispatcher name.

Categories

Resources