react query returning empty object for data - reactjs

I am trying to abstract away my react/tanstack query.
I have a custom hook like the following:
const useGamesApi = () => {
const upcomingGamesQuery = useQuery(
["upcoming", date],
async () => {
const ret = await apiGetUpcomingGames(date);
return ret;
},
{
onSuccess: (data) => {
setGames(data);
},
}
);
return {
games: upcomingGamesQuery,
};
};
export default useGamesApi;
I am trying to consume my API as follows:
const [games, setGames] = useState<Game[]>([]);
const gamesApi = useGamesApi();
useEffect(() => {
setGames(gamesApi.games.data);
}, []);
This leads to compilation errors and also the value of my games state variable remains an empty array, as if the useEffect never ran.
Basically I am trying to abstract away my react query to provide a simplified way of interacting with it for my components, whilst also giving it a chance to modify the parameter of the date, so that I can be able to set until which date I would like to query.
What would be the correct (compilation vise) and idiomatic way of doing this with react?
(note I am using this in a react native project, not sure if it counts.)

As per rules , You need to add all the variables used inside useEffect as dependency so that it reacts once the value is changed.
You don't really need useEffect for you scenario. It is used to cause side effects. simply do it like :
const games: Game[] = gamesApi?.games?.data;
const games: Game[] = gamesApi?.games?.data || []; // incase you need default value

Related

Why "React hook is called in a function" while exporting simple single function that accesses a useAtom state?

I have this simple function that needs to set a state from jotai. I would like this function to have it's own seperate file or be cleaned up somewhere, since it will be reused. I'm new to React and come from Angular. It's kind of a function in a service Angular wise.
How would you solve this properly in React?
Code:
export const setMetamaskWallet = (): void => {
const [, setWeb3] = useAtom(web3Atom);
const [, setLoading] = useAtom(loadingAtom);
const [wallet, setWallet] = useAtom(walletAtom);
setWeb3(
new Web3(window.ethereum),
)
//Todo: Create check if metamask is in browser, otherwise throw error
const setAccount = async () => {
setLoading(true);
const accounts = await window.ethereum.request(
{
method: 'eth_requestAccounts'
},
);
setWallet(
accounts[0],
);
setLoading(false);
}
if (!wallet) {
setAccount();
}
}
Hooks can only be called in React functional components or other hooks. Here it appears you have called it in a typical function, hence the error. You could package this functionality in a custom hook. It may however be most appropriate to keep this function as it is, and instead of calling hooks within it, pass the relevant data into the function as parameters.

How to re-render a component when a non state object is updated

