Code is moving on before my fetch is completed - javascript

I'm having a problem where inside my useEffect whenever I call getRoomDetails, the code continues on before it's finished fetching data from my API. So it will create the Chess object with the default value I've given for boardState, instead of the updated value from my API. How could I get it so it waits until getRoomDetails finishes, before moving onto creating the Chess object.
const initialState = {
hostTime: 600,
guestTime: 600,
chessAnnotations: "",
isHost: true,
fen: "start",
}
const getRoomDetails = () => {
fetch('/api/get-room?code=' + roomCode).then((response) =>
response.json()
).then((data) => {
const newObj = {
hostTime: data.host_curr_time,
guestTime: data.guest_curr_time,
chessAnnotations: data.chess_annotations,
isHost: data.is_host,
fen: data.fen,
};
setBoardState(newObj);
console.log(newObj)
});
}
const [boardState, setBoardState] = useState(initialState);
let game = useRef(null);
useEffect(() => {
getRoomDetails();
console.log(boardState.fen + "lit");
game.current = new Chess(boardState.fen);
console.log("0");
}, []);
Output:
start 0
0
Object { hostTime: "600.00", guestTime: "600.00", chessAnnotations: "sdf", isHost: false, fen: "rnbqkbnr/pppppppp/8/8/8/3P4/PPP1PPPP/RNBQKBNR b KQkq - 0 1" }

See the explanation in the inline comments
const initialState = {
hostTime: 600,
guestTime: 600,
chessAnnotations: "",
isHost: true,
fen: "start",
}
const getRoomDetails = () => {
// HERE: Return the promise
return fetch('/api/get-room?code=' + roomCode).then((response) =>
response.json()
).then((data) => {
const newObj = {
hostTime: data.host_curr_time,
guestTime: data.guest_curr_time,
chessAnnotations: data.chess_annotations,
isHost: data.is_host,
fen: data.fen,
};
setBoardState(newObj);
console.log(newObj)
});
}
const [boardState, setBoardState] = useState(initialState);
let game = useRef(null);
useEffect(() => {
getRoomDetails()
// HERE: run this block after the promise is resolved
.then(() => {
console.log(boardState.fen + "lit");
game.current = new Chess(boardState.fen);
console.log("0");
});
}, []);

Related

How to fetch data from the Jikanapi

I want to call an API here
https://api.jikan.moe/v4/top/anime to get the data in raw format and then create an array of only useful data out of it. What is the reason the data is not being consoled
const initialAnime = {
anime: [],
genresLoaded: false,
genres: [],
};
function createAnimeFromRawData(rawData, animeArray) {
const data = rawData.data;
data.forEach((animeData) => {
const anime = {
mal_id: animeData.mal_id,
title: animeData.title,
title_english: animeData.title_english,
type: animeData.type,
episodes: animeData.episodes,
status: animeData.status,
duration: animeData.duration,
rating: animeData.rating,
rank: animeData.rank,
synopsis: animeData.synopsis,
};
console.log(animeArray);
animeArray.push(anime);
});
}
const RawdataAnime = async (api, genre, paging) => {
const Animearray = [];
for (let i = 1; Animearray.length < 60 && i < 10; i++) {
const {
data: { results },
} = await axios.get(`${api}`);
createAnimeFromRawData(results, Animearray);
}
return Animearray;
};
export const fetchAnime = createAsyncThunk(
"myanimelist/topAnime",
async (thunkAPI) => {
const {
myanimelist: { genres },
} = thunkAPI.getState();
return RawdataAnime(`https://api.jikan.moe/v4/top/anime`, genres, false);
}
);
const animeSlice = createSlice({
name: "Myanimelist",
initialState: initialAnime,
extraReducers: (builder) => {
builder.addCase(getGenresAnime.fulfilled, (state, action) => {
state.genres = action.payload;
state.genresLoaded = true;
});
builder.addCase(fetchAnime.fulfilled, (state, action) => {
state.anime = action.payload;
});
},
});
export const store = configureStore({
reducer: {
netflix: netflixSlice.reducer,
anime: animeSlice.reducer,
},
});
I tried the code above to get an array of only useful parts of data in the code but there was nothing in the console. There was no error and no output.
Whereas the response.data will be something similar to the json below::
{
"pagination":{
...
},
"data":[
...
],
"links":{
...
},
"meta":{
...
}
}
I believe the error is in the snippet
const { data: { results }} = await axios.get(`${api}`); // There are no results in the returned content
createAnimeFromRawData(results, Animearray);
Try something like
const { data } = await axios.get(`${api}`); // Equivalent to response.data
const results = data?.data || []
createAnimeFromRawData(results, Animearray);

