Trying to console.log data within useEffect. Not logging any information - javascript

function UserAccounts() {
const [accounts, setAccounts] = useState();
useEffect(() => {
async function fetchAccounts() {
const res = await fetch(
'https://proton.api.atomicassets.io/atomicassets/v1/accounts'
);
const { accounts } = await res.json();
setAccounts(accounts);
console.log(accounts);
}
fetchAccounts();
}, []);
}
I'm trying to understand why console.log shows nothing in this example and what is the correct way to console.log the data that is being fetched from the api.

Well, you need to get the structure of the returned payload from the API correct. It does not have an accounts property.
The payload looks like this:
{
"success":true,
"data":[{"account":"joejerde","assets":"11933"},{"account":"protonpunks","assets":"9072"}],
"queryTime": 1646267075822
}
So you can rename the data property while destructuring. const { data: accountList } = await res.json();
function UserAccounts() {
const [accounts, setAccounts] = useState();
useEffect(() => {
async function fetchAccounts() {
const res = await fetch(
'https://proton.api.atomicassets.io/atomicassets/v1/accounts'
);
const { data: accountList } = await res.json();
setAccounts(accountList);
// logging both the state and the fetched value
console.log(accounts, accountList);
// accounts (state) will be undefined
// if the fetch was successful, accountList will be an array of accounts (as per the API payload)
}
fetchAccounts()
}, [])
return <div>
{JSON.stringify(accounts)}
</div>
}
Edit: using some other variable name while destructuring, confusing to use the same variable name as the state (accounts).
Working codesandbox

One thing I would change is working with try/catch surrounding async/await statements.
If your await statement fails it will never reach the console.log statement.
Unless you have another component handling those errors, I would use it in that way.
That is my suggestion:
function UserAccounts() {
const [accounts, setAccounts] = useState();
useEffect(() => {
try {
async function fetchAccounts() {
const res = await fetch(
'https://proton.api.atomicassets.io/atomicassets/v1/accounts'
);
const { accounts } = await res.json();
setAccounts(accounts);
console.log(accounts);
}
} catch (err) {
console.log(err)
// do something like throw your error
}
fetchAccounts();
}, []);
}

since state function runs asyncronousely . therefore when you use setAccounts it sets accounts variable in async way , so there is a preferred way of doing this thing is as below
problems i seen
1.fetch result should destructured with data instead of accounts variable
2.setAccounts function is running async way so it will not print result immedietly in next line
import { useEffect, useState } from "react";
export default function App() {
const [accounts, setAccounts] = useState();
async function fetchAccounts() {
const res = await fetch(
"https://proton.api.atomicassets.io/atomicassets/v1/accounts"
);
const { data } = await res.json();
setAccounts(data);
}
// on component mount / onload
useState(() => {
fetchAccounts();
}, []);
// on accounts state change
useEffect(() => {
console.log(accounts);
}, [accounts]);
return <div className="blankElement">hello world</div>;
}
check here sample

Related

useEffect must not return anything beside a function, which is used for clean-up Error Comes up Every Screen [duplicate]

