import React, {useState, useEffect, Component} from 'react';
import {Grid, Paper, TextField} from '#material-ui/core'
import DataManager from './../data_manager/data_manager'
const dataManager = new DataManager();
const Tile = (props)=>{
// Initializing State Variables
const [state, setState] = useState({
data : {}
})
const { status, data, error, isFetching } = useQuery("data",async()=>{
const res = await fetch("localhost:8000");
return res.json()
}
if(status==="success"){
setState({data})
}else{
return(<p>Doing</p>)
}
}
This code results in an infinite loop where the rendering keeps going on in a loop.
I think it is because setState causes useQuery to execute again setting the state again and so on.
Any help is appreciated. I want to store the data I get from useQuery in a state variable.
TIA.
You don't need to, it does that for you.
A great video on React Query is https://www.youtube.com/watch?v=DocXo3gqGdI, where the part part is actually replacing a setup with explicit state handling by simply using useQuery.
Should you really want to set some state then a better way is to do it from the onSuccess callback. See https://react-query.tanstack.com/reference/useQuery.
You might want to use useEffect as for now you fetch on every render:
const Tile = (props) => {
const [state, setState] = useState({
data: {},
});
const { status, data, error, isFetching } = useQuery("data", async () => {
const res = await fetch("localhost:8000");
return res.json();
});
useEffect(() => {
if (status === 'success') {
setState({ data });
}
}, [status, data]);
return status === 'success' ? (
<div>Success and use data</div>
) : (
<div>Loading</div>
);
};
Related
I want to use the list to seData and setUserData. I am using data to map through the list of users and display on the table. I am using the userData to get user information in different components. This is causing infinite loop.
import { useState, useEffect, useContext } from 'react';
import { collection, onSnapshot } from 'firebase/firestore';
import { db, auth } from '../../firebase';
import { UserContext } from '../../Contexts/UserContext';
const Datatable = () => {
const {userData, setUserData} = useContext(UserContext);
const [data, setData] = useState([]);
const [isEditing, setIsEditing] = useState(false);
const [currentId, setCurrentId] = useState("");
useEffect(() => {
const getUserData = () => {
onSnapshot(collection(db, "users"), (snapshot) => {
let list = [];
snapshot.docs.forEach((doc) => {
list.push({ id: doc.id, ...doc.data() });
setData(list);
setUserData(list)
});
}, (err) => {
console.log(err);
});
}
getUserData();
}, [])
}
Updating values to the state should not be done in useEffect. You have to create a function outside and update the state value.
If you do so, You will be getting Warning: Maximum update depth exceeded. error.
This happens because once if you update a state the reference of the state will be changed and component rerender happens. Again since the reference is changed. The useEffect will be called again and this happens infinitely which the react stops after a certain extent.
I am trying to render data from rest api site, I can get all info without issues, but is duplicating the data with an empty array first and this is creating a conflict with the map() function.
when I do a console logo I can see the duplication. what I need is to only get the array that has the data and the empty one or how can I select the array with data, since for somereason when i used the map() function I get error because its reading the empty array
useFetchData.js
import { useEffect, useState} from 'react';
import http from '../../services/httpservices';
import config from '../../services/config.json';
const useFetchData = () => {
const [data, setData] = useState([]);
const [loading, setLoading] = useState(true);
useEffect(() => {
const fetchData = async () => {
try {
const { data: response } = await http.get(config.apiEndpoint);
setData(response);
} catch (error) {
console.error(error)
}
setLoading(false);
};
fetchData();
}, []);
return {
data,
loading,
};
};
export default useFetchData;
customsite.jsx
import React, { useState } from 'react';
import Modal from './reusable/modal';
import useFetchData from './hooks/useFetchData';
const Customsite = ()=> {
const {
data,
loading,
} = useFetchData();
console.log(data);
return(
<div>
<p> we here </p>
</div>
)
}
export default Customsite
you only need to wait until the data has loaded to get the full array, you must condition the console log to loading === false
!loading && console.log(data);
the same goes with the map function you want to use. you need to add this condition. Either that or test if data.length > 0
I just started to learn React and was trying to fetch some random data. i created a useState and have two values : const [name, setName] = useState([]);
when i try to do name : response.json();
I get an error that assignment to a constant variable, I'm following a tutorial which is doing the same.
surprisingly when I create a constant variable with a different name, it works. I only can't assign the name = await response.json();
Thank you
import React, { useEffect, useState } from "react";
import { ReactDOM } from "react";
const FetchData = () =>{
const [name, setName] = useState([]);
const fetchNames = async () =>{
const url = `https://randomuser.me/api`;
try {
const response = await fetch(url);
name = await response.json();
console.log(name);
setName(name);
} catch (error) {
console.log(error);
}
}
useEffect(()=>{
fetchNames();
},[])
return(
<div>
</div>
);
}
export default FetchData;
Check my example, as I understand you want to use name inside the try as variable not from state so you should add const. I also mapped the response in your render which you can see the results. Here also codesandbox example https://codesandbox.io/embed/friendly-ishizaka-57i66u?fontsize=14&hidenavigation=1&theme=dark
import { useEffect, useState } from "react";
import "./styles.css";
export default function App() {
const [name, setName] = useState([]);
const fetchNames = async () => {
const url = `https://randomuser.me/api`;
try {
const response = await fetch(url);
constname = await response.json();
console.log(name);
setName(name?.results);
} catch (error) {
console.log(error);
}
};
useEffect(() => {
fetchNames();
}, []);
return (
<div>
{name.map((el) => (
<>
<p>{el.email}</p>
<p>{el.phone}</p>
</>
))}
</div>
);
}
there are 2 things that might help with this confusion
any variable created using const will forever hold the value it had when it was declared, so for eg if we have const x = "😀", we later can't do something like x = "something else"
in the snippet you shared name is a "state variable". state variables should NEVER be updated directly, state updates must happen through setState function only(i.e. setName in this case)
this is the part in official docs that talks about it https://reactjs.org/docs/state-and-lifecycle.html#do-not-modify-state-directly , https://reactjs.org/docs/hooks-state.html#updating-state
I'm fairly new to the context API and react hooks beyond useState and useEffect so please bare with me.
I'm trying to create a custom useGet hook that I can use to GET some data from the backend then store this using the context API, so that if I useGet again elsewhere in the app with the same context, it can first check to see if the data has been retrieved and save some time and resources having to do another GET request. I'm trying to write it to be used generally with various different data and context.
I've got most of it working up until I come to try and dispatch the data to useReducer state and then I get the error:
Hooks can only be called inside the body of a function component.
I know I'm probably breaking the rules of hooks with my call to dispatch, but I don't understand why only one of my calls throws the error, or how to fix it to do what I need. Any help would be greatly appreciated.
commandsContext.js
import React, { useReducer, useContext } from "react";
const CommandsState = React.createContext({});
const CommandsDispatch = React.createContext(null);
function CommandsContextProvider({ children }) {
const [state, dispatch] = useReducer({});
return (
<CommandsState.Provider value={state}>
<CommandsDispatch.Provider value={dispatch}>
{children}
</CommandsDispatch.Provider>
</CommandsState.Provider>
);
}
function useCommandsState() {
const context = useContext(CommandsState);
if (context === undefined) {
throw new Error("Must be within CommandsState.Provider");
}
return context;
}
function useCommandsDispatch() {
const context = useContext(CommandsDispatch);
if (context === undefined) {
throw new Error("Must be within CommandsDispatch.Provider");
}
return context;
}
export { CommandsContextProvider, useCommandsState, useCommandsDispatch };
useGet.js
import { API } from "aws-amplify";
import { useRef, useEffect, useReducer } from "react";
export default function useGet(url, useContextState, useContextDispatch) {
const stateRef = useRef(useContextState);
const dispatchRef = useRef(useContextDispatch);
const initialState = {
status: "idle",
error: null,
data: [],
};
const [state, dispatch] = useReducer((state, action) => {
switch (action.type) {
case "FETCHING":
return { ...initialState, status: "fetching" };
case "FETCHED":
return { ...initialState, status: "fetched", data: action.payload };
case "ERROR":
return { ...initialState, status: "error", error: action.payload };
default:
return state;
}
}, initialState);
useEffect(() => {
if (!url) return;
const getData = async () => {
dispatch({ type: "FETCHING" });
if (stateRef.current[url]) { // < Why doesn't this also cause an error
const data = stateRef.current[url];
dispatch({ type: "FETCHED", payload: data });
} else {
try {
const response = await API.get("talkbackBE", url);
dispatchRef.current({ url: response }); // < This causes the error
dispatch({ type: "FETCHED", payload: response });
} catch (error) {
dispatch({ type: "ERROR", payload: error.message });
}
}
};
getData();
}, [url]);
return state;
}
EDIT --
useCommandsState and useCommandsDispatch are imported to this component where I call useGet passing the down.
import {
useCommandsState,
useCommandsDispatch,
} from "../../contexts/commandsContext.js";
export default function General({ userId }) {
const commands = useGet(
"/commands?userId=" + userId,
useCommandsState,
useCommandsDispatch
);
Why am I only getting an error for the dispatchRef.current, and not the stateRef.current, When they both do exactly the same thing for the state/dispatch of useReducer?
How can I refactor this to solve my problem? To summarise, I need to be able to call useGet in two or more places for each context with the first time it's called the data being stored in the context passed.
Here are various links to things I have been reading, which have helped me to get this far.
How to combine custom hook for data fetching and context?
Updating useReducer 'state' using useEffect
Accessing context from useEffect
https://reactjs.org/warnings/invalid-hook-call-warning.html
I think your problem is because you are using useRef instead of state for storing state. If you useRef for storing state you need to manually tell react to update.
I personally would not use reducer and just stick to the hooks you are familiar with as they fulfill your current requirements. I also think they are the best tools for this simple task and are easier to follow.
Code
useGetFromApi.js
This is a generalized and reusable hook - can be used inside and outside of the context
export const useGetFromApi = (url) => {
const [data, setData] = useState(null);
const [error, setError] = useState(null);
const [loading, setLoading] = useState(false);
useEffect(() => {
if (!url) return;
const getData = async () => {
try {
setLoading(true);
setData(await API.get('talkbackBE', url));
} catch ({ message }) {
setError(message);
} finally {
setLoading(false); // always set loading to false
}
};
getData();
}, [url]);
return { data, error, loading };
};
dataProvider.js
export const DataContext = createContext(null);
export const DataProvider = ({ children, url}) => {
const { data, error, loading } = useGetFromApi(url);
return (
<DataContext.Provider value={{ data, error, loading }}>
{children}
</DataContext.Provider>
);
};
useGet.js
Don't need to check if context is undefined - React will let you know
export const useGet = () => useContext(DataContext);
Usage
Most parent wrapping component that needs access to data. This level doesn't have access to the data - only it's children do!
const PageorLayout = ({children}) => (
<DataProvider url="">{children}</DataProvider>
)
A page or component that is nested inside of the context
const NestedPageorComponent = () => {
const {data, error, loading } = useGet();
if(error) return 'error';
if(loading) return 'loading';
return <></>;
}
Hopefully this is helpful!
Note I wrote most of this on Stack in the editor so I was unable to test the code but it should provide a solid example
Context
All of my components need to fetch data.
How I fetch
Therefore I use a custom hook which fetches the data using the useEffect hook and axios. The hook returns data or if loading or on error false. The data is an object with mostly an array of objects.
How I render
I render my data conditional with an ternary (?) or the use of the short circuit (&&) operator.
Question
How can I destructure my data dependent if my useFetch hook is returning false or the data in a way i can reuse the logic or an minimal implementation to the receiving component?
What I have tried
moving the destructuring assignment into an if statement like in return. Issue: "undefined" errors => data was not available yet
moving attempt 1 to function. Issue: function does not return variables (return statement does not work either)
//issue
fuction Receiver() {
const query = headerQuery();
const data = useFetch(query);
const loaded = data.data //either ```false``` or object with ```data```
/*
The following part should be in an easy condition or passed to an combined logic but I just dont get it
destructuring assignment varies from component to component
ex:
const {
site,
data: {
subMenu: {
description,
article_galleries,
image: {
caption,
image: [{url}],
},
},
},
} = data;
*/
return loaded?(
<RichLink
title={title}
text={teaserForText}
link={link}
key={id}
></RichLink>):<Loading />
(
//for context
import axios from "axios";
import {
useHistory
} from "react-router-dom";
import {
useEffect,
useState
} from "react";
function useFetch(query) {
const [data, setData] = useState(false);
const [site, setSite] = useState(""); // = title
const history = useHistory();
useEffect(() => {
axios({
url: "http://localhost:1337/graphql",
method: "post",
data: {
query: query,
},
})
.then((res) => {
const result = res.data.data;
setData(result);
if (result === null) {
history.push("/Error404");
}
setSite(Object.keys(result)[0]);
})
.catch((error) => {
console.log(error, "error");
history.push("/Error");
});
}, [query, history, setData, setSite]);
return {
data: data,
site: site
};
}
export default useFetch;
)
You can return the error, data and your loading states from your hook. Then the component implementing the hooks can destructure all of these and do things depending upon the result. Example:
const useAsync = () => {
// I prefer status to be idle, pending, resolved and rejected.
// where pending status is loading.
const [status, setStatus] = useState('idle')
const [data, setData] = useState([])
const [error, setError] = useState(null)
useEffect(() => {
setStatus('pending')
axios.get('/').then(resp => {
setStatus('resolved')
setData(resp.data)
}).catch(err => {
setStatus('rejected') // you can handle error boundary
setError(err)
})
}, []}
return {status, data, error}
}
Component implementing this hook
const App = () => {
const {data, status, error} = useAsync()
if(status === 'idle'){
// do something
}else if(status === 'pending'){
return <Loader />
}else if(status === 'resolved'){
return <YourComponent data ={data} />
}else{
return <div role='alert'>something went wrong {error.message}</div>
}
}
the hooks can be enhanced more with the use of dynamic api functions.