Can I use two useEffect and have map inside a map - javascript

I am new to React and would like some help with the following problem. I current have this code.
import React, { useState, useEffect } from "react";
function FetchData() {
const [repos, setRepos] = useState([]);
const [isLoading, setIsLoading] = useState(true);
useEffect(() => {
fetch("https://api.github.com/orgs/org_name/repos")
.then((res) => res.json())
.then((data) => {
setRepos(data);
})
.then(() => {
setIsLoading(false);
})
.catch((err) => console.log(err));
}, []);
return (
<div>
{repos.map((repo) => (
<div key={repo.id}>
<div>
<h2>Name: {repo.name}</h2>
<p>Top 5 Contributors</p>
))}
My above codes work fine, but my problem now is that I would like to add the top 5 contributors to the repository and to access that I have to go to https://api.github.com/repos/org_name/{repos}/contributors, and to get to that, I first have to use repo.contributor_url Should I use another useEffect and map to show the top 5 contributors?
Edit
Basically I want to do something like this.
useEffect(() => {
fetch(`${repos.contributors_url}`)
.then((res) => res.json())
.then((data) => {
setContributors(data);
console.log(data);
})
.catch((err) => console.log(err));
}, []);
...
<p> Top 5 Contributors: </p>
<ul>
{contributors.map((c, i) => {
<li key={i}>{c.name}</li>
)}
</ul>

Since you are new to React. React used to have class based components to handle state and those class based components had special functions called- Life-Cycle-Methods. But from React 16.8 onwards React Community came up with React-Hooks and functional components can now be used to handle state and useState() and useEffect() are examples of Hooks.
Now useEffect() alone is used to do perform life-cycle method's work.
The way you have used useEffect() in your code is simulating componentDidMount() as you have kept the 2nd argument as an empty array []
We can use other life-cycle methods like componentDidUpdate() and componetnWillUnmount() using useEffect() Hook itself.
Then based on your requirement you can use useEffect() Hook as many times as required by your Component.
Coming to Updated part of your question now:
So, you basically need to do promise chaining. We know that fetch() is promise based,so when one asynchronous call is resolved and we get the first data, within your useEffect() hook only, you need to make another asynchronous request using the second url-end point to get the respective data.
Here is the updated code now: Try this
import React, { useState, useEffect } from 'react';
function FetchData() {
const [repos, setRepos] = useState([]);
const [isLoading, setIsLoading] = useState(true);
const [contributors, setContributors] = useState([]);
const [isContributorLoading, setIsContributorLoading] = useState(true);
useEffect(() => {
fetch('https://api.github.com/orgs/{org}/repos')
.then((res) => res.json())
.then((data) => {
setRepos(data); // Data 1(repos) is received
// Now We make another API call to get Data 2 (contributors)
return fetch('https://api.github.com/repos/{org}/{repos}/contributors');
})
.then((res) => res.json()) // Chaining promise,handling 2nd Fetch request
.then((data2) => {
console.log(data2);
setContributors(data2);
})
.then(() => {
setIsLoading(false);
})
.catch((err) => console.log(err));
}, []);
return (
<div>
{ repos.length && repos.map((repo) => (
<div key={repo.id}>
<div>
<h2>Name: {repo.name}</h2>
</div>
</div>
))}
<p> Top 5 Contributors: </p>
<ul>
{contributors.length && contributors.map((c, i) => {
return <li key={i}>{c.name}</li>
)}
</ul>
</div>
);
}
So, basically you need to learn a bit more about how to use Hooks especially useEffect(), for now. Do some googling stuff, It would not be good if I tell you everything now. Give it a shot then.

You can directly call apis inside one useEffect.
import React, { useState, useEffect } from "react";
function App() {
const [repos, setRepos] = useState([]);
const [contributor, setContributor] = useState([]);
const [isLoading, setIsLoading] = useState(true);
useEffect(() => {
async function caller() {
try {
setIsLoading(true);
const response = await fetch(
"https://api.github.com/orgs/octokit/repos"
);
const result = await response.json();
const contri = [];
console.log(result);
result.forEach((item) => {
contri.push(fetch(`${item.contributors_url}`));
});
Promise.all(contri)
.then((contributorResults) => contributorResults)
.then((responses) => {
console.log(responses);
return Promise.all(responses.map((r) => r.json()));
})
.then((cont) => {
setContributor([...cont])
});
setRepos(result);
} catch (err) {
console.log(err);
} finally {
setIsLoading(false);
}
}
caller();
}, []);
return (
<div>
{repos.map((repo,index) => (
<div key={repo.id}>
<h2> Name: {repo.name} </h2>
{ contributor[`${index}`] && contributor[`${index}`].slice(0,5).map(item => {
return <div key={item.id}>
<div>{item.login}</div>
</div>
})}
</div>
))}
{isLoading && <div>...loading</div>}
</div>
);
}
export default App;

Related

React returning empty state

I understand that useEffect() hook is supposed to fire on the first render. In my code, I am getting errors for not being able to use my States properties. This is because the State doesn't have the API data before the DOM renders. I guess I am just not understanding if I am missing something, but the console log shows that the questions array is empty twice before actually getting any data. If I am not trying to access the data anywhere, no data is thrown, but as soon as I try to use it, the code says that the data doesn't exist. I tried to use async but get the same error.
export default function Quiz(props) {
const [questions, setQuestions] = React.useState([])
React.useEffect(() => {
fetch("https://opentdb.com/api.php?amount=10")
.then(res => res.json())
.then(data => setQuestions(data.results))
}, [])
console.log(questions)
return (
<div>
<div className="quiz--question">
<h1>{questions.category}</h1>
</div>
</div>
)
}
questions is an array , so you need to iterate that to display the value. You can use map
import * as React from 'react';
import './style.css';
export default function App() {
const [questions, setQuestions] = React.useState([]);
React.useEffect(() => {
fetch('https://opentdb.com/api.php?amount=10')
.then((res) => res.json())
.then((data) => {
setQuestions(data.results);
console.log(questions);
});
}, []);
return (
<div>
{questions.map((elem) => (
<div className="quiz--question">
<h1>{elem.category}</h1>
</div>
))}
</div>
);
}
Demo Here

React Map function not rendering on browser, but it is console.logging

I'm working on a React homework assignment, working with the pokemon API. In this particular component, I'm accessing the API to return a list of all the pokemon names and render them to the browser.
I've called the API and mapped it out, and it seems to work when I console.log the names, but I'm not sure what I'm doing wrong that it is not rendering onto the actual browser, and could definitely use some help. Code:
import React, { useEffect, useState } from "react";
function PokedexHome(){
const [pokedexList, setPokedexList] = useState(undefined);
const [isLoading, setIsLoading] = useState(true);
const [hasError, setHasError] = useState(false);
useEffect(() => {
fetch(`https://pokeapi.co/api/v2/pokemon/?limit=100&offset=0`)
.then(response => response.json())
.then(data => {
setPokedexList(data)
setIsLoading(false);
},
error => {
setHasError(true)
setIsLoading(false)
}
);
},[]);
if(isLoading){
return <p>Loading...</p>
}
if(hasError){
return <p>An error has occurred, please try again later</p>
}
pokedexList.results.map((pokemon) => {
console.log(pokemon.name)
return <div className="list-container">
<p>{pokemon.name}</p>
</div>
})
};
export default PokedexHome
if you have a list then your PokedexHome returns void :)
so, first of all, you are missing a return before the map.
second, (if nothing changed lately) you can't return an array of components, you need to return a single component, which can be a Fragment (a React component without UI representation, created for this purpose)
return (
<>
{
pokedexList.results.map((pokemon) => {
console.log(pokemon.name)
return <div className="list-container">
<p>{pokemon.name}</p>
</div>
})
}
</>
)
You miss the return keyword in front if pokedexList. It should be like this. And also add () on your return
return pokedexList.results.map((pokemon) => {
return (
<div className="list-container">
<p>{pokemon.name}</p>
</div>
)
})
};
Your component need to return a JSX, so add return and wrap your list with <></>.
import React, { useEffect, useState } from 'react';
function PokedexHome() {
const [pokedexList, setPokedexList] = useState(undefined);
const [isLoading, setIsLoading] = useState(true);
const [hasError, setHasError] = useState(false);
useEffect(() => {
fetch(`https://pokeapi.co/api/v2/pokemon/?limit=100&offset=0`)
.then((response) => response.json())
.then(
(data) => {
setPokedexList(data);
setIsLoading(false);
},
(error) => {
setHasError(true);
setIsLoading(false);
},
);
}, []);
if (isLoading) {
return <p>Loading...</p>;
}
if (hasError) {
return <p>An error has occurred, please try again later</p>;
}
return (
<>
{pokedexList.results.map((pokemon) => {
console.log(pokemon.name);
return (
<div className="list-container">
<p>{pokemon.name}</p>
</div>
);
})}
</>
);
}
The most probable issue I can think of here is, you are not wrapping the whole JSX in () round braces. It specifies that you want to return something. With main function return statement of course
Example code
Try wrapping it like this.
return pokedexList.results.map((pokemon) => (
<div className="list-container">
<p>{pokemon.name}</p>
</div>
))

After fetching the api, I see error "TypeError: Cannot read property 'question' of undefined" in ReactJS

I have a parent component called App. I want to send the data that i took from the api(it includes random questions and answers) to child component.In child componenet(QuestionGrid), when i want to take the first question inside the array that come from api, I face the error. i want to use console.log(items[0].question) to see the first question but it fires error.But when I use console.log(items) it allow me to see them. I also aware of taking the data after they loaded.I used also useEffect. Here is my parent component
import './App.css';
import React, { useState,useEffect} from 'react';
import QuestionGrid from './components/QuestionGrid';
function App() {
const [error, setError] = useState(null);
const [isLoaded, setIsLoaded] = useState(false);
const [items, setItems] = useState([]);
useEffect(() => {
fetch("https://opentdb.com/api.php?amount=40&category=9&difficulty=medium&type=multiple")
.then(res => res.json())
.then(
(result) => {
setIsLoaded(true);
setItems(result.results);
},
(error) => {
setIsLoaded(true);
setError(error);
}
)
}, [])
return (
<div className="App">
<QuestionGrid isLoaded={isLoaded} items={items}/>
</div>
);
}
export default App;
Here is my child component
import React, { useState, useEffect } from 'react';
export default function QuestionGrid({ isLoaded, items }) {
if(isLoaded){
console.log(items[0].question)
}
return isLoaded ?
<section className="cards">
</section> : <h1>Loading</h1>;
}
It will fire and error because the initial state of items is an empty array. And there is no indexes and object on the items state on the first render.
you can check if the the items is loaded by only checking its length.
return items.length > 0 ? <h1>your jsx component</h1> : <span>Loading...</span>
First thing, you should use the .catch() in fetch like:
fetch("https://opentdb.com/api.php?amount=40&category=9&difficulty=medium&type=multiple")
.then(res => res.json())
.then((result) => {
setIsLoaded(true);
setItems(result.results);
})
.catch(error => {
setIsLoaded(true);
setError(error);
)}
)
You are checking for isLoaded but not if there is any data. You are setting isLoaded(true) in both your result and also in error (which is not bad).
The error is caused because there is nothing in items[0]. To check for this you can call console.log(items?.[0].question) or you can make the check in your if-condition if(items.length > 0)

How to update a function with useEffect (like componentDidUpdate) and use .map function to display from JSON file ReactJS

I've been trying to update a function once data has been recieved and set to a state (using useState). After that the function will use the .map function display the data into a template.
However i am getting two errors, one is 'projects.map is not a function' (btw projects is my state name, where data is stored) and inside the useEffect function which updates when projects is changed 'Expected an assignment or function call and instead saw an expression'
import React, { useState, useEffect } from 'react';
import ProjectSummary from './projectSummary';
function ProjectList() {
// setting my state
const [projects, setProjects] = useState([])
// getting the data from some dummy online data when the app starts
useEffect(() => {
fetch('https://jsonplaceholder.typicode.com/posts')
.then(response => response.json())
.then(data => setProjects({ data }))
}, []);
// makeing a function call postList, which stores a ternery operator
const postList = () => {
// The ternery operator asks if there is anything inside the porjects state
projects.length ? (
// If there is something in the state, it will map out the JSON array in the 'projectSummary template
projects.map(projects => {
return(
<div >
<ProjectSummary key={projects.id} title={projects.title} author={projects.userId} date='30 september, 2019' content={projects.body}/>
</div>
)
})
) : (
// If there isnt anything in the state is prints out 'Loading Data'
<h1>Loading Data</h1>
);
}
// useEffect updates when the 'projects' stae is updated (like componentDidUpdate, and runs the function again
useEffect(() => {
postList()
}, [projects]);
return(
<div className="ProjectList">
// The component should output the postList function, which should map out the array, in the template
{ postList }
</div>
)
}
export default ProjectList
You need to make some corrections to your component
import React, { useState, useEffect } from 'react';
import ProjectSummary from './projectSummary';
function ProjectList() {
const [projects, setProjects] = useState([])
useEffect(() => {
fetch('https://jsonplaceholder.typicode.com/posts')
.then(response => response.json())
.then(data => setProjects(data))
}, []);
return(
<div className="ProjectList">
{
projects.length ?
projects.map(projects => (
<div>
<ProjectSummary key={projects.id} title={projects.title} author={projects.userId} date='30 september, 2019' content={projects.body} />
</div>
))
:
<h1>Loading Data</h1>
}
</div>
)
}
export default ProjectList;
You don't need the postList function and the second useEffect.
You may want to add additional checks to determine when the posts are loading and when they're empty after loading is done, so you don't just get a loading message
try this
const [projects, setProjects] = useState([])
and
.then(data => setProjects(data))
assuming that the data is an array
First you have to set the initial projects to an empty array like this:
const [projects, setProjects] = useState([])
, because currently projects is an empty string, which does not have the map function.
For the fetch, you should write it like this:
useEffect(async () => {
const data = await fetch('https://jsonplaceholder.typicode.com/posts')
.then(response => response.json())
setProjects(data);
}, []);
Hope this helps.
Array.prototype.map() is for Array.
const [projects, setProjects] = useState('');
projects not an array.

React Hooks - Making an Ajax request

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.

Categories

Resources