I'm trying to show images from array by they path but getting error
App.js:44 Uncaught nc (in promise) TypeError: Cannot read properties of undefined (reading '_location')
I know that I must do something with promises, async functions etc., but I don't understand at all. So below is code example
const fireBaseApp = initializeApp(firebaseConfig);
const storage = getStorage(fireBaseApp);
function App() {
const [url, setUrl] = useState([]);
const [item, setItem] = useState([]);
const listRef = ref(storage, "/");
//getting all images from the bucket
useEffect(() => {
listAll(listRef).then((res) => {
res.items.forEach(async (item) => {
await setUrl((arr) => [...arr, item]);
});
});
}, []);
//Trying to download image from array with its' path
useEffect(() => {
const func = async () => {
const download = ref(storage, url[2]._location.path_);
await getDownloadURL(download).then((x) => {
setItem(x);
});
};
func();
}, []);
return (
<>
<img src={item} />
</>
);
}
So can anybody explain please how to do it properly. Thank you !
Add url[2] to the useEffect dependency array. But also check if it's set before running the async func. Like this:
useEffect(() => {
if(url[2]){
const func = async () => {
const download = ref(storage, url[2]._location.path_);
await getDownloadURL(download).then((x) => {
setItem(x);
});
};
func();
}
}, [url[2]]);
Related
In utils file I wrote a function that collects all the urls of images located inside a folder in Firebase Storage with the path "proj_name/screenshots/uid/" (there are 8 imgs) and import it inside Next.js [id] page using getServerSideProps(). However when I console.log the length of array with the urls it always outputs 0. What may cause this problem?
Utils file:
...
export const getImages = async (dir) => {
let data = []
const storage = getStorage()
const listRef = ref(storage, dir)
await listAll(listRef)
.then(res => {
res.items.forEach(itemRef => {
getDownloadURL(itemRef)
.then(url => {
data.push(url)
})
})
})
return data
}
Next.js page file:
import { getItems, getImages } from "../../utils/firebase"
export const getServerSideProps = async (context) => {
const id = context.params.id
const item = await getItems(id)
const screenshots_urls = await getImages('proj_name/screenshots/' + id)
return{
props: { item, screenshots_urls }
}
}
const Details = ({ item, screenshots_urls }) => {
return (
<>
{
console.log(screenshots_urls.length)
}
</>
)
}
export default Details
I would recommend using either async-await or promise chaining only and not both. Try refactoring the code as shown below:
export const getImages = async (dir) => {
let data = []
const storage = getStorage()
const listRef = ref(storage, dir)
const res = await listAll(listRef)
const promises = res.items.map((itemRef) => getDownloadURL(itemRef))
const data = await Promise.all(promises)
console.log(data)
return data
}
I'm having trouble getting my loading status to appear before the longRunningCode executes. I tried making it async to no avail.
const [loadingStatus, setLoadingStatus] = useState(undefined);
const [myAction, setMyAction] = useState(undefined);
const longRunningCode = () => {
const file = // synchronous code to generate a gzipped File object
return file;
}
// also tried
// const longRunningCode = async () => {
// ...
useEffect(() => {
if (myAction) {
setLoadingStatus('Do the thing...')
const result = longRunningCode()
// also tried `await` with async version
// ...
setLoadingStatus(undefined)
setMyAction(undefined)
}
}, [myAction])
//...
return (
<div>
<p>{loadingStatus}</p>
<button onClick={() => setMyAction({})}>Generate file</button>
</div>
)
I was thinking something along the lines of:
const [loadingStatus, setLoadingStatus] = useState(undefined);
const [myAction, setMyAction] = useState(undefined);
const longRunningCode = () => {
return new Promise(resolve, reject)=>{
const file = // synchronous code to generate a File object
resolve(file);
}
}
useEffect(() => {
if (myAction) {
setLoadingStatus('Do the thing...')
longRunningCode().then(file=>{
// ...
setLoadingStatus(undefined)
setMyAction(undefined)
})
}
}, [myAction])
//...
return <p>{loadingStatus}</p><button onClick={() => setMyAction({})} />
**Edit: ** with setTimeout
const [loadingStatus, setLoadingStatus] = useState(undefined);
const [myAction, setMyAction] = useState(undefined);
const longRunningCode = () => {
const file = // synchronous code to generate a File object
return file
}
}
useEffect(() => {
if (myAction) {
setLoadingStatus('Do the thing...')
//a 0 second delay timer waiting for longrunningcode to finish
let timer = setTimeout(() =>{
longRunningCode()
setLoadingStatus(undefined)
setMyAction(undefined)
}, 0);
// clear Timmer on unmount
return () => {
clearTimeout(timer);
};
}
}, [myAction])
//...
return <p>{loadingStatus}</p><button onClick={() => setMyAction({})} />
useEffect callbacks are only allowed to return undefined or a destructor function. In your example, you have passed an async function which returns a Promise. It may or may not run, but you will see React warnings that useEffect callbacks are run synchronously.
Instead, define an async function, and then call it.
const [loadingStatus, setLoadingStatus] = useState(undefined);
const [myAction, setMyAction] = useState(undefined);
useEffect(() => {
const doAction = async () => {
if (myAction) {
setLoadingStatus('Do the thing...');
const result = await longRunningCode();
// ...
setLoadingStatus(undefined);
setMyAction(undefined);
}
};
doAction();
}, [myAction]);
//...
return <p>{loadingStatus}</p><button onClick={() => setMyAction({})} />
Otherwise, what you have written will work.
I get an error when I try to get data from "/api/products"
function HomeScreen (props){
const [products, setProduct] = useState([]);
useEffect(() => {
const fetchData = async () => {
const {data} = await axios.get('/api/products');
setProduct(data);
}
fetchData();
return () => {
};
}, [])
check if your API gets error during fetch or your API is running. It may cause because of wrong url too.
TLDR; Basically I'm trying to figure out how to get data from my api into my React app on load so my app has it available right away. I'm totally fine with redoing or reworking any or all of my code below if need be. I just need to figure out how to do this. I think it needs to go into the context so that all my components have access to it.
I have created a ReactJS app with hard-coded dummy data and it works great. Now we need to convert it to pulling in real dynamic data from the database and am having trouble with understanding how promises work with state and useEffect. It gets so confusing. I am creating the contextValue object in my final .then call and I need that at the end of my file in the return. I tried saving it to a state variable, but that doesn't seem to be working. I am getting an error TypeError: _useContext is null in a different file.
Here is the entire context file with any irrelevant code redacted:
import PropTypes from 'prop-types';
export const ScheduleContext = createContext();
export const ScheduleProvider2 = (props) => {
const allChemicals = [...];
const allBOMs = [...];
const makeRandomStr = (length) => {...};
const getRandomChemicals = () => {};
const getRandomBOMs = () => {..};
const getRandomIntInclusive = (min, max) => {...};
const randomDate = (start, end, startHour, endHour) => {...};
const quotes = [...];
const getRandomComments = () => {..};
const getBatchNumbers = () => {...};
const [context, setContext] = useState(null);
const [orders, setOrders] = useState(null);
const [filteredOrders, setFilteredOrders] = useState(null);
const [pendingOrderIDs, setPendingOrderIDs] = useState(null);
const [activeOrder, setActiveOrder] = useState(0);
const [columns, setColumns] = useState([]);
const [showDetails, setShowDetails] = useState(false);
const [title, setTitle] = useState('Title');
const [lineType, setLineType] = useState('Type');
const chemicals = useState(allChemicals);
const BOMs = useState(allBOMs);
useEffect(() => {
const fetchPendingOrders = () => {
const ordersURL = 'http://localhost:3001/orders';
return fetch(ordersURL, {
cache: 'no-cache', // *default, no-cache, reload, force-cache, only-if-cached
headers: {
'Content-Type': 'application/json'
},
referrerPolicy: 'no-referrer' // no-referrer, *client
});
};
fetchPendingOrders()
.then((result) => {
return result.json();
})
.then((data) => {
const tempOrders = data.map((el, index) => {
return {
id: index,
.....
};
});
setOrders(tempOrders);
setFilteredOrders(tempOrders);
const pendingOrderIDVals = tempOrders.map(function(val) {
return val.id;
});
setPendingOrderIDs(pendingOrderIDVals);
const contextValue = {
orders,
setOrders,
filteredOrders,
setFilteredOrders,
pendingOrderIDs,
setPendingOrderIDs,
columns,
setColumns,
showDetails,
setShowDetails,
activeOrder,
setActiveOrder,
title,
setTitle,
lineType,
setLineType,
chemicals,
BOMs
};
setContext(contextValue);
console.log(contextValue);
})
.catch((e) => {
console.log(e);
});
}, []);
return (
<ScheduleContext.Provider value={context}>
{props.children}
</ScheduleContext.Provider>
);
};
That error TypeError: _useContext is null is happening in a functional component in a file that reads in the context file. These are the two relevant lines(This is the beginning of the SchedulePage.js where the error is happening):
import { ScheduleContext } from '../../schedule-context-new';
const Schedule = () => {
const {
showDetails,
orders,
setOrders,
activeOrder,
columns,
setColumns,
title,
pendingOrderIDs,
filteredOrders,
setTitle,
lineType,
setLineType
} = useContext(ScheduleContext);
....
I'm also using the ScheduleProvider in my app.js if that makes a difference:
import { ScheduleProvider2 } from './schedule-context-new';
import Schedule from './components/home/SchedulePage';
import './App.scss';
const App = () => {
return (
<ScheduleProvider2>
<div className={'App'}>
<Schedule />
</div>
</ScheduleProvider2>
);
};
Update:
Per the example link, I tried changing the end of my context file to this and now it is complaining about Error: ScheduleProvider2(...): Nothing was returned from render.
(async function() {
const context = await fetchPendingOrders();
return (
<ScheduleContext.Provider value={context}>
{props.children}
</ScheduleContext.Provider>
);
})();
};
Update 2 - I've figured out how to create a basic barebones replica of my app that actually produces the same error. Once again for some reason, the useEffect code wasn't getting called until I commented out useEffect, I don't know why.
https://codesandbox.io/s/tender-cookies-jzn5v
You should waite for API result
const Schedule = () => {
const {
orders,
setOrders,
filteredOrders,
setFilteredOrders,
pendingOrderIDs,
setPendingOrderIDs
} = useContext(ScheduleContext) ||[];
Or use useEffect
const data = useContext(ScheduleContext);
useEffect(() => {
// Do you calculation here when ScheduleContext is ready
}, [data])
codesandbox
im using this Api to get json data.
const FetchEarthquakeData = url => {
const [data, setData] = useState(null);
useEffect(() => {
fetch(url)
.then(res => res.json())
.then(jsonData => setData(jsonData.features))
}, [url]);
return data;
};
The problem is when I use this function like this:
const jsonData = FetchEarthquakeData(url)
console.log(jsonData);
I get following console.logs:
null
Array(17)
So my function FetchEarthquakeData returns the null variable and! the desired api. However if I want to map() over the jsonData, the null value gets mapped. How can I refactor my code so I get only the Array?
I'm not quite sure what useState() and setData() do. But in order to fetch the json data from the API, you can make the function as followed, then you can perform operations on the fetched data.
const url = "https://earthquake.usgs.gov/earthquakes/feed/v1.0/summary/4.5_day.geojson"
const FetchEarthquakeData = url => {
return new Promise(resolve => {
fetch(url)
.then(res => res.json())
.then(jsonData => resolve(jsonData.features))
})
}
FetchEarthquakeData(url).then(features => {
console.log(features)
// Do your map() here
})
as per the documentation of react hooks ,Only Call Hooks from React Functions Don’t call Hooks from regular JavaScript functions.
React Function Components -- which are basically just JavaScript Functions being React Components which return JSX (React's Syntax.)
for your requirement you can do as follows in your react component.
idea is to have state hook initialized with empty array then update it once json data available. the fetch logic can be moved inside useeffect .
const SampleComponent=()=>{
const [data,setData] = useState([])
useeffect (){
fetch(url).then((responsedata)=>setData(responseData) ,err=>console.log (err)
}
return (
<>
{
data.map((value,index)=>
<div key=index>{value}<div>
}
</>
)
}
if you find above fetching pattern repetitive in your app thou can use a custom hook for the same.
const useFetch = (url, options) => {
const [response, setResponse] = React.useState(null);
const [error, setError] = React.useState(null);
React.useEffect(() => {
const fetchData = async () => {
try {
const res = await fetch(url, options);
const json = await res.json();
setResponse(json);
} catch (error) {
setError(error);
}
};
fetchData();
}, []);
return { response, error };
};
in your React component you can use like
const {data,error} = useFetch(url , options)
You have to do it in an async fashion in order to achieve what you need.
I recommend you doing it separately. In case you need the data to load when the component mounts.
Another thing is that your function is a bit confusing.
Let's assume some things:
const Example = () => {
const [data, setData] = useState(null);
useEffect(() => {
const fetchData = async () => {
const result = await fetch(url);
setData(result.features);
};
fetchData();
}, []);
return (
<div>
{console.log(data)}
{data && <p>{JSON.stringify(data, null, 2)}</p>}
</div>
);
};
I am sure that the way you are doing it is storing the data properly on data, but you can not see it on your console.log. It is a bit tricky.
You can read a bit more here => https://medium.com/#wereHamster/beware-react-setstate-is-asynchronous-ce87ef1a9cf3
Adding a bit more of complexity in case you want to handle different states like loading and error.
const Example = () => {
const [data, setData] = useState(null);
const [isLoading, setIsLoading] = useState(false);
const [isError, setIsError] = useState(false);
useEffect(() => {
const fetchData = async () => {
setIsError(false);
setIsLoading(true);
try {
const result = await fetch(url);
setData(result.features);
} catch (error) {
setIsError(true);
}
setIsLoading(false);
};
fetchData();
}, []);
return (
<div>
{console.log(data)}
{data && <p>{JSON.stringify(data, null, 2)}</p>}
</div>
);
};