I'm trying to pass some data through useContext() that I'm getting from a Contentful API but I can't figure out how. Can someone tell me what I'm doing wrong?
First, I get the data and save it in a state:
const [products, setProducts] = useState([]);
function getProducts() {
Client.getEntries("products")
.then((entry) => {
entry.items.map((item) => {
setProducts(products.push(item));
});
})
.catch((err) => console.log(err));
}
useEffect(() => {
getProducts();
}, []);
Then I pass the state to the Provider:
<ProductContext.Provider value={{ products }}>
//children
</ProductContext.Provider>
When I log 'products' inside the getProducts() function, I get an array with a bunch of objects, but when I try to map it somewhere else in my app, I get a products.map is not a function.
import { ProductContext } from "../../../Context";
export default function ProductList() {
const { products } = useContext(ProductContext);
return (
<Container>
{products.map(product => {
//do something
})}
</Container>
);
}
.then((entry) => {
entry.items.map((item) => {
setProducts(products.push(item));
});
could be simplified to
.then((entry) => setProducts(entry.items));
Maybe that's not the cause, but could be that the multiple calls to setProducts() cause some delay (set of state is asynchronous)
Related
Hey I'm trying to fetch an API, but it dosnt returns anything.
I've checked and I cannot access my pre-built values inside my fetch.
How can I access my values inside the fetch ?
import React, { useState, useEffect } from 'react';
function App() {
const [ positionLat, setPositionLat ] = useState('') ;
const [ positionLong, setPositionLong] = useState('') ;
navigator.geolocation.getCurrentPosition(function(position) {
setPositionLat(position.coords.latitude);
setPositionLong(position.coords.longitude);
});
console.log(positionLat) // returns good result
console.log(positionLong) // returns good result
// I obviously need to call those values inside my fetch
useEffect(() => {
console.log(positionLat) // returns undefined
console.log(positionLong) // returns undefined
fetch(`https://api.openweathermap.org/data/2.5/weather?lat=${positionLat}&lon=${positionLong}&appid={api_key}b&units=metric`)
.then(res => {
return res.json();
})
.then(data => {
console.log(data)
})
}, []);
return (
<div className="App">
<p>lattitude :{positionLat}</p>
<p>longitude :{positionLong}</p>
</div>
);
}
export default App;
One option is to change your effect hook to only run the main body once the values are defined:
useEffect(() => {
if (positionLat === '' || positionLong === '') {
return;
}
// rest of function
fetch(...
}, [positionLat, positionLong]);
You also need to fix your geolocation call to occur only once, on mount.
useEffect(() => {
navigator.geolocation.getCurrentPosition(function(position) {
setPositionLat(position.coords.latitude);
setPositionLong(position.coords.longitude);
});
}, []);
Another option is to split it up into two components, and only render the child component (which does the fetching) once the geolocation call is finished, which might look cleaner.
const App = () => {
const [coords, setCoords] = useState();
useEffect(() => {
navigator.geolocation.getCurrentPosition(function (position) {
setCoords(position.coords);
});
}, []);
return coords && <Child {...coords} />;
};
const Child = ({ latitude, longitude }) => {
useEffect(() => {
fetch(`https://api.openweathermap.org/data/2.5/weather?lat=${latitude}&lon=${longitude}&appid={api_key}b&units=metric`)
.then(res => res.json())
.then(data => {
// do stuff with data
})
// .catch(handleErrors); // don't forget to catch errors
}, []);
return (
<div className="App">
<p>latitude :{latitude}</p>
<p>longitude :{longitude}</p>
</div>
);
};
I have a component the uses useEffect to fetch data from a file.
In the component i have a condiiton that only shows the content of the component if we have data.
Now how can a test the conditional part of the content i my test case?
This is what i have right now:
Component:
function MunicipalityInfo() {
const [municipalityData, setMunicipalityData] = useState({})
const fetchData = async () => {
try{
const result = await fetch(XMLFile)
const data = await result.text();
const xml = new XMLParser().parseFromString(data);
const res = XMLMapper(xml)
setMunicipalityData(res)
}catch(e){
console.log(e)
}
}
useEffect(() => {
fetchData();
}, []);
return(
<>
{ municipalityData.units &&
municipalityData.units.map((city, index) => {
return (
<Div key={index} data-testid="municipalityInfo-component" className="mt-5 p-3">
<HeaderMain data-testid="header-main">{city.City}</HeaderMain>
<HeaderSub data-testid="header-sub" className="mt-4">{city.venamn}</HeaderSub>
<BodyText data-testid="body-text">{city.Address}, {city.City}</BodyText>
<MapLink href={"#"} data-testid="map-link"><i data-testid="map-icon" className="fas fa-map-marker-alt"></i> Show on map</MapLink>
<LinkList data-testid="link-list">
<LinkListItem data-testid="list-item-first">
<Link href={city.BookingURL} data-testid="link-book-vaccination">Some text</Link>
</LinkListItem>
</LinkList>
<Calendar data={city.unit}/>
</Div>
)
})
}
<CitiesSideBar>
<Sidebar data={municipalityData.cities}/>
</CitiesSideBar>
</>
)
}
export default MunicipalityInfo;
And this is my test:
describe("<MunicipalityInfo />", () => {
it("renders without crashing", async () => {
const {queryByTestId, findByText, findByTestId} = render(<MunicipalityInfo/>, {})
expect(queryByTestId("municipalityInfo-component")).not.toBeInTheDocument();
expect(await findByTestId("municipalityInfo-component")).toBeInTheDocument(); <--- this line fails
})
})
And the error of my testcase:
TestingLibraryElementError: Unable to find an element by: [data-testid="municipalityInfo-component"]
if your problem is trying to test if something shouldn't be in the page...
use the queryBy
if you're want it to wait for something... then you want to await findBy (or wrap in a waitFor)
here's the docs: https://testing-library.com/docs/react-testing-library/cheatsheet/
I'm assuming you're mocking the fetch request so it wouldn't be the test problem...
if you're not mocking it... then you probably should mock and return either data or no data to test if it should or not render.
one way to elegantly "avoid" mocking would be by abstracting it in a custom hook:
function useCustomHook(){
const [municipalityData, setMunicipalityData] = useState({})
useEffect(() => {
fetch(XMLData)
.then((res) => res.text())
.then(async (data) => {
let xml = new XMLParser().parseFromString(data);
let result = await XMLMapper(xml)
setMunicipalityData(await result)
})
.catch((err) => console.log(err));
}, []);
return municipalityData;
}
function MunicipalityInfo({municipalityData = useCustomHook()}) { ... }
then in the test you can simply
render(<MunicipalityInfo municipalityData={'either null or some mocked data'} />)
I use AppContext, when I fetch data from server I want it to save in context but on the first render it doesn't save. If I make something to rerender state data appears in context.
Here is my code:
useEffect(() => {
fetch('https://beautiful-places.ru/api/places')
.then((response) => response.json())
.then((json) => myContext.updatePlaces(json))
.then(() => console.log('jsonData', myContext.getPlaces()))
.catch((error) => console.error(error));
}, []);
My getPlaces and updatePlaces methods:
const [allPlaces, setAllPlaces] = useState();
const getPlaces = () => {
return allPlaces;
};
const updatePlaces = (json) => {
setAllPlaces(json);
};
const placesSettings = {
getPlaces,
updatePlaces,
};
Here is how I use AppContext:
<AppContext.Provider value={placesSettings}>
<ThemeProvider>
<LoadAssets {...{ assets }}>
<SafeAreaProvider>
<AppStack.Navigator headerMode="none">
<AppStack.Screen
name="Authentication"
component={AuthenticationNavigator}
/>
<AppStack.Screen name="Home" component={HomeNavigator} />
</AppStack.Navigator>
</SafeAreaProvider>
</LoadAssets>
</ThemeProvider>
</AppContext.Provider>;
Could you explain please why my console.log('jsonData', ...) returns undefined?
I don't understand because on previous .then I saved it.
Edit to note that the code below is not copy-paste ready. It is an example of how to attack the problem – you will need to implement it properly in your project.
The 'problem' is that hooks are asynchronous – in this specific case, your useEffect further uses an asynchronous fetch too.
This means that the data that is returned by the fetch will only be available after the component has rendered, and because you're not updating state/context using a hook, the context won't update.
The way to do this requires a few changes.
In your context implementation, you should have a setter method that sets a state variable, and your getter should be that state variable.
placesContext.js
import React, { createContext, useState } from "react";
export const placesContext = createContext({
setPlaces: () => {},
places: [],
});
const { Provider } = placesContext;
export const PlacesProvider = ({ children }) => {
const [currentPlaces, setCurrentPlaces] = useState(unit);
const setPlaces = (places) => {
setCurrentPlaces(places);
};
return (
<Provider value={{ places: currentPlaces, setPlaces }}>{children}</Provider>
);
};
Wrap your App with the created Provider
App.js
import { PlacesProvider } from "../path/to/placesContext.js";
const App = () => {
// ...
return (
<PlacesProvider>
// Other providers, and your app Navigator
</PlacesProvider>
);
}
Then, you should use those variables directly from context.
MyComponent.js
import { placesContext } from "../path/to/placesContext.js";
export const MyComponent = () => {
const { currentPlaces, setPlaces } = useContext(placesContext);
const [hasLoaded, setHasLoaded] = useState(false);
useEffect(() => {
async function fetchPlacesData() {
const placesData = await fetch('https://beautiful-places.ru/api/places');
if (placesData) {
setPlaces(placesData);
} else {
// error
}
setHasLoaded(true);
}
!hasLoaded && fetchPlacesData();
}, [hasLoaded]);
return (
<div>{JSON.stringify(currentPlaces)}</div>
)
};
I'm trying to fetch data from this api https://randomuser.me/api/?results=25
with this code
function Users() {
const [Users, setUsers] = useState([])
useEffect(() => {
axios.get('https://randomuser.me/api/?results=25')
.then(Response=>{
if(Response.data){
alert("FOund")
setUsers([Response.data])
}else{
alert("not found")
}
})
}, [])
const displaylist = Users.map((User,index)=>{
return(
<h3>{User.gender}</h3>
)
})
return (
<div>
{displaylist}
</div>
)
}
export default Users
But nothing is showing up and console is giving this error:
Warning: Each child in a list should have a unique "key" prop.
Check the render method of Users. See https://reactjs.org/link/warning-keys for more information.
at h3
at Users (http://localhost:3000/static/js/main.chunk.js:627:83)
at div
at App
When you map through an array React needs a unique key, something like a User.id; in this case you could use the index as well. I changed your function a little bit like this:
function Users() {
const [Users, setUsers] = useState([]);
useEffect(() => {
axios.get("https://randomuser.me/api/?results=25").then((Response) => {
if (Response.data) {
alert("FOund");
setUsers(Response.data.results);
} else {
alert("not found");
}
}).catch(error => console.log(error));
}, []);
const displaylist = Users.map((User, index) => {
return <h3 key={index}>{User.gender}</h3>;
});
return <div>{displaylist}</div>;
}
export default Users;
The usage of Json result is wrong , `
if (Response.result) {
alert("FOund");
setUsers(Response.result);
} else {
alert("not found");
}`
// Home.js
import React, { useState, useEffect } from "react";
import Todo from "../components/Todo";
import { firestore } from "../database/firebase";
export default function Home() {
const [todos, setTodos] = useState([]);
useEffect(() => {
firestore
.collection("todos")
.get()
.then(snapshot => {
setTodos(
snapshot.docs.map(doc => {
return { id: doc.id, ...doc.data() };
})
);
});
}, []);
return (
<>
{todos.map(todo => (
<Todo
key={todo.id}
id={todo.id}
title={todo.title}
></Todo>
))}
</>
);
}
I had this simple todo app, where I update todos state when I get data back from firestore.
Above useEffect set my todos as [{id:"123", title : "work"}, ...].
But, I want to put all firestore getter in one file and simple call
useEffect(() => {
getTodos().then(data=>setTodos(data))
})
Then how should I define getTodos function? I tried below code and with many variations, like adding async and await, but none of them worked.
// firestore.js
export const getTodos = () => {
return firestore
.collection("todos")
.get()
.then(snapshot => {
snapshot.docs.map(doc => {
return { id: doc.id, ...doc.data() };
});
});
};
Utilizing async/await syntax will allow you to clean things up and give you the desired result. You'll need to change things up a bit. Try something like this:
export const getTodos = async function() {
const data = await firestore.collection("todos").get();
const dataArr = data.docs.map(doc => {
return { id: doc.id, ...doc.data() };
});
return dataArr;
};
Another solution without async/await.
// firestore.js
export const getTodos = () => (
firestore
.collection("todos")
.get()
.then((snapshot) => (
snapshot.docs.map((({ id, data }) => (
{ id, ...data() }
))
))
);