how to use async function with custom hooks inside useEffects

i want the get the moralis useTokenPrice to fetch an updated price after every five seconds, but from the rules of hook a react hook cannot be used inside useEffects.
how do i go about it.
my code
function SpeedPrice(props) {
const [price, setPrice] = useState({
symbol: "",
token_address: "",
price: "",
});
const MINUTE_MS = 5000;
const address = props.address;
const symbol = props.symbol;
async function GetPrice() {
const result = await useTokenPrice({ // moralis hook
chain: "eth",
address: address,
});
const usdPrice = result.data.formattedUsd;
setPrice({ symbol: symbol, token_address: address, price: usdPrice });
}
// GetPrice(); infinite loop
useEffect(() => {
const interval = setInterval(() => {
console.log("call getprice");
// GetPrice() error! React Hooks must be called in a React function component or a custom React Hook function
}, MINUTE_MS);
return () => clearInterval(interval);
}, []);
return price.price;
}
what i have done
useEffect(() => {
const interval = setInterval(() => {
// moved the function inside useEffects
async function GetPrice() {
const result = await useTokenPrice({ // moralis hook
chain: "eth",
address: address,
});
const usdPrice = result.data.formattedUsd;
setPrice({ symbol: symbol, token_address: address, price: usdPrice });
}
GetPrice();
}, MINUTE_MS);
return () => clearInterval(interval);
}, []);
You can use hooks only in top level. But in your case
useTokenPrice return fetch function which you can use everywhere:
const {fetchTokenPrice/*👈*/, data /*👈*/} = useTokenPrice({
chain: 'eth',
address: address
});
useEffect(() => {
const interval = setInterval(async () => {
console.log('call getprice');
await fetchTokenPrice(address); // 👈
}, MINUTE_MS);
return () => clearInterval(interval);
}, []);
const usdPrice = data.formattedUsd; // 👈
return data.isLoading || data.isFetching ? 'Loading...' : usdPrice;

Struggling using react-redux hooks with state