I have an object which value updates and i would like to know if there is a way to re-render the component when my object value is updated.
I can't create a state object because the state won't be updated whenever the object is.
Using a ref is not a good idea(i think) since it does not cause a re-render when updated.
The said object is an instance of https://docs.kuzzle.io/sdk/js/7/core-classes/observer/introduction/
The observer class doesn't seem to play well with your use case since it's just sugar syntax to manage the updates with mutable objects. The documentation already has a section for React, and I suggest following that approach instead and using the SDK directly to retrieve the document by observing it.
You can implement this hook-observer pattern
import React, { useCallback, useEffect, useState } from "react";
import kuzzle from "./services/kuzzle";
const YourComponent = () => {
const [doc, setDoc] = useState({});
const initialize = useCallback(async () => {
await kuzzle.connect();
await kuzzle.realtime.subscribe(
"index",
"collection",
{ ids: ["document-id"] },
(notification) => {
if (notification.type !== "document" && notification.event !== "write")
return;
// getDocFromNotification will have logic to retrieve the doc from response
setDoc(getDocFromNotification(notification));
}
);
}, []);
useEffect(() => {
initialize();
return () => {
// clean up
if (kuzzle.connected) kuzzle.disconnect();
};
}, []);
return <div>{JSON.stringify(doc)}</div>;
};
useSyncExternalStore, a new React library hook, is what I believe to be the best choice.
StackBlitz TypeScript example
In your case, a simple store for "non state object" is made:
function createStore(initialState) {
const callbacks = new Set();
let state = initialState;
// subscribe
const subscribe = (cb) => {
callbacks.add(cb);
return () => callbacks.delete(cb);
};
// getSnapshot
const getSnapshot = () => state;
// setState
const setState = (fn) => {
state = fn(state);
callbacks.forEach((cb) => cb());
};
return { subscribe, getSnapshot, setState };
}
const store = createStore(initialPostData);
useSyncExternalStore handles the job when the update of "non state object" is performed:
const title = React.useSyncExternalStore(
store.subscribe,
() => store.getSnapshot().title
);
In the example updatePostDataStore function get fake json data from JSONPlaceholder:
async function updatePostDataStore(store) {
const response = await fetch(`https://jsonplaceholder.typicode.com/posts/${Math.floor(Math.random()*100)+1}`)
const postData = await response.json()
store.setState((prev)=>({...prev,...postData}));
};
My answer assumes that the object cannot for some reason be in React as state (too big, too slow, too whatever). In most cases that's probably a wrong assumption, but it can happen.
I can't create a state object because the state won't be updated whenever the object is
I assume you mean you can't put that object in a React state. We could however put something else in state whenever we want an update. It's the easiest way to trigger a render in React.
Write a function instead of accessing the object directly. That way you can intercept every call that modifies the object. If you can reliably run an observer function when the object changes, that would work too.
Whatever you do, you can't get around calling a function that does something like useState to trigger a render. And you'll have to call it in some way every time you're modifying the object.
const myObject = {};
let i = 0;
let updater = null;
function setMyObject(key, value) {
myObject[key] = value;
i++;
if (updater !== null) {
updater(i);
}
};
Change your code to access the object only with setMyObject(key, value).
You could then put that in a hook. For simplicity I'll assume there's just 1 such object ever on the page.
function useCustomUpdater() {
const [, setState] = useState(0);
useEffect(()=>{
updater = setState;
return () => {
updater = null;
}
}, [setState]);
}
function MyComponent() {
useCustomUpdater();
return <div>I re-render when that object changes</div>;
}
Similarly, as long as you have control over the code that interacts with this object, you could wrap every such call with a function that also schedules an update.
Then, as long as your code properly calls the function, your component will get re-rendered. The only additional state is a single integer.
The question currently lacks too much detail to give a good assessment whether my suggested approach makes sense. But it seems like a very simple way to achieve what you describe.
It would be interesting to get more information about what kind of object it is, how frequently it's updated, and in which scope it lives.

react doesn't render latest value