I was trying the useEffect example something like below:
useEffect(async () => {
try {
const response = await fetch(`https://www.reddit.com/r/${subreddit}.json`);
const json = await response.json();
setPosts(json.data.children.map(it => it.data));
} catch (e) {
console.error(e);
}
}, []);
and I get this warning in my console. But the cleanup is optional for async calls I think. I am not sure why I get this warning. Linking sandbox for examples. https://codesandbox.io/s/24rj871r0p
For React version <=17
I suggest to look at Dan Abramov (one of the React core maintainers) answer here:
I think you're making it more complicated than it needs to be.
function Example() {
const [data, dataSet] = useState<any>(null)
useEffect(() => {
async function fetchMyAPI() {
let response = await fetch('api/data')
response = await response.json()
dataSet(response)
}
fetchMyAPI()
}, [])
return <div>{JSON.stringify(data)}</div>
}
Longer term we'll discourage this pattern because it encourages race conditions. Such as — anything could happen between your call starts and ends, and you could have gotten new props. Instead, we'll recommend Suspense for data fetching which will look more like
const response = MyAPIResource.read();
and no effects. But in the meantime you can move the async stuff to a separate function and call it.
You can read more about experimental suspense here.
If you want to use functions outside with eslint.
function OutsideUsageExample({ userId }) {
const [data, dataSet] = useState<any>(null)
const fetchMyAPI = useCallback(async () => {
let response = await fetch('api/data/' + userId)
response = await response.json()
dataSet(response)
}, [userId]) // if userId changes, useEffect will run again
useEffect(() => {
fetchMyAPI()
}, [fetchMyAPI])
return (
<div>
<div>data: {JSON.stringify(data)}</div>
<div>
<button onClick={fetchMyAPI}>manual fetch</button>
</div>
</div>
)
}
For React version >=18
Starting with React 18 you can also use Suspense, but it's not yet recommended if you are not using frameworks that correctly implement it:
In React 18, you can start using Suspense for data fetching in opinionated frameworks like Relay, Next.js, Hydrogen, or Remix. Ad hoc data fetching with Suspense is technically possible, but still not recommended as a general strategy.
If not part of the framework, you can try some libs that implement it like swr.
Oversimplified example of how suspense works. You need to throw a promise for Suspense to catch it, show fallback component first and render Main component when promise it's resolved.
let fullfilled = false;
let promise;
const fetchData = () => {
if (!fullfilled) {
if (!promise) {
promise = new Promise(async (resolve) => {
const res = await fetch('api/data')
const data = await res.json()
fullfilled = true
resolve(data)
});
}
throw promise
}
};
const Main = () => {
fetchData();
return <div>Loaded</div>;
};
const App = () => (
<Suspense fallback={"Loading..."}>
<Main />
</Suspense>
);
When you use an async function like
async () => {
try {
const response = await fetch(`https://www.reddit.com/r/${subreddit}.json`);
const json = await response.json();
setPosts(json.data.children.map(it => it.data));
} catch (e) {
console.error(e);
}
}
it returns a promise and useEffect doesn't expect the callback function to return Promise, rather it expects that nothing is returned or a function is returned.
As a workaround for the warning you can use a self invoking async function.
useEffect(() => {
(async function() {
try {
const response = await fetch(
`https://www.reddit.com/r/${subreddit}.json`
);
const json = await response.json();
setPosts(json.data.children.map(it => it.data));
} catch (e) {
console.error(e);
}
})();
}, []);
or to make it more cleaner you could define a function and then call it
useEffect(() => {
async function fetchData() {
try {
const response = await fetch(
`https://www.reddit.com/r/${subreddit}.json`
);
const json = await response.json();
setPosts(json.data.children.map(it => it.data));
} catch (e) {
console.error(e);
}
};
fetchData();
}, []);
the second solution will make it easier to read and will help you write code to cancel previous requests if a new one is fired or save the latest request response in state
Working codesandbox
Until React provides a better way, you can create a helper, useEffectAsync.js:
import { useEffect } from 'react';
export default function useEffectAsync(effect, inputs) {
useEffect(() => {
effect();
}, inputs);
}
Now you can pass an async function:
useEffectAsync(async () => {
const items = await fetchSomeItems();
console.log(items);
}, []);
Update
If you choose this approach, note that it's bad form. I resort to this when I know it's safe, but it's always bad form and haphazard.
Suspense for Data Fetching, which is still experimental, will solve some of the cases.
In other cases, you can model the async results as events so that you can add or remove a listener based on the component life cycle.
Or you can model the async results as an Observable so that you can subscribe and unsubscribe based on the component life cycle.
You can also use IIFE format as well to keep things short
function Example() {
const [data, dataSet] = useState<any>(null)
useEffect(() => {
(async () => {
let response = await fetch('api/data')
response = await response.json()
dataSet(response);
})();
}, [])
return <div>{JSON.stringify(data)}</div>
}
void operator could be used here.
Instead of:
React.useEffect(() => {
async function fetchData() {
}
fetchData();
}, []);
or
React.useEffect(() => {
(async function fetchData() {
})()
}, []);
you could write:
React.useEffect(() => {
void async function fetchData() {
}();
}, []);
It is a little bit cleaner and prettier.
Async effects could cause memory leaks so it is important to perform cleanup on component unmount. In case of fetch this could look like this:
function App() {
const [ data, setData ] = React.useState([]);
React.useEffect(() => {
const abortController = new AbortController();
void async function fetchData() {
try {
const url = 'https://jsonplaceholder.typicode.com/todos/1';
const response = await fetch(url, { signal: abortController.signal });
setData(await response.json());
} catch (error) {
console.log('error', error);
}
}();
return () => {
abortController.abort(); // cancel pending fetch request on component unmount
};
}, []);
return <pre>{JSON.stringify(data, null, 2)}</pre>;
}
I read through this question, and feel the best way to implement useEffect is not mentioned in the answers.
Let's say you have a network call, and would like to do something once you have the response.
For the sake of simplicity, let's store the network response in a state variable.
One might want to use action/reducer to update the store with the network response.
const [data, setData] = useState(null);
/* This would be called on initial page load */
useEffect(()=>{
fetch(`https://www.reddit.com/r/${subreddit}.json`)
.then(data => {
setData(data);
})
.catch(err => {
/* perform error handling if desired */
});
}, [])
/* This would be called when store/state data is updated */
useEffect(()=>{
if (data) {
setPosts(data.children.map(it => {
/* do what you want */
}));
}
}, [data]);
Reference => https://reactjs.org/docs/hooks-effect.html#tip-optimizing-performance-by-skipping-effects
For other readers, the error can come from the fact that there is no brackets wrapping the async function:
Considering the async function initData
async function initData() {
}
This code will lead to your error:
useEffect(() => initData(), []);
But this one, won't:
useEffect(() => { initData(); }, []);
(Notice the brackets around initData()
For fetching from an external API using React Hooks, you should call a function that fetches from the API inside of the useEffect hook.
Like this:
async function fetchData() {
const res = await fetch("https://swapi.co/api/planets/4/");
res
.json()
.then(res => setPosts(res))
.catch(err => setErrors(err));
}
useEffect(() => {
fetchData();
}, []);
I strongly recommend that you do not define your query inside the useEffect Hook, because it will be re-render infinite times. And since you cannot make the useEffect async, you can make the function inside of it to be async.
In the example shown above, the API call is in another separated async function so it makes sure that the call is async and that it only happens once. Also, the useEffect's dependency array (the []) is empty, which means that it will behave just like the componentDidMount from React Class Components, it will only be executed once when the component is mounted.
For the loading text, you can use React's conditional rendering to validate if your posts are null, if they are, render a loading text, else, show the posts. The else will be true when you finish fetching data from the API and the posts are not null.
{posts === null ? <p> Loading... </p>
: posts.map((post) => (
<Link key={post._id} to={`/blog/${post.slug.current}`}>
<img src={post.mainImage.asset.url} alt={post.mainImage.alt} />
<h2>{post.title}</h2>
</Link>
))}
I see you already are using conditional rendering so I recommend you dive more into it, especially for validating if an object is null or not!
I recommend you read the following articles in case you need more information about consuming an API using Hooks.
https://betterprogramming.pub/how-to-fetch-data-from-an-api-with-react-hooks-9e7202b8afcd
https://reactjs.org/docs/conditional-rendering.html
try
const MyFunctionnalComponent: React.FC = props => {
useEffect(() => {
// Using an IIFE
(async function anyNameFunction() {
await loadContent();
})();
}, []);
return <div></div>;
};
Other answers have been given by many examples and are clearly explained, so I will explain them from the point of view of TypeScript type definition.
The useEffect hook TypeScript signature:
function useEffect(effect: EffectCallback, deps?: DependencyList): void;
The type of effect:
// NOTE: callbacks are _only_ allowed to return either void, or a destructor.
type EffectCallback = () => (void | Destructor);
// Destructors are only allowed to return void.
type Destructor = () => void | { [UNDEFINED_VOID_ONLY]: never };
Now we should know why effect can't be an async function.
useEffect(async () => {
//...
}, [])
The async function will return a JS promise with an implicit undefined value. This is not the expectation of useEffect.
Please try this
useEffect(() => {
(async () => {
const products = await api.index()
setFilteredProducts(products)
setProducts(products)
})()
}, [])
To do it properly and avoid errors: "Warning: Can't perform a React state update on an unmounted..."
useEffect(() => {
let mounted = true;
(async () => {
try {
const response = await fetch(`https://www.reddit.com/r/${subreddit}.json`);
const json = await response.json();
const newPosts = json.data.children.map(it => it.data);
if (mounted) {
setPosts(newPosts);
}
} catch (e) {
console.error(e);
}
})();
return () => {
mounted = false;
};
}, []);
OR External functions and using an object
useEffect(() => {
let status = { mounted: true };
query(status);
return () => {
status.mounted = false;
};
}, []);
const query = async (status: { mounted: boolean }) => {
try {
const response = await fetch(`https://www.reddit.com/r/${subreddit}.json`);
const json = await response.json();
const newPosts = json.data.children.map(it => it.data);
if (status.mounted) {
setPosts(newPosts);
}
} catch (e) {
console.error(e);
}
};
OR AbortController
useEffect(() => {
const abortController = new AbortController();
(async () => {
try {
const response = await fetch(`https://www.reddit.com/r/${subreddit}.json`, { signal: abortController.signal });
const json = await response.json();
const newPosts = json.data.children.map(it => it.data);
setPosts(newPosts);
} catch (e) {
if(!abortController.signal.aborted){
console.error(e);
}
}
})();
return () => {
abortController.abort();
};
}, []);
I know it is late but just I had the same problem and I wanted to share that I solved it with a function like this!
useEffect(() => {
(async () => {
try {
const response = await fetch(`https://www.reddit.com/r/${subreddit}.json`);
const json = await response.json();
setPosts(json.data.children.map(it => it.data));
} catch (e) {
console.error(e);
}
}) ()
}, [])
With useAsyncEffect hook provided by a custom library, safely execution of async code and making requests inside effects become trivially since it makes your code auto-cancellable (this is just one thing from the feature list). Check out the Live Demo with JSON fetching
import React from "react";
import { useAsyncEffect } from "use-async-effect2";
import cpFetch from "cp-fetch";
/*
Notice: the related network request will also be aborted
Checkout your network console
*/
function TestComponent(props) {
const [cancel, done, result, err] = useAsyncEffect(
function* () {
const response = yield cpFetch(props.url).timeout(props.timeout);
return yield response.json();
},
{ states: true, deps: [props.url] }
);
return (
<div className="component">
<div className="caption">useAsyncEffect demo:</div>
<div>
{done ? (err ? err.toString() : JSON.stringify(result)) : "loading..."}
</div>
<button className="btn btn-warning" onClick={cancel} disabled={done}>
Cancel async effect
</button>
</div>
);
}
export default TestComponent;
The same demo using axios
Just a note about HOW AWESOME the purescript language handles this problem of stale effects with Aff monad
WITHOUT PURESCRIPT
you have to use AbortController
function App() {
const [ data, setData ] = React.useState([]);
React.useEffect(() => {
const abortController = new AbortController();
void async function fetchData() {
try {
const url = 'https://jsonplaceholder.typicode.com/todos/1';
const response = await fetch(url, { signal: abortController.signal });
setData(await response.json());
} catch (error) {
console.log('error', error);
}
}();
return () => {
abortController.abort(); // cancel pending fetch request on component unmount
};
}, []);
return <pre>{JSON.stringify(data, null, 2)}</pre>;
}
or stale (from NoahZinsmeister/web3-react example)
function Balance() {
const { account, library, chainId } = useWeb3React()
const [balance, setBalance] = React.useState()
React.useEffect((): any => {
if (!!account && !!library) {
let stale = false
library
.getBalance(account)
.then((balance: any) => {
if (!stale) {
setBalance(balance)
}
})
.catch(() => {
if (!stale) {
setBalance(null)
}
})
return () => { // NOTE: will be called every time deps changes
stale = true
setBalance(undefined)
}
}
}, [account, library, chainId]) // ensures refresh if referential identity of library doesn't change across chainIds
...
WITH PURESCRIPT
check how useAff kills it's Aff in the cleanup function
the Aff is implemented as a state machine (without promises)
but what is relevant to us here is that:
the Aff encodes how to stop the Aff - You can put your AbortController here
it will STOP running Effects (not tested) and Affs (it will not run then from the second example, so it will NOT setBalance(balance)) IF the error was thrown TO the fiber OR INSIDE the fiber
Ignore the warning, and use the useEffect hook with an async function like this:
import { useEffect, useState } from "react";
function MyComponent({ objId }) {
const [data, setData] = useState();
useEffect(() => {
if (objId === null || objId === undefined) {
return;
}
async function retrieveObjectData() {
const response = await fetch(`path/to/api/objects/${objId}/`);
const jsonData = response.json();
setData(jsonData);
}
retrieveObjectData();
}, [objId]);
if (objId === null || objId === undefined) {
return (<span>Object ID needs to be set</span>);
}
if (data) {
return (<span>Object ID is {objId}, data is {data}</span>);
}
return (<span>Loading...</span>);
}
The most easy way is to use useAsyncEffect from 'use-async-effect'
You can find it on NPM.
const ProtectedRoute = ({ children }) => {
const [isAuth, setIsAuth] = useState(false);
useAsyncEffect(async () => {
try {
const data = await axios("auth");
console.log(data);
setIsAuth(true);
} catch (error) {
console.log(error);
}
}, []);
if (!isAuth)
return <Navigate to="/signin" />
return children;
}

get data from async function to another function React JS

I have problem with async function. I need track.user in another function but my func getTracks() async. I don't have clue how can i get this.
const Player = ({trackUrl, index, cover, id}) => {
const [track, setTrack] = useState({})
const [user, setUser] = useState({})
useEffect(() => {
const getTracks = async () => {
await httpClient.get(`/track/${id}`)
.then((response) => {
setTrack(response.data);
})
}
getTracks();
getUser() // track.user undefined
}, [])
const getUser = async() => {
await httpClient.get(`/profile/${track.user}/`)
.then((response) => {
setUser(response.data);
})
}
}
I would declare both functions at the beginning of the component (you can later optimise them with useCallback but it's not that important in this phase).
const getTracks = async () => {
await httpClient.get(`/track/${id}`)
.then((response) => {
setTrack(response.data);
})
}
const getUser = async() => {
await httpClient.get(`/profile/${track.user}/`)
.then((response) => {
setUser(response.data);
})
}
I would then call an async function inside the useEffect hook. There are a couple of ways of doing it: you can either declare an async function in the useEffect hook and call it immediately, or you can call an anonymous async function. I prefer the latter for brevity, so here it is:
useEffect(() => {
(async () => {
await getTracks();
getUser();
})();
}, []);
Now when you call getUser you should be sure that getTracks has already set the track variable.
Here is the complete component:
const Player = ({trackUrl, index, cover, id}) => {
const [track, setTrack] = useState({})
const [user, setUser] = useState({})
const getTracks = async () => {
await httpClient.get(`/track/${id}`)
.then((response) => {
setTrack(response.data);
})
}
const getUser = async() => {
await httpClient.get(`/profile/${track.user}/`)
.then((response) => {
setUser(response.data);
})
}
useEffect(() => {
(async () => {
await getTracks();
getUser();
})();
}, []);
}
EDIT 07/18/22
Following Noel's comments and linked sandbox, I figured out that my answer wasn't working. The reason why it wasn't working is that the track variable was't available right after the getTrack() hook execution: it would have been available on the subsequent render.
My solution is to add a second useEffect hook that's executed every time the track variable changes. I have created two solutions with jsonplaceholder endpoints, one (see here) which preserves the most of the original solution but adds complexity, and another one (here) which simplifies a lot the code by decoupling the two methods from the setTrack and setUser hooks.
I'll paste here the simpler one, adapted to the OP requests.
export default function Player({ trackUrl, index, cover, id }) {
const [track, setTrack] = useState({});
const [user, setUser] = useState({});
const getTracks = async () => {
// only return the value of the call
return await httpClient.get(`/track/${id}`);
};
const getUser = async (track) => {
// take track as a parameter and call the endpoint
console.log(track, track.id, 'test');
return await httpClient.get(`profile/${track.user}`);
};
useEffect(() => {
(async () => {
const trackResult = await getTracks();
// we call setTrack outside of `getTracks`
setTrack(trackResult);
})();
}, []);
useEffect(() => {
(async () => {
if (track && Object.entries(track).length > 0) {
// we only call `getUser` if we are sure that track has at least one entry
const userResult = await getUser(track);
console.log(userResult);
setUser(userResult);
}
})();
}, [track]);
return (
<div className="App">{user && user.id ? user.id : "Not computed"}</div>
);
}
You can move the second request to the then block of the dependent first request,i.e., getTracks.
Also, you shouldn't mix then and await.
useEffect(() => {
const getTracks = () => {
httpClient.get(`/track/${id}`)
.then((response) => {
setTrack(response.data);
httpClient.get(`/profile/${response.data.user}/`)
.then((response) => {
setUser(response.data);
})
})
}
getTracks();
}, [])
You shouldn't be mixing thens with async/await. You should be using another useEffect that watches out for changes in the track state and then calls getUser with that new data.
function Player(props) {
const { trackUrl, index, cover, id } = props;
const [ track, setTrack ] = useState({});
const [ user, setUser ] = useState({});
async function getTracks(endpoint) {
const response = await httpClient.get(endpoint);
const data = await response.json();
setTrack(data);
}
async function getUser(endpoint) {
const response = await httpClient.get(endpoint);
const data = await response.json();
setUser(data);
}
useEffect(() => {
if (id) getTracks(`/track/${id}`);
}, []);
useEffect(() => {
if (track.user) getUser(`/profile/${track.user}`);
}, [track]);
}

Array map is giving me an error when trying to call the data in a dynamic render operation

function UserTransactionsComponent1() {
const [accounts, setAccounts] = useState();
useEffect(() => {
async function fetchData() {
const res = await fetch(
'https://proton.api.atomicassets.io/atomicassets/v1/accounts'
);
const { data } = await res.json();
setAccounts(data);
}
fetchData();
}, []);
accounts.map((result) => {
const { account } = result;
});
return <PageLayout>Hi! {account}</PageLayout>;
}
export default UserTransactionsComponent1;
I console.log(accounts) right before I map it and all the properties are there. The issue is that the account in the acounts.map is showing greyed out on VSCode. It's not being picked up on the return. This is causing me to receive the following error: TypeError: Cannot read properties of undefined (reading 'map'). What's the reason for this?
The return statement is outside the variable (account) scope.
function UserTransactionsComponent1() {
const [accounts, setAccounts] = useState();
useEffect(() => {
async function fetchData() {
const res = await fetch(
"https://proton.api.atomicassets.io/atomicassets/v1/accounts"
);
const { data } = await res.json();
setAccounts(data);
}
fetchData();
}, []);
const getAccounts = () => {
if (accounts)
return accounts?.map((result) => {
const { account } = result;
return account;
})
}
return (
<PageLayout>
Hi!{" "}
{getAccounts()}
</PageLayout>
);
}
export default UserTransactionsComponent1;
The problem is that your map function is running before your fetch has completed, so accounts is still undefined when you try mapping.
There's a few ways to solve this. One options is just to use .then(). So put your map function inside of .then, inside your useEffect.
.then(() => accounts.map( // insert rest of function here ))
This tells the code to run the map function only after the fetch completes
accounts is not defined until the fetch is complete, so you need to map it in an effect, which waits for the state of accounts to be set:
useEffect(() => {
accounts.map(...);
}, [accounts]);
On top of that, when you return, account will be undefined. You can create a loading screen or something while the data is fetching, then re-render with the data:
return (
<PageLayout>{accounts ? accounts : "Loading..."}</PageLayout>
);
I'm not sure what you're trying to do in your map function; you're not specifying specifically which account in the array you want; you'll need another state.

Function not getting called in useEffect()

I want these two functions to be called every time the component renders, but they are not being executed. And when I put the functions in the dependency array it results in an infinite loop. Any idea why they are not being called?
function PortfolioComponent() {
const [requestedAssets, setRequestedAssets] = useState([]);
const [assets, setAssets] = useState([]);
useEffect(() => {
async function calcValue() {
Promise.all(
requestedAssets.map(async function (asset) {
try {
const response = await axios.get(assetData(asset.AssetId));
let cp = response.data.market_data.current_price.eur;
let value = Number(cp) * Number(asset.Amount);
return { ...asset, value: value, price: cp };
} catch (error) {
console.log(error.response.data.error);
throw error;
}
})
)
.then((newAssetArray) => {
setAssets(newAssetArray);
console.log(newAssetArray);
console.log(assets);
})
.catch((error) => {
console.log(error);
});
}
async function getAssets() {
try {
const response = await axios.get("http://localhost:4200/assets");
// Do as you wish with response here
const assetResponse = response.data.rows;
setRequestedAssets(assetResponse);
console.log(requestedAssets);
} catch (error) {
console.log(error.response.data.error);
}
}
getAssets();
calcValue();
}, []);
Also some weird behaviour I just discovered...
For example, this line of code:
let cp = await response.data.market_data.current_price.eur;
When I remove the await keyword and save it in VS code, the data is retrieved as expected. However, when I refresh the browser the arrays are empty again. The same goes for when I add the await keyword again and save. The same thing happens.
This is what worked for me. So, instead of having a useState variable for requestedAssets, I created a variable inside the getAssets method instead. I'm not exactly sure why this works and not the other way. But, if anybody could explain, that would be great.
function PortfolioComponent() {
//const [requestedAssets, setRequestedAssets] = useState([]);
const [assets, setAssets] = useState([]);
useEffect(() => {
async function getAssets() {
const response = await axios.get("http://localhost:4200/assets");
const requestedAssets = response.data.rows;
console.log(requestedAssets);
Promise.all(
requestedAssets.map(async function (asset) {
try {
const response = await axios.get(assetData(asset.AssetId));
let cp = response.data.market_data.current_price.eur;
let value = Number(cp) * Number(asset.Amount);
return { ...asset, value: value, price: cp };
} catch (error) {
console.log(error.response.data.error);
throw error;
}
})
)
.then((newAssetArray) => {
setAssets(newAssetArray);
console.log(newAssetArray);
console.log(assets);
})
.catch((error) => {
console.log(error);
});
}
getAssets();
}, []);
The recommendation is to declare your functions inside the useEffect, see the official documentation. If you keep scrolling in the docs, they even have an example similar to yours, with an async function.
If, for some reason, you do need to have your function declared outside the useEffect, you can use a useCallback, which allows you to declare them in the dependency array. Something like this:
const getAssets = useCallback(async() => {
try {
const response = await axios.get("http://localhost:4200/assets");
// Do as you wish with response here
const assetResponse = response.data.rows;
setRequestedAssets(assetResponse);
console.log(requestedAssets);
} catch (error) {
console.log(error.response.data.error);
}
}, [requestedAssets])
useEffect(() => {
getAssets()
}, [getAssets])
You can also see the section Do I need to specify functions as effect dependencies or not? in this blog here for more information.
PS: This blog is from Dan Abramov, one of the creators of React, so reliable source ;)

How to pass state from one useEffect to another useEffect on intial page load?

I have a component which displays products for a category. CategoryId is taken from subscribe method which is formed by pubsub pattern so I am waiting sub function to finish and passing to my API but it is not working on intial load of the page?
import { subscribe } from "./pubsub";
const Test = () => {
const [productId, setProductId] = useState({});
const [response, setResponse] = useState([]);
React.useEffect(() => {
function sub() {
return new Promise((resolve, reject) => {
subscribe("product-message", (data) => {
// console.log("Got some message", data);
// setProductId(data.productId);
resolve(data.productId);
});
});
}
async function fetchData() {
let message = await sub();
let response = await fetch(
`https://jsonplaceholder.typicode.com/todos/${message.productId}` // Here I couldn't get the async data from above useEffect
);
console.log(response);
setResponse(response);
}
fetchData();
}, []);
return <div>{response.title}</div>; //It is not printing in intial load
};
export default Test;
So here is my sandbox link: https://codesandbox.io/s/happy-forest-to9pz?file=/src/test.jsx
If you only need the response, you do not need to store productId in state and then use it in another useEffeect to fetch data. You can simply implement the logic in one useEffec. Also note that you need to use the json response from fetch call so you need to use it like
let response = await fetch(
`https://jsonplaceholder.typicode.com/todos/${productId}`
).then(res => res.json());
or
let res = await fetch(
`https://jsonplaceholder.typicode.com/todos/${productId}`
)
let response = await res.json();
Complete function will look like
const Test = () => {
const [response, setResponse] = useState([]);
React.useEffect(() => {
async function fetchData(productId) {
let response = await fetch(
`https://jsonplaceholder.typicode.com/todos/${productId}`
).then(res => res.json());
console.log(response);
setResponse(response);
}
console.log("Api calls");
subscribe("product-message", (data) => {
// console.log("Got some message", data);
fetchData(data.productId);
});
}, []);
return <div>{response.title}</div>;
};
export default Test;
However if you need productId in your application, you can go via a multiple useEffect approach like you have tried in your sandbox. Also make sure that you are using thee fetch call correctly and also make sure to not make the API call wheen productId is not available
const Test = () => {
const [productId, setProductId] = useState({});
const [response, setResponse] = useState([]);
React.useEffect(() => {
console.log("Api calls");
subscribe("product-message", (data) => {
// console.log("Got some message", data);
setProductId(data.productId);
});
}, []);
React.useEffect(() => {
async function fetchData() {
const res = await fetch(
`https://jsonplaceholder.typicode.com/todos/${productId}` // Here I couldn't get the async data from above useEffect
);
const response = await res.json();
console.log(response);
setResponse(response);
}
if(productId) {
fetchData();
}
}, [productId]);
return <div>{response.title}</div>;
};
export default Test;
Working Sandbox

Categories