Hello fellow developers,
I'm creating a little app in react native, and i'm using react-redux to store and manage my datas. I've got an events.js reducer registered in my store, using 'events/load' dispatch action to set the initialState in my App.js. It looks like that :
import { auth, firestore } from '../lib/firebase'
const initialState = {
events: [],
userEvents: []
}
export default async function eventsReducer(state = initialState, action) {
switch (action.type) {
case 'events/load': {
const userId = auth.currentUser.uid
const eventsData = []
const userEventsData = []
// starts with other events
const queryEvents = await firestore.collection("events").where("userId", "!=", userId).get()
if (queryEvents) {
queryEvents.forEach(ev => {
eventsData.push(Object.assign({}, { id: ev.id }, ev.data()))
})
}
// user events
const userEvents = await firestore.collection("events").where("userId", "==", userId).get()
if (userEvents) {
userEvents.forEach(ev => {
userEventsData.push(Object.assign({}, { id: ev.id }, ev.data()))
})
}
return {
...state,
events: eventsData,
userEvents: userEventsData
}
}
case 'events/set': {
// Set the classics events
return {
...state,
events: action.payload
}
}
case 'userEvents/set': {
// Set the userEvents
return {
...state,
userEvents: action.payload
}
}
default:
return state
}
}
Now, I have a screen, using SectionList from react native, and I want to display there all the userEvents from my store, previously loaded. The problem is that my useSelector hook only return Promise when i try to get events. If i try to get events.userEvents, it's undefined.
Here what it look like :
const Manage = ({navigation}) => {
const [loading, setLoading] = React.useState(true);
const [events, setEvents] = React.useState([]);
const eventsSelector = useSelector((state) => state.events)
// use effect one time
useEffect(() => {
async function loadDatas() {
console.log('events selected', eventsSelector) // what i've got in my events ?
// get the datas for coming events
const storedEventsIds = []
const comingEvents = []
const passedEvents = []
if (eventsSelector !== null) {
eventsSelector.userEvents.forEach(event => { // <= THIS IS NOT WORKING - error : Cannot read properties of undefined (reading 'forEach')
// compare dates to know if it's a coming or passed event
const dateTimeStart = new Date(`${event.dateStart} ${event.timeStart}`)
const dateTimeEnd = new Date(`${event.dateEnd} ${event.timeEnd}`)
const dateNow = new Date()
if (!(doc.id in storedEventsIds)) {
if (compareAsc(dateTimeStart, dateNow) >= 0 ||
(compareAsc(dateTimeStart, dateNow) < 0 && compareAsc(dateTimeEnd, dateNow) >= 0)) {
comingEvents.push(Object.assign({}, event, {id: doc.id, passed: false}))
storedEventsIds.push(doc.id)
} else {
passedEvents.push(Object.assign({}, event, {id: doc.id, passed: true}))
storedEventsIds.push(doc.id)
}
}
})
}
// set events
setEvents([{
title: "Événements à venir",
data: comingEvents
}, {
title: "Événéments passés",
data: passedEvents
}]);
setLoading(false)
};
loadDatas()
}, [])
// ... omitted manage component code - basically only html
}
When i console log my eventsSelector this is what i've got, so you can see that I have some userEvents. But it's a Promise and i don't really know why...
events selected Promise {
"_U": 0,
"_V": 1,
"_W": Object {
"_U": 0,
"_V": 1,
"_W": Object {
"events": Array [],
"userEvents": Array [],
},
"_X": null,
"events": Array [],
"userEvents": Array [
Object {
"coverImage": null,
"dateEnd": "04/02/2022",
"dateStart": "04/11/2021",
"description": "Hbzbwkks
Sjjdjdjdjd
Wjdkkdieizhdbf
Sjjdjdjdjd",
"id": "ewPrFzAqQmusKrsqQnSP",
"maxLimit": null,
"online": true,
"openPrice": false,
"place": null,
"price": 50,
"secretPlace": false,
"timeEnd": "17:25",
"timeStart": "17:25",
"title": "Mon super event",
"userId": "pPL2g7bWDYZDzUGtlGCSyLOQ3WK2",
"website": "https://bloodbee.space",
},
Object {
"dateEnd": "05/10/2021",
"dateStart": "04/10/2021",
"description": "La teuf de l'année - 24h de folie ouais :)",
"id": "gSlmysO0KffkjGKyP9fR",
"maxLimit": 3000,
"online": false,
"openPrice": false,
"place": "Puy du pariou",
"price": 30,
"secretPlace": true,
"timeEnd": "23:00",
"timeStart": "23:00",
"title": "Rave party de l'année",
"userId": "pPL2g7bWDYZDzUGtlGCSyLOQ3WK2",
"website": null,
},
],
},
"_X": null,
}
How can I fix this ? Do you have any improvment suggestions ?
Thank you for your help :)
Edit :
Some code about my App.js
const AppWrapped = () => {
const [appIsReady, setAppIsReady] = useState(false);
const [signedIn, setSignedIn] = useState(false);
const dispatch = useDispatch()
const loadApp = async () => {
// Manage auth
await auth.onAuthStateChanged( async (user) => {
if (user) {
// Log the user
await dispatch({ type: 'users/set', payload: user })
// dispatch to get events
await dispatch({ type: 'events/load' })
// User is signed in, see docs for a list of available properties
// https://firebase.google.com/docs/reference/js/firebase.User
setSignedIn(true)
} else {
// User is signed out
await dispatch({ type: 'users/set', payload: null })
setSignedIn(false)
}
})
}
if (!appIsReady) {
return (
<AppLoading
startAsync={loadApp}
onFinish={() => setAppIsReady(true)}
onError={console.warn}
autoHideSplash={true}
/>
)
} else {
// omitted html - when the app is ready - no more splashscreen
}
};
const App = () => {
return (
<StoreProvider store={store}>
<PaperProvider theme={theme}>
<AppWrapped />
</PaperProvider>
</StoreProvider>
)
};
did you dispatch the events/load action ?
/// src/redux/events/eventActions.js
import store from '../store'
function loadEvents() {
return {
type: 'events/load'
}
}
store.dispatch(loadEvents())
A good practice is to create a const for every action like this :
/// src/redux/events/eventTypes.js
const LOAD_EVENTS = 'events/load';
/// src/redux/events/eventActions.js
import store from '../store'
import { LOAD_EVENTS } from './eventTypes.js'
function loadEvents() {
return {
type: LOAD_EVENTS
}
}
store.dispatch(loadEvents())
/// src/redux/events/eventReducer.js
import { LOAD_EVENTS } from './eventTypes.js'
const initialState = {
events: [],
...
}
export default async function eventsReducer(state = initialState, action) {
switch (action.type) {
case LOAD_EVENTS: {
... Some code
return {
...state,
events: eventsData,
userEvents: userEventsData
}
}
default:
return state
}
}

