How Can I call useCollection hook again with updated parameters - javascript

When My Home component(image above) mounts for the first time, it gives call to the hook "useCollection" with the arguments below, Now how can I call useCollection hook again with a different set of arguments for filtering my list,
It gives error calling to a function by using onClickButon handlers as well I m not able to use "UseState" as useState does not call the above hook again.
useCollection Hook
import React, { useEffect, useRef, useState } from "react";
import { projectFirestore } from "../firebase/config";
export const useCollections = (collection, _query, _orderBy) => {
const [documents, setDocuments] = useState(null);
const [error, setError] = useState(null);
const query = useRef(_query).current;
const orderBy = useRef(_orderBy).current;
useEffect(() => {
let ref = projectFirestore.collection(collection);
if (query) {
ref = ref.where(...query);
}
if (orderBy) {
ref = ref.orderBy(...orderBy);
}
const unsubscribe = ref.onSnapshot(
(snapshot) => {
let results = [];
snapshot.docs.forEach((doc) => {
results.push({ ...doc.data(), id: doc.id });
});
setDocuments(results);
setError(null);
},
(error) => {
console.log(error);
setError("could not fetch data");
}
);
return () => unsubscribe();
}, [collection, query, orderBy]);
return { documents, error };
};

Related

useContext is in a render loop once a context function is called by one of its children

Everytime i call the function on one of the useContext children to update the state of a variable it endlessly loops crashing react.
**
Context**
import { createContext, useState, useMemo } from "react";
import { onAuthStateChanged } from "firebase/auth";
import { auth } from "../../pages/firebaseConfig";
export const ScoreContext = createContext();
function ScoreProvider({ children }) {
const [score, setScore] = useState(0);
const [user, setUser] = useState(null);
onAuthStateChanged(auth, (user) => {
retrieveUserData(user);
setUser(user);
});
const retrieveUserData = async () => {
let email = await user.email;
const response = await fetch(
`http://localhost:3001/api/users/email/${email}`
);
const data = await response.json();
// console.log(data.payload.total_score);
setScore(data.payload.total_score);
return data.payload;
};
const [level, setLevel] = useState(null);
function updateLevel(i) {
setLevel(i);
console.log("hello world");
}
console.log(level);
return (
<ScoreContext.Provider
value={{
score: score,
update: retrieveUserData,
user: user,
// level: level,
updateLevel: updateLevel,
}}
>
{children}
</ScoreContext.Provider>
);
}
export default ScoreProvider;
Child
const onClick = () => { context.updateLevel(2); };
Tried adding a useMemo but didnt work. Tried wrapping the state inside the function but made code unreachable.

Why is my function not dispatching in react component?

why is fetchReviews not fetching?
Originally didn't use fetchData in use effect.
Ive tried using useDispatch.
BusinessId is being passed into the star component.
no errors in console.
please let me know if theres other files you need to see.
thank you!
star component:
import React, { useState, useEffect } from 'react';
import { useDispatch } from 'react-redux';
import {AiFillStar } from "react-icons/ai";
import { fetchReviews } from '../../actions/review_actions';
function Star(props) {
const [rating, setRating] = useState(null);
// const [reviews, setReview] = useState(props.reviews)
// const dispatch = useDispatch();
useEffect(() => {
const fetchData = async () => {
await fetchReviews(props.businessId)
};
fetchData();
console.log(props);
// getAverageRating();
});
const getAverageRating = () => {
let totalStars = 0;
props.reviews.forEach(review => {totalStars += review.rating});
let averageStars = Math.ceil(totalStars / props.reviews.length);
setRating(averageStars);
}
return (
<div className='star-rating-container'>
{Array(5).fill().map((_, i) => {
const ratingValue = i + 1;
return (
<div className='each-star' key={ratingValue}>
<AiFillStar
className='star'
color={ratingValue <= rating ? '#D32322' : '#E4E5E9'}
size={24} />
</div>
)
})}
</div>
);
};
export default Star;
star_container:
import { connect } from "react-redux";
import { withRouter } from "react-router-dom";
import Star from "./star";
import { fetchReviews } from "../../actions/review_actions";
const mSTP = state => {
return {
reviews: Object.values(state.entities.reviews)
};
}
const mDTP = dispatch => {
return {
fetchReviews: businessId => dispatch(fetchReviews(businessId))
};
};
export default connect(mSTP, mDTP)(Star);
console image
why is fetchReviews not fetching? Originally didn't use fetchData in use effect. Ive tried using useDispatch. BusinessId is being passed into the star component. no errors in console.
edit!***
made some changes and added useDispatch. now it wont stop running. its constantly fetching.
function Star(props) {
const [rating, setRating] = useState(null);
const [reviews, setReview] = useState(null)
const dispatch = useDispatch();
useEffect(() => {
const fetchData = async () => {
const data = await dispatch(fetchReviews(props.businessId))
setReview(data);
};
fetchData();
// console.log(props);
// getAverageRating();
}), [];
ended up just calling using the ajax call in the useEffect.
useEffect(() => {
const fetchReviews = (businessId) =>
$.ajax({
method: "GET",
url: `/api/businesses/${businessId}/reviews`,
});
fetchReviews(props.businessId).then((reviews) => getAverageRating(reviews));
}), [];
if anyone knows how i can clean up and use the dispatch lmk.
ty all.
useEffect(() => {
const fetchData = async () => {
const data = await dispatch(fetchReviews(props.businessId))
setReview(data);
};
fetchData();
// console.log(props);
// getAverageRating();
}), [];
dependency array is outside the useEffect. Since useEffect has no dependency option passed, function inside useEffect will run in every render and in each render you keep dispatching action which changes the store which rerenders the component since it rerenders code inside useEffect runs
// pass the dependency array in correct place
useEffect(() => {
const fetchData = async () => {
const data = await dispatch(fetchReviews(props.businessId))
setReview(data);
};
fetchData();
// console.log(props);
// getAverageRating();
},[]), ;
Passing empty array [] means, code inside useEffect will run only once before your component mounted

useEffect keeps rendering

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.

react custom hook causing infinite loop

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]);

How to handle localstorage with result of a call to an external API in ReactJS?

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]);

Categories