For the initial render, I have object date, which is an empty array. I then try to get data from an influxDB, but the get result isn't reflected by React with a re-render. The get function is calling in useEffect (you can see this in screenshots). I use typescript, and to avoid getting an error on the initial load (that data is an empty array of objects and it doesn't have a value property) I use the typescript syntax, but it still doesn't display the correct value. It doesn't display anything at all.
What could be the problem? In the last photo, we can see another way to display data without a question mark from typescript, but it doesn't work correctly either, even if the length of the array is greater than 0, it still doesn't display data[0].value.
Initial data:
Data after DB get:
Get the first element in array:
Browser result (before ':' we should see data[0].value):
Alternate way (when data isn't empty we should see the value of the first object in array):
I also show we code
import React from 'react';
import './App.css';
import { FluxTableMetaData } from '#influxdata/influxdb-client';
const { InfluxDB } = require('#influxdata/influxdb-client');
export const App = () => {
debugger
const authData = {
token: 'Emyw1rqUDthYRLpmmBc6O1_yt9rGTT57O50zoKiXUoScAjL6G-MgUN6G_U9THilr86BfIPHMYt6_KSDNHhc9Jg==',
org: 'testOrg',
bucket: 'test-bucket',
};
const client = new InfluxDB({
url: 'http://localhost:8086',
token: authData.token,
});
const queryApi = client.getQueryApi(authData.org);
const query = `from(bucket: "${authData.bucket}") |> range(start: -1d)`;
const data: any[] = [];
React.useEffect(() => {
queryApi.queryRows(query, {
next(row: string[], tableMeta: FluxTableMetaData) {
debugger;
const o = tableMeta.toObject(row);
const item = {
time: o._time,
measurement: o._measurement,
field: o._field,
value: o._value,
};
return data.push(item);
},
error(error: Error) {
return error;
},
complete() {
console.log(data)
return data;
},
})
},[]);
debugger;
return (
<div>
<div>{data.length !== data[0].value}:</div>
<div>hello</div>
</div>
);
};
another way:
<div>
<div>{data[0]?.value}:</div>
<div>hello</div>
</div>
The main issue in your code is, You have defined data as a const variable, and not as a state. Thus, in useEffect, even if your data gets changed, it will not reflect on data[0].value as it is a const variable and react doesn't render updated values of variables. It updates/renders only if it's a state.
In short, Convert your const data to be a stateand use setState like below for your code to work!
const [data, setData] = React.useState([]);
...
setData([...data , item]);
I suggest you use the React States for that in the following way
var [nameOfVariableWhichWillChange, changeFunction] = React.useState("");
now whenever whichever function wants to change the value of that function just use changeFunction(newValueOfVariable)
the plus point of using React state is wherever you might have used that variable on change of That variable each instance will change on its own...
Do let me know does that solve your problem, or you need something else
React doesn't re-render the webpage even if the data has changed. You need to store your data inside a React.useState and call setState to trigger a re-render.
const [data, setData] = useState([])
React.useEffect(() => {
...
next(row: string[], tableMeta: FluxTableMetaData) {
...
setData([...data, item])
},
...
Read about useState here for more information: https://reactjs.org/docs/hooks-state.html

useEffect on infinite loop using async fetch function

I am trying to understand why the following useEffect is running in an infinite loop. I made the fetchSchedule helper function to call the getSchedule service (using Axios to query the API endpoint). Reason I did not define this function inside the useEffect hook is because I would like to alternatively also call it whenever the onStatus function is invoked (which toggles a Boolean PUT request on a separate endpoint).
The eslinter is requiring fetchSchedule be added to the array of dependencies, which seems to be triggering the infinite loop.
The way it should work is fetching the data from the database on first render, and then only each time either the value prop is updated or the onStatus button is toggled.
So far my research seems to point that this may have something to do with the way useEffect behaves with async functions and closures. I’m still trying to understand Hooks and evidently there’s something I’m not getting in my code…
import React, { useEffect, useCallback } from 'react';
import useStateRef from 'react-usestateref';
import { NavLink } from 'react-router-dom';
import { getSchedule, updateStatus } from '../../services/scheduleService';
import Status from './status';
// import Pagination from './pagination';
const List = ({ value }) => {
// eslint-disable-next-line
const [schedule, setSchedule, ref] = useStateRef([]);
// const [schedule, setSchedule] = useState([]);
const fetchSchedule = useCallback(async () => {
const { data } = await getSchedule(value);
setSchedule(data);
}, [value, setSchedule]);
const onStatus = (id) => {
updateStatus(id);
fetchSchedule();
console.log('fetch', ref.current[0].completed);
};
useEffect(() => {
fetchSchedule();
}, [fetchSchedule]);
return (...)
Update March 2021
After working with the repo owner for react-usestateref, the package now functions as originally intended and is safe to use as a replacement for useState as of version 1.0.5. The current implementation looks like this:
function useStateRef(defaultValue) {
var [state, setState] = React.useState(defaultValue);
var ref = React.useRef(state);
var dispatch = React.useCallback(function(val) {
ref.current = typeof val === "function" ?
val(ref.current) : val;
setState(ref.current);
}, []);
return [state, dispatch, ref];
};
You would be fine if it weren't for this react-usestateref import.
The hook returns a plain anonymous function for setting state which means that it will be recreated on every render - you cannot usefully include it in any dependency array as that too will be updated on every render. However, since the function is being returned from an unknown custom hook (and regardless, ESLint would correctly identify that it is not a proper setter function) you'll get warnings when you don't.
The 'problem' which it tries to solve is also going to introduce bad practice into your code - it's a pretty way to avoid properly handling dependencies which are there to make your code safer.
If you go back to a standard state hook I believe this code will work fine. Instead of trying to get a ref of the state in onStatus, make it async as well and return the data from fetchSchedule as well as setting it.
const [schedule, setSchedule] = useState([]);
const fetchSchedule = useCallback(async () => {
const { data } = await getSchedule(value);
setSchedule(data);
return data;
}, [value]);
const onStatus = async (id) => {
updateStatus(id);
const data = await fetchSchedule();
};
useEffect(() => {
fetchSchedule();
}, [fetchSchedule]);
Alternatively, although again I wouldn't really recommend using this, we could actually write a safe version of the useStateRef hook instead:
function useStateRef(defaultValue) {
var [state, setState] = React.useState(defaultValue);
var ref = React.useRef(defaultValue);
ref.current = state;
return [state, setState, ref];
}
A state setter function is always referentially identical throughout the lifespan of a component so this can be included in a dependency array without causing the effect/callback to be recreated.

React Hooks: Referencing data that is stored inside context from inside useEffect()

I have a large JSON blob stored inside my Context that I can then make references to using jsonpath (https://www.npmjs.com/package/jsonpath)
How would I go about being able to access the context from inside useEffect() without having to add my context variable as a dependency (the context is updated at other places in the application)?
export default function JsonRpc({ task, dispatch }) {
const { data } = useContext(DataContext);
const [fetchData, setFetchData] = useState(null);
useEffect(() => {
task.keys.forEach(key => {
let val = jp.query(data, key.key)[0];
jp.value(task.payload, key.result_key, val);
});
let newPayload = {
jsonrpc: "2.0",
method: "call",
params: task.payload,
id: "1"
};
const domain = process.env.REACT_APP_WF_SERVER;
let params = {};
if (task.method === "GET") {
params = newPayload;
}
const domain_params =
JSON.parse(localStorage.getItem("domain_params")) || [];
domain_params.forEach(e => {
if (e.domain === domain) {
params[e.param] = e.value;
}
});
setFetchData({ ...task, payload: newPayload, params: params });
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [task]);
}
I'm gonna need to post an answer because of code, but I'm not 100% sure about what you need, so I'll build a correct answer with your feedback :)
So, my first idea is: can't you split your effects in two React.useEffect? Something like this:
export default function JsonRpc({ task, dispatch }) {
...
useEffect(() => {
...
setFetchData(...);
}, [task]);
useEffect(() => {
...
}, [data]);
..
}
Now, if my understanding are correct, this is an example of events timeline:
Due to the update on task you will trigger the first useEffect, which can setFetchData();
Due to the update on fetchData, and AXIOS call is made, which updates data (property in the context);
At this, you enter the second useEffect, where you have the updated data, but NO call to setFetchData(), thus no loop;
Then, if you wanted (but couldn't) put data in the dependencies array of your useEffect, I can imagine the two useEffect I wrote have some shared code: you can write a common method called by both useEffects, BUT it's important that the setFetchData() call is outside this common method.
Let me know if you need more elaboration.
thanks for your reply #Jolly! I found a work around:
I moved the data lookup to a state initial calculation:
const [fetchData] = useState(processFetchData(task, data));
then im just making sure i clear the component after the axios call has been made by executing a complete function passed to the component from its parent.
This works for now, but if you have any other suggestions id love to hear them!

Resources