Testing a ResizeObserver inside an Effect Fn

I'm struggling a bit with unit testing the following:
const { element, state } = props;
const { theme } = state;
const { computedHeight, computedWidth } = theme;
const elementToSize = document.querySelector(element);
const observer = new ResizeObserver(element => {
const { contentRect } = element[0];
// set the viewport size in the state
dispatch(setViewportSize(state, contentRect.width, contentRect.height));
// perform the task function
wrapper(600, computedWidth, computedHeight, contentRect.width, contentRect.height).
then(() => {
elementToSize.style.height = `${contentRect.height <= computedHeight ? contentRect.height : computedHeight}px`;
elementToSize.style.width = `${contentRect.width <= computedWidth ? contentRect.width : computedWidth}px`;
}).catch( e => new Error(e));
});
observer.observe(document.documentElement);
const wrapper = async (ms, ...callbackArgs) => {
try {
await asyncInterval(checkSize, ms, ...callbackArgs);
} catch {
new Error('async Interval has failed...');
}
return await asyncInterval(checkSize, ms, ...callbackArgs);
};
};
export const SizeableEffect = (element, state) => [effectFn, { element, state } ];
In the code above, I have difficulties with unit testing the code inside the ResizeObserver.
I have the following in my test.
import ResizeObserver from 'resize-observer-polyfill';
import { SizeableEffect } from 'effects';
import { domMock } from '../mocks/dom';
jest.mock('resize-observer-polyfill');
describe('SizeableEffect', () => {
let stateMock, dispatch, effect, effectFn;
beforeEach(() => {
stateMock = {
viewportWidth: null,
viewportHeight: null,
theme: {
chatFrameSize: {
width: 300,
height: 500
}
}
};
dispatch = jest.fn();
effect = SizeableEffect('elementMock', stateMock);
effectFn = effect[0];
Object.defineProperties(window, {
document: {
value: domMock()
}
});
});
it('the effect should be called with the element', () => {
expect(effect[1]).toEqual(
expect.objectContaining({
element: 'elementMock',
state: stateMock
})
);
});
it('the effect function should perform the operations', () => {
effectFn(dispatch, { element: 'some', state: stateMock });
expect(document.querySelector).toHaveBeenCalledWith('some');
expect(dispatch).not.toHaveBeenCalled();
expect(ResizeObserver).toHaveBeenCalled();
});
});
And as you can see from the screenshot, I have uncovered lines where the ResizeObserver is doing the login by measuring the width' and height's against the viewport.
How can I cover those lines in the best way?

undefined after setState() (use hook) in Reactjs

