I have following component where I want to display data from an API, ShopScreen.js. I retrieve data with useEffect hook from API in service folder, and everything is ok, the json data is being loaded into data variable from useState hook, when I console.log it.
I have problem to the display data from this variable with map method. I get the error: Cannot read property 'map' of undefined. Can spot somebody where the problem is?
ShopScreen.js:
import React, { useState, useEffect } from 'react';
import { View, Text, StyleSheet, Button } from 'react-native';
import { fetchShops } from '../services/fetchShops';
const ShopsScreen = props => {
const [data, setShops] = useState({});
useEffect(() => {
fetchShops()
.then(response => response.json())
.then(data => setShops(data));
}, []);
return(
<View>
<Text>The Shops Screen!</Text>
{data.result.map(shop => {return (<Text>{shop.address}</Text>)})}
</View>
);
};
export default ShopsScreen;
My service for fetching data is fetchShops.js
export const fetchShops = () => {
const URL = `https://vilvumbiyl.execute-api.eu-west-1.amazonaws.com/Dev/store/MEBD/list`;
return fetch(URL)
}
useEffect without any params is equal to componentDidMount and for this reason, is called after the render.
So, the first time your jsx code is called, data.result.map is undefined and only after the re-render, do to the response of fetchShops(), it has a value.
You simply need to check the value like this:
data.result && data.result.map()
You can try:
const [data, setShops] = useState({result : {}});
or test data.result before use map on it.
ShopsScreen returns your view(JSX) before you get answer from your rest API. The result is null. You get the exception.
Related
I have a sidebar which sets a category Id on click and shows product information based on this Id.
In order to display each product's details, I make an api post call using axios in a useEffect() hook and setData with useState hook.
However, when I try to console.log the data, I get unreliable data back, that is, some undefined and some data.
import { useState, useEffect, useCallback, useMemo } from "react";
import axios from "axios";
import ProductCard from "../Product-cards/_medium-product-card";
const ProductDisplay = ({ subCategorySkus, categoryId, allSkus }) => {
const [data, setData] = useState();
const [isLoading, setIsLoading] = useState();
const apiRequest = { product_skus: allSkus };
const productData = useProductData(apiRequest);
useEffect(() => {
const fetchData = async () => {
try {
const response = await axios.post("/products", apiRequest);
setData(response.data);
setIsLoading(false);
} catch (err) {
console.log(err.message);
}
};
fetchData();
}, [categoryId]);
console.log(data);
return isLoading ? (
" data is loading ..."
) : (
<div className="product-display">
<h3>Child component Id: {categoryId}</h3>
<ProductCard categoryId={categoryId} />
</div>
);
};
export default ProductDisplay;
You can see here what I get in the console:
.
I have tried adding an if condition, but I am not sure that addresses the issue. I have tried with different dependencies in the useEffect. But I feel there is something I am not understanding.
The api is sending the information correctly, but I get several responses in the console.log and some of them come in as undefined.
This means that I can do a data.map because it crashes the component.
Any help?
UPDATE:
#moonwave99 provided the answer (see below). For the benefit of others who may browse through this question and to clarify what the solution entails, I am including an image below, showing where I changed my code. I think that most importantly, I had not initialised my useState() to an empty array, like this: useState([]).
Any further clarification of the issues here by anyone who knows more, is welcomed. As this is only a quick fix without enough context to understand the logic.
I have a NextJS application where I am building a search results page based on a query parameter being passed. I am using the next/router to pick up the query string. The first time the page loads after compilation the querystring gets picked up and the call to the API is successful and returns results. Every subsequent try, whether on a F5 refresh, reloading the page via the reload button, or accessing the page again the call to the API gives no results because the querystring is undefined.
the URL:
http://localhost:3000/search?search=blah
import { useRouter } from "next/router";
import React, { useEffect, useState } from 'react';
import axios from 'axios'
export default function SearchResults1(props) {
const { query } = useRouter();
const [searchResults, setSearchResults] = useState({})
const [ getQuery, setQuery] = useState({})
useEffect(() => {
function fetchData(){
setQuery(query.search)
console.log(getQuery)
if (getQuery) {
axios.get(`http://localhost:3001/search/${getQuery}`)
.then((response) => {
setSearchResults(response.data)
})
.catch((error) => {
console.warn(error)
})
}
}
fetchData()
},[getQuery, setQuery])
I tried adding the getQuery and setQuery in the useEffect hook so it would be triggered when there are changes, but i still get undefined whenever i load the page a second time.
I also tried removing the getQuery and setQuery and tried without using state, but I still get the same result if I call the query.search directly - the first time it works, subsequently it does not.
I did notice that if I called {query.search} in the return HTML it always renders - so I am thinking it has something to do with perhaps the API call happening and not waiting for the query to be populated.
return (
<div>{query.search}</div> <!--this always works and shows the correct value-->
)
Any thoughts or suggestions on how I can achieve consistent results and always return the query parameters so I can make a good API call? I'm fairly confident I am missing something obvious.
Instead of parsing it directly with the useRouter(), try to get the router ready state and access it once it is ready in the useEffect.
import { useRouter } from "next/router";
import React, { useEffect, useState } from 'react';
import axios from 'axios'
export default function SearchResults1(props) {
const router = useRouter();
const [searchResults, setSearchResults] = useState({})
//const query = router.query; <-- or use this. The point is to access router.isReady in useEffect
useEffect(() => {
if (!router.isReady) return; // <-- only use the query when it is ready.
function fetchData(){
axios.get(`http://localhost:3001/search/${router.query.search}`)
.then((response) => {
setSearchResults(response.data)
})
.catch((error) => {
console.warn(error)
})
}
fetchData()
},[router.isReady]) // <-- use this router.isReady
According to the Next.js, It will be an empty object during prerendering if the page doesn't have data fetching requirements.
https://nextjs.org/docs/api-reference/next/router
This way, we can make sure the router information is ready for use when you access it.
I am making an weather app with API, I am successfully receiving the data with API in the function but I am not able to take it out of the function
here is the code
import React, {useState} from 'react'
import {Text, View} from 'react-native'
const axios = require('axios');
let HomeScreen =() => {
let key = "XXXX"
axios.get(`https://api.weatherapi.com/v1/current.json?key=${key}&q=London&aqi=no`)
.then(function (response) {
// handle success
console.log(response)
})
return(
<Text>This is {response}</Text>
)
}
export default HomeScreen
If you want to simply return data from the API use a normal JS function, not a React component.
function getData() {
return axios(`https://api.weatherapi.com/v1/current.json?key=${key}&q=London&aqi=no`)
}
It will return a promise which you can then use somewhere else.
async main() {
const data = await getData();
}
If you want your component to render data retrieved from the API you need to do things differently.
Use useEffect to run once when the component is mounted, and use useState to store the data once it's been retrieved. The state will then inform the JSX how to render.
Note that the response from the API is an object with location and current properties. You can't just add that to the JSX because React won't accept it. So, depending on what value you need from the data, you need to target it specifically.
Here's an example that returns the text value from the condition object of the current object: "It is Sunny".
const { useEffect, useState } = React;
function Example() {
// Initialise your state with an empty object
const [data, setData] = useState({});
// Call useEffect with an empty dependency array
// so that only runs once when the component is mounted
useEffect(() => {
// Retrieve the data and set the state with it
async function getData() {
const key = 'XXX';
const data = await axios(`https://api.weatherapi.com/v1/current.json?key=${key}&q=London&aqi=no`)
setData(data.data);
}
getData();
}, []);
// If there is no current property provide a message
if (!data.current) return <div>No data</div>;
// Otherwise return the current condition text
return (
<p>It is {data.current.condition.text}</p>
);
}
I am trying out react-table and am very new to react in general. Right now, in the code below, a local JSON(MOCK_DATA.json) is passed into the const data. I want to replace the local JSON with data fetched from an URL. How can I do that? Thank you :)
import React, { useMemo } from 'react'
import { useTable, useFilters, useGlobalFilter, usePagination, useSortBy } from 'react-table'
import MOCK_DATA from './MOCK_DATA.json'
import { COLUMNS } from './columns'
import { GlobalFilter } from './GlobalFilter'
import { ColumnFilter } from './ColumnFilter'
export const FilteringTable = () => {
const columns = useMemo(() => COLUMNS, [])
const data = useMemo(() => MOCK_DATA, [])
...
}
you can use http libraries/js API's and set the response in a react state. Simplest is the fetch
const [data, setData] = React.useState();
fetch('http://example.com/movies.json')
.then(response => response.json())
.then(data => setData(data));
Alternatively, you can use axios which is also great, and my personal favorite.
Note the above example works only for functional components. If you plan to use class components use this.setState() API. When do you want to trigger the API call to your backend server depends on your use case. Most common usecase would be to fetch data on page load. So your app would look like.
function App(){
const [data, setData] = React.useState({}); //setting it to empty map as default
React.useEffect(() => {
fetch('http://your-api/')
.then(response => setData(response.json())) //setting the response in our data object
.then(data => console.log(data));
},[]) //called when page is loaded for first time
return (<Table data = {data}/>) // when data is fetched, react re renders the table component since value of data changes.
}
You can fetch data with the Fetch api and return the data to state with the useState hook.
Maybe this links give you some insight for your further research on this topic.
https://www.youtube.com/watch?v=T3Px88x_PsA&t=1s (youtube),
https://blog.logrocket.com/patterns-for-data-fetching-in-react-981ced7e5c56/ (some Article)
Widgets.js
import React, {useContext} from 'react';
import { DataContext } from '../contexts/DataContext';
const Widgets = () => {
const {updates} = useContext(DataContext);
console.log(updates);
return (
<div className="MainWidget">
<ul>
{updates.map(update => {
return (
<div>
<li>{update.current.condition}</li>
<p>{update.current.temp_c}</p>
</div>
);
})}
</ul>
</div>
);
}
export default Widgets;
I'm mapping data from an API which is returning an error: TypeError: updates.map is not a function but its actually returning the data in the console using the console.log() function.
DataContext.js
:I'm using axios to fetch data from weatherapi.com and setting the state 'updates' with 'setUpdates' function.
import React, {useState, useEffect, createContext} from 'react';
import axios from 'axios';
export const DataContext = createContext();
const DataContextProvider = (props) => {
const [updates, setUpdates] = useState({});
const url = "https://api.weatherapi.com/v1/current.json?key=931701d0de0c4d05b0b34936203011&q=London";
useEffect(() => {
axios.get(url)
.then(res => {
console.log(res.json())
setUpdates(res.data)
})
.catch(err => {
console.log(err)
})
})
return (
<div>
<DataContext.Provider value={{updates}}>
{props.children}
</DataContext.Provider>
</div>
)
}
export default DataContextProvider;
You're requesting data for only one location and the API returns you an object, not an array. You can either change your component to expect an object, or update the context provider, so it provides an array. That will looks something like:
const [updates, setUpdates] = useState([]);//note you want an array ([]) not an object ({})
...
let data = res.data
if(!data instanceof Array) data = [data] //put an object into array if needed
setUpdates(data)
UPDATE
In you repo make following changes:
In the DataContext you need to parse JSON response into an object, so replace
axios.get(url)
.then(res => {
setUpdates(res.data.current)
})
with
axios.get(url)
.then(res => res.json())
.then(res => {
setUpdates(res.current)
})
This will mean you already have current in the provider and in the component you can access its fields directly, so you'll need to replace
<p>{updates.current}</p>
with something like
<p>{updates.temp_c}</p>
<p>{updates.humidity}</p>
Not sure, but either your updates is an object, which means you can't map, or while your API is being called, that is undefined, which is why it crashes.
If your update is an object then you can do: map function for objects (instead of arrays)
If its undefined while calling API, then you can do a simple check to see if its undefined and only map when it's not.