I trying map data which i get using custom hook that recives data from my "storage" (which is actually a class with array of objects and async function to get them) i reciving an error: Cannot read properties of undefined (reading 'map').
Code of hook:
const useCreations = () => {
const service = new aboutPisanka();
const [data, setData] = useState();
useEffect(() => {
console.log(123 )
service.getAllCreations().then((data) => setData(data));
}, []);
return data
};
Code of element:
const data = useCreations();
console.log(data);
const cards = data.map((data) => {
const { heading, text, id, image } = data;
return (
<div key={id}>
<img src={require(image)}/>
<h1>{heading}</h1>
<p>{text}</p>
</div>
);
});
Code of function in class where data stores:
getAllCreations = async () => {
return this._creations;
};
(Sorry for my bad english)
As I understand it happens because it maps data before it gets to state but I don't have any idea how to fix it only making high order componen which will wrap my element.
Your data is initialize as undefined. So when the render runs before you assign an array value to data, your .map function fails because you call that on an undefined. Just initialize your data as an empty array.
const useCreations = () => {
const service = new aboutPisanka();
const [data, setData] = useState([]); // NOTE: Initialization as an empty array
useEffect(() => {
console.log(123 )
service.getAllCreations().then((data) => setData(data));
}, []);
return data
};
Also, you could use es6 de structuring to improve readability (subjective).
const data = useCreations();
console.log(data);
const cards = data.map(({ heading, text, id, image }) => (
<div key={id}>
<img src={require(image)}/>
<h1>{heading}</h1>
<p>{text}</p>
</div>
));
I do not see your whole code however if you are receiving data in correct format after a while because of Api response,you can add ? in cards const cards = data?.map((data) => { to fix that error
I am playing around with an API that gets a list of Pokemon and corresponding data that looks like this.
export function SomePage() {
const [arr, setArray] = useState([]);
useEffect(() => {
fetchSomePokemon();
}, []);
function fetchSomePokemon() {
fetch('https://pokeapi.co/api/v2/pokemon?limit=5')
.then(response => response.json())
.then((pokemonList) => {
const someArray = [];
pokemonList.results.map(async (pokemon: { url: string; }) => {
someArray.push(await fetchData(pokemon))
})
setArray([...arr, someArray]);
})
}
async function fetchData(pokemon: { url: string; }) {
let url = pokemon.url
return await fetch(url).then(async res => await res.json())
}
console.log(arr);
return (
<div>
{arr[0]?.map((pokemon, index) => (
<div
key={index}
>
{pokemon.name}
</div>
))
}
</div>
);
}
The code works(kind of) however on the first render the map will display nothing even though the console.log outputs data. Only once the page has been refreshed will the correct data display. I have a feeling it's something to do with not handling promises correctly. Perhaps someone could help me out.
TIA
Expected output: Data populated on initial render(in this case, pokemon names will display)
The in-build map method on arrays in synchronous in nature. In fetchSomePokemon you need to return a promise from map callback function since you're writing async code in it.
Now items in array returned by pokemonList.results.map are promises. You need to use Promise.all on pokemonList.results.map and await it.
await Promise.all(pokemonList.results.map(async (pokemon: { url: string; }) => {
return fetchData.then(someArray.push(pokemon))
}));
On your first render, you don't have the data yet, so arr[0] doens't exist for you to .map on it, so it crashes. You need to check if the data is already there before mapping.
Using optional chaining, if there's no data it will not throw an error on your first render and it will render correctly when the data arrive and it re-renders.
...
return (
<div>
{arr[0]?.map((pokemon, index) => (
<div key={index}>{pokemon.name}</div>
))}
</div>
);
}
in
useEffect(() => { fetchSomePokemon(); }, []);
[] tells react there is no dependencies for this effect to happen,
read more here https://reactjs.org/docs/hooks-effect.html#tip-optimizing-performance-by-skipping-effects
One way to solve your issues is to await the data fetching in useEffect().
Here's a POC:
export function Page() {
const [pokemon, setPokemon] = useState([]);
// will fetch the pokemon on the first render
useEffect(() => {
async function fetchPokemon() {
// ... logic that fetches the pokemon
}
fetchPokemon();
}, []);
if (!pokemon.length) {
// you can return a spinner here
return null;
}
return (
<div>
{pokemon.map(item => {
// return an element
})}
</div>
);
}
I used the useEffect to updata the data, I can see the data is a array in DEV tool, but when I using map to traverse the data, will show be that data.map is not a function. If I use Object.keys(data).map, there are not thing was showed, but no error...
How to fix ...?
export default function List() {
let [data, setData] = useState();
useEffect(async () => {
let result = await loadList();
console.log(result.data.data);
setData({ data: result.data.data });
return;
}, data);
return (
<div>
{
(data) && data.map((item) => {
return <div key={item.id} >{item.title} author: {item.author} Creact time: { item.creactetime}</div>
})
}
</div>
)}
useEffect does not allow the use of an async function. Instead you should just use the then method when calling your loadList function and set the data in the callback.
It seems that useEffect doesn't need a dependency as it only needs to get the data on the first render, not when data changes. The latter would result in an infinite loop (Update data => do something when data updates => update data)
In setData just pass the result.data.data to update data, not within another object. Otherwise the data will not be an array, but an object.
export default function List() {
let [data, setData] = useState(null);
useEffect(() => {
loadList().then((result) => {
setData(result.data.data);
});
}, []);
return (
<div>
{data &&
data.map((item) => (
<div key={item.id}>
{item.title} author: {item.author} Creact time: {item.creactetime}
</div>
))}
</div>
);
}
useEffect can not have an async callback you need to wrap it like this
useEffect(()=>{
const fetch = async () => {
const result = await loadList();
setData(result.data.data);
}
fetch();
}, []);
data must be an array if you want to map on it, you can try to set directly data with result.data.data if it's an array, like following :
useEffect(async () => {
let result = await loadList();
setData(result.data.data);
}, []);
I have a functional component Users.js and a seperate Api.js.
Below the code which gives me the error:
// Function Users.js
useEffect(() => {
console.log(fetchUsers());
const data = fetchUsers();
setUsers(data);
//setUsers(fetchUsers());
}, []);
// Api.js
export const fetchUsers = async () => {
const data = await fetch('https://jsonplaceholder.typicode.com/users');
return await data.json();
};
I am getting the following error:
TypeError: users.map is not a function
This is because I return a promise, which I could see by evaluating the console.log(fetchUsers()).
How can I fix this issue, because it worked as long as I used the function fetchUsers inside my function and not in a seperate file...
I tried to update your code to give you two examples:
export default function App() {
const [users, setUsers] = useState();
useEffect(() => {
async function getData() {
const data = await fetchUsers();
setUsers(data);
}
getData()
}, []);
return (
<div className="App">
<h1>Hello CodeSandbox </h1>
{
(users || []).map(item => <span>{item.name}</span>)
}
</div>
);
}
export const fetchUsers = async () => {
const data = await fetch('https://jsonplaceholder.typicode.com/users');
return await data.json();
};
The second one is better than the first one, I used .then() on your function:
export default function App() {
const [users, setUsers] = useState();
useEffect(() => {
fetchUsers().then(items => setUsers(items))
}, []);
return (
<div className="App">
<h1>Hello CodeSandbox </h1>
{
(users || []).map(item => <span>{item.name}</span>)
}
</div>
);
}
In both cases I am able to retrieve the users.
I tried it in a single file but you can of course move the functions where you prefer.
I suggest you, before to render data, to check if the users array exists (users || []) or to give it a default empty array value [users, setUsers] = useState([])
You can handle the promise which is returned and then set the state.
fetchUsers().then((result) => setUsers(result));
I have just began playing around with React hooks and am wondering how an AJAX request should look?
I have tried many attempts, but am unable to get it to work, and also don't really know the best way to implement it. Below is my latest attempt:
import React, { useState, useEffect } from 'react';
const App = () => {
const URL = 'http://api.com';
const [data, setData] = useState({});
useEffect(() => {
const resp = fetch(URL).then(res => {
console.log(res)
});
});
return (
<div>
// display content here
</div>
)
}
You could create a custom hook called useFetch that will implement the useEffect hook.
If you pass an empty array as the second argument to the useEffect hook will trigger the request on componentDidMount. By passing the url in the array this will trigger this code anytime the url updates.
Here is a demo in code sandbox.
See code below.
import React, { useState, useEffect } from 'react';
const useFetch = (url) => {
const [data, setData] = useState(null);
useEffect(() => {
async function fetchData() {
const response = await fetch(url);
const json = await response.json();
setData(json);
}
fetchData();
}, [url]);
return data;
};
const App = () => {
const URL = 'http://www.example.json';
const result = useFetch(URL);
return (
<div>
{JSON.stringify(result)}
</div>
);
}
Works just fine... Here you go:
import React, { useState, useEffect } from 'react';
const useFetch = url => {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
const fetchUser = async () => {
const response = await fetch(url);
const data = await response.json();
const [user] = data.results;
setData(user);
setLoading(false);
};
useEffect(() => {
fetchUser();
}, []);
return { data, loading };
};
const App = () => {
const { data, loading } = useFetch('https://api.randomuser.me/');
return (
<div className="App">
{loading ? (
<div>Loading...</div>
) : (
<React.Fragment>
<div className="name">
{data.name.first} {data.name.last}
</div>
<img className="cropper" src={data.picture.large} alt="avatar" />
</React.Fragment>
)}
</div>
);
};
Live Demo:
Edit
Updated based on version change (thanks #mgol for bringing it to
my attention in the comments).
Great answers so far, but I'll add a custom hook for when you want to trigger a request, because you can do that too.
function useTriggerableEndpoint(fn) {
const [res, setRes] = useState({ data: null, error: null, loading: null });
const [req, setReq] = useState();
useEffect(
async () => {
if (!req) return;
try {
setRes({ data: null, error: null, loading: true });
const { data } = await axios(req);
setRes({ data, error: null, loading: false });
} catch (error) {
setRes({ data: null, error, loading: false });
}
},
[req]
);
return [res, (...args) => setReq(fn(...args))];
}
You can create a function using this hook for a specific API method like so if you wish, but be aware that this abstraction isn't strictly required and can be quite dangerous (a loose function with a hook is not a good idea in case it is used outside of the context of a React component function).
const todosApi = "https://jsonplaceholder.typicode.com/todos";
function postTodoEndpoint() {
return useTriggerableEndpoint(data => ({
url: todosApi,
method: "POST",
data
}));
}
Finally, from within your function component
const [newTodo, postNewTodo] = postTodoEndpoint();
function createTodo(title, body, userId) {
postNewTodo({
title,
body,
userId
});
}
And then just point createTodo to an onSubmit or onClick handler. newTodo will have your data, loading and error statuses. Sandbox code right here.
use-http is a little react useFetch hook used like: https://use-http.com
import useFetch from 'use-http'
function Todos() {
const [todos, setTodos] = useState([])
const { request, response } = useFetch('https://example.com')
// componentDidMount
useEffect(() => { initializeTodos() }, [])
async function initializeTodos() {
const initialTodos = await request.get('/todos')
if (response.ok) setTodos(initialTodos)
}
async function addTodo() {
const newTodo = await request.post('/todos', {
title: 'no way',
})
if (response.ok) setTodos([...todos, newTodo])
}
return (
<>
<button onClick={addTodo}>Add Todo</button>
{request.error && 'Error!'}
{request.loading && 'Loading...'}
{todos.map(todo => (
<div key={todo.id}>{todo.title}</div>
)}
</>
)
}
or, if you don't want to manage the state yourself, you can do
function Todos() {
// the dependency array at the end means `onMount` (GET by default)
const { loading, error, data } = useFetch('/todos', [])
return (
<>
{error && 'Error!'}
{loading && 'Loading...'}
{data && data.map(todo => (
<div key={todo.id}>{todo.title}</div>
)}
</>
)
}
Live Demo
I'd recommend you to use react-request-hook as it covers a lot of use cases (multiple request at same time, cancelable requests on unmounting and managed request states). It is written in typescript, so you can take advantage of this if your project uses typescript as well, and if it doesn't, depending on your IDE you might see the type hints, and the library also provides some helpers to allow you to safely type the payload that you expect as result from a request.
It's well tested (100% code coverage) and you might use it simple as that:
function UserProfile(props) {
const [user, getUser] = useResource((id) => {
url: `/user/${id}`,
method: 'GET'
})
useEffect(() => getUser(props.userId), []);
if (user.isLoading) return <Spinner />;
return (
<User
name={user.data.name}
age={user.data.age}
email={user.data.email}
>
)
}
image example
Author disclaimer: We've been using this implementation in production. There's a bunch of hooks to deal with promises but there are also edge cases not being covered or not enough test implemented. react-request-hook is battle tested even before its official release. Its main goal is to be well tested and safe to use as we're dealing with one of the most critical aspects of our apps.
Traditionally, you would write the Ajax call in the componentDidMount lifecycle of class components and use setState to display the returned data when the request has returned.
With hooks, you would use useEffect and passing in an empty array as the second argument to make the callback run once on mount of the component.
Here's an example which fetches a random user profile from an API and renders the name.
function AjaxExample() {
const [user, setUser] = React.useState(null);
React.useEffect(() => {
fetch('https://randomuser.me/api/')
.then(results => results.json())
.then(data => {
setUser(data.results[0]);
});
}, []); // Pass empty array to only run once on mount.
return <div>
{user ? user.name.first : 'Loading...'}
</div>;
}
ReactDOM.render(<AjaxExample/>, document.getElementById('app'));
<script src="https://unpkg.com/react#16.7.0-alpha.0/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom#16.7.0-alpha.0/umd/react-dom.development.js"></script>
<div id="app"></div>
I find many wrong usages of useEffect in the answers above.
An async function shouldn't be passed into useEffect.
Let's see the signature of useEffect:
useEffect(didUpdate, inputs);
You can do side effects in didUpdate function, and return a dispose function. The dispose function is very important, you can use that function to cancel a request, clear a timer etc.
Any async function will return a promise, but not a function, so the dispose function actually takes no effects.
So pass in an async function absolutely can handle your side effects, but is an anti-pattern of Hooks API.
Here's something which I think will work:
import React, { useState, useEffect } from 'react';
const App = () => {
const URL = 'http://api.com';
const [data, setData] = useState({})
useEffect(function () {
const getData = async () => {
const resp = await fetch(URL);
const data = await resp.json();
setData(data);
}
getData();
}, []);
return (
<div>
{ data.something ? data.something : 'still loading' }
</div>
)
}
There are couple of important bits:
The function that you pass to useEffect acts as a componentDidMount which means that it may be executed many times. That's why we are adding an empty array as a second argument, which means "This effect has no dependencies, so run it only once".
Your App component still renders something even tho the data is not here yet. So you have to handle the case where the data is not loaded but the component is rendered. There's no change in that by the way. We are doing that even now.