I learn react and js myself. please explain why this situation occurs. PS: excuse me for the large text, I tried to explain the problem as clearly as possible. thanks. Essence of the matter: set the initial state through the hook:
const [pokemon, setPokemon] = useState({
img: "",
name: "",
types: [],
abilities: [],
moveList: [],
weight: "",
height: "",
description: "",
genus: "",
chanceToCatch: "",
evolutionURL: ""
});
further I make api requests to get information from inside useEffect:
useEffect(() => {
const fetchData = async () => {
await Axios({
method: "GET",
url: urlPokemonAPI
})
.then(result => {
const pokemonResponse = result.data;
/* Pokemon Information */
const img = pokemonResponse.sprites.front_default;
const name = pokemonResponse.name;
const weight = Math.round(pokemonResponse.weight / 10);
const height = pokemonResponse.height / 10;
const types = pokemonResponse.types.map(type => type.type.name);
const abilities = pokemonResponse.abilities.map(
ability => ability.ability.name
);
const moveList = pokemonResponse.moves.map(move => move.move.name);
setPokemon(() => {
return {
img: img,
name: name,
weight: weight,
types: types,
abilities: abilities,
moveList: moveList,
height: height
};
});
})
await Axios({
method: "GET",
url: urlPokemonSpecies
}).then(result => {
let description = "";
result.data.flavor_text_entries.forEach(flavor => {
if (flavor.language.name === "en") {
description = flavor.flavor_text;
}
});
let genus = "";
result.data.genera.forEach(genera => {
if (genera.language.name === "en") {
genus = genera.genus;
}
});
const evolutionURL = result.data.evolution_chain.url;
const eggGroups = result.data.egg_groups.map(
egg_group => egg_group.name
);
const chanceToCatch = Math.round(
(result.data.capture_rate * 100) / 255
);
setPokemon(pokemon => {
return {
...pokemon,
description: description,
genus: genus,
chanceToCatch: chanceToCatch,
evolutionURL: evolutionURL,
eggGroups: eggGroups
};
});
});
};
fetchData();
}, [urlPokemonAPI, urlPokemonSpecies]);
The problem arises specifically with eggGroups (with identical handling of abilities and types there is no such problem). And this is what happens when I want to output data to a page as <div> Egg Group: {pokemon.eggGroups} </div> the data is displayed normally, but as soon as I want to output eggGroups as well as abilities and types separated by commas (join ( ',')) - error: TypeError: pokemon.eggGroups is undefined. I decided to check this matter through the console and stuffed this eggGroups key into the timeout:
At some point, eggGroups becomes undefined ... why, I can’t understand. But if I set the state separately, like const [egg, setEgg] = useState ([]); setEgg (eggGroups); such a problem is not observed. why is this happening? everything was fine with types and abilities. Thank you in advance.
state updater from hooks doesn't merge the state values when updating state, instead it just replaces the old value with new one
Since you use state updater like
setPokemon(() => {
return {
img: img,
name: name,
weight: weight,
types: types,
abilities: abilities,
moveList: moveList,
height: height
};
});
eggGroups property is lost and hence it becomes undefined. You need to update it by spreading the previous state values obtained from callback
setPokemon((prev) => {
return {
...prev
img: img,
name: name,
weight: weight,
types: types,
abilities: abilities,
moveList: moveList,
height: height
};
});
Your code have a problem, this is the proper way to do await with axios,
you need to import axios like this
import axios from 'axios';
the await should be call with a promise, then it return the data from api like this:
const result = await axios.get(urlPokemonAPI);
This is the code snippet with the same logic to your code
useEffect(() => {
const fetchData = async () => {
// import axios from 'axios';
try {
const result = await axios.get(urlPokemonAPI);
const pokemon = result.data;
setPokemon({
img: pokemon.sprites.front_default,
name: pokemon.name,
weight: Math.round(pokemon.weight / 10),
types: pokemon.types.map(i => i.type.name),
abilities: pokemon.abilities.map(i => i.ability.name),
moveList: pokemon.moves.map(i => i.move.name),
height: pokemon.height / 10
});
const result2 = await axios.get(urlPokemonSpecies);
const data = result2.data;
let description = "";
data.flavor_text_entries.forEach(i => {
const lang = i.language.name
if (lang === "en") {
description = i.flavor_text;
}
});
let genus = "";
data.genera.forEach(i => {
const lang = i.language.name;
if (lang === "en") {
genus = i.genus;
}
});
setPokemon(pokemon => {
return {
...pokemon,
description,
genus,
chanceToCatch: Math.round((data.capture_rate * 100) / 255),
evolutionURL,
eggGroups: data.egg_groups.map(g => g.name)
};
});
} catch (e) {
console.log(e);
}
};
fetchData();
}, [urlPokemonAPI, urlPokemonSpecies]);
do you see another problem: you call setPokemon two times, let's rewrite it again:
useEffect(() => {
const fetchData = async () => {
// import axios from 'axios';
try {
const result = await axios.get(urlPokemonAPI);
const data1 = result.data;
const result2 = await axios.get(urlPokemonSpecies);
const data2 = result2.data;
function resolveDescription(data) {
let description = "";
data.flavor_text_entries.forEach(i => {
const lang = i.language.name
if (lang === "en") {
description = i.flavor_text;
}
});
return description;
}
function resolveGenus(data) {
let genus = "";
data.genera.forEach(i => {
const lang = i.language.name;
if (lang === "en") {
genus = i.genus;
}
});
return genus;
}
setPokemon({
img: data1.sprites.front_default,
name: data1.name,
weight: Math.round(data1.weight / 10),
types: data1.types.map(i => i.type.name),
abilities: data1.abilities.map(i => i.ability.name),
moveList: data1.moves.map(i => i.move.name),
height: data1.height / 10,
description: resolveDescription(data2),
genus: resolveGenus(data2),
chanceToCatch: Math.round((data2.capture_rate * 100) / 255),
evolutionURL: data2.evolution_chain.url,
eggGroups: data2.egg_groups.map(g => g.name)
});
} catch (e) {
console.log(e);
}
};
fetchData();
}, [urlPokemonAPI, urlPokemonSpecies]);

Categories

Resources