Today I made a useFetch hook to get all the data from a certain category. As you can see on the image it's possible to see all the data in JSON format. Also you can see that it is in an array of objects. I was wondering how I can show this data in normal format like on the page. Most of the time I'm getting the error of data.name NULL. But as you can see the data is fetched correctly in JSON format on the image. I just don't understand how to show all this data normally. Any suggestions?
enter image description here
enter image description here
import React from "react";
import "../Style/menu.css";
import { useParams, withRouter } from "react-router-dom";
import useFetch from "../ApiService/useFetch";
import { render } from "#testing-library/react";
const Product = () => {
const { id } = useParams();
const { data, error, isPending } = useFetch("http://localhost:8080/products/category/" + id);
return (
<p>{JSON.stringify(data)}</p>
)
}
export default Product;
import { useState, useEffect } from "react";
const useFetch = (url) => {
const [data, setData] = useState(null);
const [isLoading, setIsPending] = useState(true);
const [error, setError ] = useState(null);
useEffect(() => {
fetch(url) //custom url so you can reuse it
.then(res => {
if(!res.ok) {
throw Error('could not fetch data');
}
return res.json();
})
.then(data => {
setData(data);
setIsPending(false)
setError(null)
})
.catch(err => {
setError(null)
setIsPending(false)
})
}, [url]);
return {data, isLoading, error} //use properties with custom hook
}
export default useFetch;
This might be helpful for you
...
const Product = () => {
const { id } = useParams();
const { data, error, isPending } = useFetch("http://localhost:8080/products/category/" + id);
return (
{ data && data.length &&
data.map((row) =>{
<p>row.name</p>
})
}
)
}
...
your useFetch is async, as I can see isPending variable, why don't you use this ?
const { id } = useParams();
const { data, error, isPending } = useFetch("http://localhost:8080/products/category/" + id);
return (
<p>{isPending ? null : JSON.stringify(data)}</p>
)
Related
In a react app, when creating a component, I use useEffect to handle a HTTP request via a custom hook (which fetch via a useCallback). Then, to parse the parameters for the request, I have a layer for services which return the expected values.
As a result, this workflow keeps re-rendering in a loop and the apps gets stacked.
Component:
import React, { Fragment, useContext, useEffect, useState } from 'react';
import { NavLink } from 'react-router-dom';
import { useParams } from 'react-router-dom';
import { useTranslation } from 'react-i18next';
import { getProject } from '../../../services/Project.service';
import { AuthContext } from '../../../shared/context/auth.context';
import { NavOptions } from '../../../shared/constants/NavOptions';
import { useHttpClient } from '../../../shared/hooks/http.hook';
import SideNavigation from '../../../shared/components/Navigation/SideNavigation/SideNavigation';
import NavLinks from '../../../shared/components/Navigation/NavLinks/NavLinks';
import './Dashboard.css';
const Dashboard = (props) => {
console.log('Dashboard...');
const { isLoading, error, sendRequest, clearError } = useHttpClient();
const [project, setProject] = useState();
const auth = useContext(AuthContext);
const projectId = useParams().projectId;
const getProject = async () => {
console.log('getProject...');
console.log('auth', auth.token);
const response = await sendRequest(getProject(projectId, auth.token));
if (response.status === 201) {
const responseData = await response.json();
console.log('project:', responseData);
setProject(responseData);
} else {
console.log('getting buildings failed!');
const error = await response.json();
}
};
useEffect(() => {
projectId && getProject();
}, []);
const { t, i18n } = useTranslation();
let content = (
<div className="bim-y-dashboard">
.
.
.
</div>
);
return (
<Fragment>
<SideNavigation>
<NavLinks options={NavOptions.PROJECT} projectId />
</SideNavigation>
<MainContent>{content}</MainContent>
</Fragment>
);
};
export default Dashboard;
Custom hook:
export const useHttpClient = () => {
const auth = useContext(AuthContext);
const [isLoading, setIsLoading] = useState(false);
const [error, setError] = useState();
const activeHttpRequests = useRef([]);
const sendRequest = useCallback(
async (url, method = 'GET', body = null, headers = {}) => {
console.log('sendRequest...');
console.log('url', url);
console.log('method', method);
console.log('body', body);
console.log('headers', headers);
},[]);
const clearError = () => {
setError(null);
};
useEffect(() => {
return () => {
activeHttpRequests.current.forEach(abortCtrl => abortCtrl.abort());
};
}, []);
return { isLoading, error, sendRequest, clearError };
};
Service:
export const getProject = (projectId, token) => {
console.log('getProject...');
return (`/projects/id/${projectId}`, 'GET', null, {
'Content-Type': 'application/json',
Authorization: 'Bearer ' + token,
});
}
What am I missing to avoid this constant re-rendering?
Thanks in advance.
I have the feeling your issue is in code you have not shared, as the above looks legit.
Things you can try:
Comment out const auth = useContext(AuthContext); in your custom hook to make sure the culprit is not in your context
Make sure const response = await sendRequest(..) returns what you are expecting
Add a new hook, something like const [data, setData] = useState(null), then after you get a response, set it in your hook setData(response)
Finally, in your useEffect, update your condition as projectId && !data && getProject();
That will ensure your re rendering issue is neither on your auth context nor in your fetch request.
Hope that helps; if not, please update the question with the full code of your component and I'll take a second look :)
RESOLVED:
Found this post: Link
It explains that because of sendRequest method, it keeps re-rendering.
I am relatively new to react hooks and I am trying to create this custom hook to handle CRUD operations for my API.
This is the hook file:
import React, { useState, useEffect } from "react";
const useApi = (url, headers = { method: "GET" }, payload = null) => {
const [isLoading, setIsLoading] = useState(true);
const [apiData, setApiData] = useState(null);
const [serverError, setServerError] = useState(null);
const [api, setApi] = useState({});
const list = async () => {
try {
const resp = await fetch(url);
const data = await resp?.json();
setApiData(data);
setIsLoading(false);
} catch (error) {
setServerError(error);
} finally {
setIsLoading(false);
}
};
const create = async () => {
try {
const resp = await fetch(url, (headers = { method: "POST" }), payload);
const data = await resp?.json();
setApiData(data);
setIsLoading(false);
} catch (error) {
setServerError(error);
} finally {
setIsLoading(false);
}
};
setApi({
...api,
list: list,
create: create
});
return { isLoading, apiData, serverError, api };
};
export default useApi;
However, when I call api.list() in my main component inside a useEffect() hook, I get an infinite loop.
Sample component call:
import { useEffect } from "react";
import useApi from "./useApi";
export default function App() {
const {
isLoading: loading,
apiData: students,
serverError: error,
api
} = useApi("https://59f0f160ce72350012bec011.mockapi.io/students");
console.log(loading, students, error, api);
useEffect(() => {
api.list();
}, [api]);
return (
<div className="App">
<h1>list</h1>
{loading ? "loading" : students.map((x) => x.name)}
</div>
);
}
Here's the sandbox for it:
https://codesandbox.io/s/cocky-chebyshev-d9q89?file=/src/App.js:0-492
Can anyone help me understand the issue?
Thank you in advance!
This is what is causing the infinite loop:
setApi({
...api,
list: list,
create: create
});
You are not supposed to call setState() during a render.
In your case, you don't need to useState for the api object, you can just return it on every render:
return {
isLoading,
apiData,
serverError,
api: { list, create }
};
Here is a link to the fixed sandbox
Also, another warning: this code will repeatedly call api.list().
useEffect(() => {
api.list();
}, [api]);
Since api changes on every render, it will repeatedly call api.list().
This is the object that changes on every render:
return { isLoading, apiData, serverError, api };
You can ensure that you only call api.list() one time by using a ref.
import { useRef } from 'react'
// In the component
const gotRef = useRef(false)
useEffect(() => {
if (!gotRef.current) {
api.list();
gotRef.current = true
}
}, [api]);
So basically I'm learning how do api calls and I'm messing around with the the open weather api, trying to display the name and and temperature of a few different cities. The problem is the response is an object of arrays and trying to put all the names of the cities into to state so i can display them. Whats the best way to get all the names into state?
import "./App.css";
import React, { useEffect, useState } from "react";
import axios from "axios";
function App() {
const [cityName, setCityName] = useState("");
const [temp, setTemp] = useState("");
const [description, setDescription] = useState("");
const fetchData = () => {
//api variables (normily stored somewhere safer)
const lat = "35.320696399999996";
const lon = "-75.8232391";
const key = "115475ac7a8dda2a7e7ec0b27b93ce35";
const cnt = "5";
const url = `https://api.openweathermap.org/data/2.5/find?lat=${lat}&lon=${lon}&cnt=${cnt}&appid=${key}`;
axios
.get(url)
.then((res) => {
console.log(res.data.list);
})
.catch((err) => {
console.log(err);
});
};
useEffect(() => {
fetchData();
}, []);
return <div className="App"></div>;
}
export default App;
You can map the response list to a new array with just the properties you want. Update your component to have a single "data" state array to hold the mapped response values.
const [data, setData] = useState([]);
const fetchData = () => {
...
axios
.get(url)
.then((res) => {
console.log(res.data.list);
// destructure name, main.temp, and weather
const data = res.data.list.map(({ name, main: { temp }, weather }) => {
// weather is an array of length 1, but still provide fallback
const { description } = weather[0] ?? {};
return {
description,
name,
temp,
};
});
setData(data);
})
.catch((err) => {
console.log(err);
});
};
useEffect(() => {
fetchData();
}, []);
I'm learning React and am having trouble with a value defined in a custom context provider. I access the value in a component under the provider with a custom hook but it's reported as being undefined. I've gone through the questions on SO and have verified my syntax with the lesson in my book but can't find the problem.
This is my custom provider and custom hook:
import React, { createContext, useState, useEffect, useContext } from 'react';
const ApiContext = createContext();
export const useApi = () => useContext(ApiContext);
export const ApiProvider = ({ children }) => {
const [loading, setLoading] = useState(false);
const [error, setError] = useState();
const [baseImageUrl, setBaseImageUrl] = useState();
const apiKey = 'api_key=SECRET';
const baseUrl = 'https://api.themoviedb.org/3';
const objToParams = (obj) => {
let params = '';
if(obj) {
const keys = Object.keys(obj);
for(let key of keys) {
params += `&${key}=${encodeURIComponent(obj[key])}`;
}
}
return params;
}
const api = {
get: async (path, params) => {
const resp = await fetch(baseUrl + path + '?' + apiKey + objToParams(params));
return await resp.json();
}
}
useEffect( () => {
try {
setLoading(true);
const config = api.get('/configuration');
console.log(config);
config.images && setBaseImageUrl(config.images.secure_base_url);
}
catch (error) {
console.error(error);
setError(error);
}
finally {
setLoading(false);
}
}, []);
if( loading ) {
return <p>Loading...</p>;
}
if( error ) {
return <pre>{JSON.stringify(error, null, 2)}</pre>;
}
return (
<ApiContext.Provider value={{ api, baseImageUrl }}>
{ children }
</ApiContext.Provider>
);
}
and this is the component where I access the value through the custom hook:
import React, { useState } from 'react';
import { ApiProvider, useApi } from './components/context/ApiProvider';
import Header from './components/Header';
import Main from './components/Main';
import Footer from './components/Footer';
import './App.css';
const App = () => {
const [searching, setSearching] = useState(false);
const [searchResults, setSearchResults] = useState([])
const [searchError, setSearchError] = useState();
const {api} = useApi();
const onSearch = (query) => {
try {
setSearching(true);
setSearchResults(api.get('/search/multi', {query: encodeURIComponent(query)} ));
console.log(searchResults);
}
catch (error) {
console.error(error);
setSearchError(error);
}
finally {
setSearching(false);
}
}
return (
<ApiProvider>
<div className="main-layout">
<Header onSearch={ onSearch }/>
<Main
searching={ searching }
searchError={ searchError }
searchResults={ searchResults }
/>
<Footer />
</div>
</ApiProvider>
);
}
export default App;
You can't consume the context in the component where you apply it.
<ComponentA>
<Context.Provider value={"somethong"} >
<ComponentB/>
</Context.Provider>
</ComponentA>
In the above example, only ComponentB can consume the value. ComponentA can't.
If you wan't to consume the value in your App component, it has to be the child (or grandchild ...) of the ContextProvider.
<Context.Provider value={"somethong"} >
<App/>
</Context.Provider>
If I understand your code correctly than you are trying to consume the context in your App, while also returning the provider for the same context.
The main gold is to make a serch bar from an external API. I'm using Context API to provide a global state, and a custom async hook to make a call to a pokeapi, I'm currently available, to store the data searched in localstorage, but the thing is that I store that data from a state that changes in a event, so when I reload the page the state is undefined, and sets the local storage value to undefined... there is a better approach to solve this?
context:
import React,{createContext, useEffect} from 'react'
import { usePokemonReducer } from './PokemonReducer'
import {FIND_POKEMON} from './Actions'
export const PokemonContext = createContext()
const PokemonProvider = ({children}) => {
const [state, dispatch] = usePokemonReducer(()=>{
const localData = localStorage.getItem('pokemons');
return localData ? JSON.parse(localData) : [];
});
const { pokemon } = state;
const findPokemon = (pokemon) => dispatch({ type: FIND_POKEMON, pokemon})
useEffect(() => {
localStorage.setItem('pokemons', JSON.stringify(pokemon.pokemon));
}, [pokemon]);
const providerValues = {
pokemon,
findPokemon,
}
return (
<PokemonContext.Provider value={providerValues}>
{children}
</PokemonContext.Provider>
)
}
export default PokemonProvider;
customAsyncHook:
import {useEffect, useState, useContext} from 'react'
import { PokemonContext } from '../../Services/Store/PokemonContext'
import {FIND_POKEMON} from '../../Services/Store/Actions'
import axios from 'axios'
const useAsyncHook = (id) => {
const [result, setResult] = useState();
const [loading, setLoading] = useState('false');
const { findPokemon } = useContext(PokemonContext)
useEffect(() => {
async function getPokemon() {
try {
setLoading('true');
const response = await axios(
`https://pokeapi.co/api/v2/pokemon/${id}`
);
setResult(response.data);
findPokemon({type:FIND_POKEMON, pokemon:response.data });
} catch (error) {
setLoading('null');
findPokemon({type:FIND_POKEMON, pokemon:null });
}
}
if (id !== "") {
getPokemon();
}
}, [id]);
return [result, loading];
}
export default useAsyncHook
You can just use if condition. if pokemon is undefined, you don't need to set item to localStorage.
useEffect(() => {
if (pokemon.pokemon !== undefined) {
localStorage.setItem('pokemons', JSON.stringify(pokemon.pokemon));
}
}, [pokemon]);