How do I make dependent queries in react-query? - javascript

I have a component called Sidebar.tsx that is fetching some data here but I want to only fetch levels only if studentId is available. I looked through dependent queries on react-query documentation . I applied that in my code but it looks like it doesn't work. Here's is what I did.
First of all I fetched the studentId from an API too, here is how it looks like:
const studentId = intakeProgramStore.getStudentShipByUserId(authUser?.id)
.data?.data.data[0];
Now another function to get programs
class IntakeProgramStore{
getIntakeProgramsByStudent(studentId: string) {
return useQuery(
['intakeProgram/studentId', studentId],
() => intakeProgramService.getIntakeProgramsByStudent(studentId),
{ enabled: !!studentId },
);
}
}
export const intakeStore = new IntakeProgramStore();
This function is located in stores/intake.store.ts and what it does is that when it gets called it calls a service method called intakeProgramService.getIntakeProgramsByStudent which is also imported at the top and it works fine as expected. The problem I have here is that it is calling that service even if studentId is undefined while it was supposed to call it when it has a value.
Here's how I'm calling it inside Sidebar.tsx which is my UI component
const programs = intakeProgramStore.getIntakeProgramsByStudent(student.id)
.data?.data.data[0];
I moved the code that was in the stores/intake.store.ts file into Sidebar.tsx and now it worked fine but I don't know why. Here's how it looks
const programs = useQuery(
['intakeProgram/studentId', student?.id],
() => intakeProgramService.getIntakeProgramsByStudent(student?.id),
{ enabled: !!student
I'm not sure why it's not working when I'm fetching inside the store but works when I do it in the UI component.

Generally, your code looks correct. The only thing that I can see is that in the first example, you're calling useQuery inside a function called getIntakeProgramsByStudent, which is an invalid hook call. Hooks can only be called:
by functional react components
inside custom hooks, which are functions that start with use....
see also the rules of hooks react documentation.
A custom hook for your useQuery call would be:
export const useIntakeProgramsByStudent(studentId: string) {
return useQuery(
['intakeProgram/studentId', studentId],
() => intakeProgramService.getIntakeProgramsByStudent(studentId),
{ enabled: !!studentId },
);
}

Related

Unit test redux toolkit queries connected via fixedCacheQuery using react testing library

I have two components in my react app:
Component A
Performs a lazy fetch of users. This looks like:
const ComponentA = () => {
const [trigger, {data}] = useLazyLoadUsers({
fixedCacheKey: fixedLoadUsersKey,
});
useEffect(() => {
trigger();
}, []);
return <div>{data.map(user => user.id)}</div>
}
Component B
Wants to render a loading indicator while useLazyLoadUsers's isLoading property equals true. This component looks like this:
const ComponentB = () => {
const [, {isLoading}] = useLazyLoadUsers({
fixedCacheKey: fixedLoadUsersKey,
});
if (!isLoading) {
return <div>Users loaded</div>
}
return <div>Loading users</div>
}
The issue
While this works well (the states are in sync via the fixedLoadUsersKey), I'm struggling to find documentation or examples on how to test Component B.
Testing Component A is well documented here https://redux.js.org/usage/writing-tests#ui-and-network-testing-tools.
I already have an overwritten react testing library render method that provides a real store (which includes all my auto-generated queries).
What I would like to do is testing that Component B loading indicator renders - or not - based on a mocked isLoading value. I want to keep my current or similar implementation, not duplicating the isLoading state into another slice.
So far, I have tried mocking useLazyLoadUsers without success. I also tried dispatching an initiator before rendering the test, something like
it('should render the loading indicator', async () => {
const store = makeMockedStore();
store.dispatch(myApi.endpoints.loadUsers.initiate());
render(<ComponentB />, {store});
expect(await screen.findByText('Loading users')).toBeVisible();
})
This didn't work either.
Does someone have a hint on how to proceed here or suggestions on best practices?

A React, Vue, or other code depends on an object, not yet available. The best ways to wait for that variable, before rendering component content?

When an HTML document defined a variable that is not available until a later time during the page load.
Issue: A React, Vue, or other block of code depends on an object that has not yet been declared and outside the direct scope of the component, like window['varname']. What are the proper way(s) to wait for that variable to be defined before rendering a component's real content.
My Attempt:
import React from 'react'
import ReactDOM from 'react-dom/client'
import AppWrapper from "./components/AppWrapper";
const App = () => {
let intervalId
intervalId = setInterval(() => {
console.log('Waking up... checking if window.app is defined')
if (window['app'] !== undefined) {
console.log('yes')
clearInterval(intervalId)
} else {
console.log('no')
}
}, 1000)
if(app.ins.length === 0) {
return 'Loading...'
}
return (
<AppWrapper app={window['app']}></AppWrapper>
)
}
export default App
What other ways could you, should you, do it?
I will make it clearer for you :) I will describe exactly my problem: So I am writing a custom Joomla Component for Joomla. The Joomla Component is written in PHP and uses the Joomla Framework. Inside this component, I have written a Reactjs component. The way that you inject JavaScript into Joomla is via Joomla Methods. These methods either load the JS on the head of the document or in the body. Now, my Reactjs component is loaded during this process. This is fine and it works as long as I do not need to rely on outside variables.
I am using Joomla to store data that is need by the Reactjs component. The way that Joomla makes data available to JS is by a Joomla library that will inject the JS object into a script tag. This is also okay. The issue is that when the head tag loads the Reactjs component before the injected JS object, needed by the Reactjs component, is available. In my example above I store the global JS object into the window object as window.app = Some Object. Because the window.app object is not available at the time the Reactjs component has been loaded, I add a setInterval and check every 500 ms.
Then the setInterval wakes up and checks to see if the window["app"] is available yet. It keeps doing that until it is available. Once it is, it quits the interval and loads the Reactjs component container, passing in the required object.
Now, two things here:
I have no way of synchronizing this process in Joomla. Joomla is stubborn like that.
This is my attempted to only load the Reactjs container component once the data is available.
Question: Knowing the situation, what are the best strategies to accomplish this, apart from my current strategy?
Thanks :)
I believe, one of the approaches could be any kind of callback or subscription.
For example, you can define a function, which changes a state in state-container like redux.
(Pseudocode)
async function loadAppData(store) {
const data = await fetch('/some-data');
const json = await data.json();
store.dispatch('data-loaded', json)
}
And in component
function App() {
const appData = useSelector(store => store.appData);
if (!appData) {
return 'Loading...'
}
return <Markup />
}
Other option can be subscription. Again you can add some function which emits some event:
async function loadAppData(store) {
const data = await fetch('/some-data');
const json = await data.json();
eventBus.emit('data-loaded', json)
// or
window.appData = json
}
In react you can
function App() {
const [appData, setAppData] = useState();
useEffect(() => {
setAppData(window.appData)
}, [window.appData])
if (!appData) {
return 'Loading...'
}
return <Markup />
}
Or in Vue.js you could
data() {
return {
appData: ''
}
}
mounted() {
this.$on('data-loaded', this.onDataLoaded)
}
methods: {
onDataLoaded($event) {
this.appData = $event;
}
}

React returns undefined and later the data with fetch api

With the below code I would like to use react-select but when I console.log(testUsers) at first this is blank and then data is finally there, but in the select data is blank. Is there any way to not select blank?
My code:
const { request: getUser, isLoading } = useRequest("");
const [testUsers, setUsers] = useState("");
useEffect(() => {
getUser({
path: `${someapi}/user?id=${record?.user_uid}`,
overwritePath: true,
}).then((data: any) => {
setUsers(data[0].fullname);
});
}, [testUsers]);
console.log(testUsers, "/////////////////");
The output of the console:
/////////
////////
////////
some api returns /////////////
It's to know that React runs the callback of an useEffect after all others normal JavaScript codes such as a console.log() and after the JSX is rendered. And even if that wasn't the case, a network request is asynchronous so you get the data after some delay.
The workaround here is to use a conditional rendering. Something like this as an exmple:
{!testUsers ? <p>Loading...</p> : <div>Render actual content</div> }
But the main error you are making here is to add testUsers in the dependency array. Since you are calling a state setter that's muting it you would get an infinite calls. Do like this instead:
useEffect(() => {
getUser({
path: `${someapi}/user?id=${record?.user_uid}`,
overwritePath: true,
}).then((data: any) => {
setUsers(data[0].fullname);
});
}, []);
Lastly, about why you are getting multiple console.log(), you can check this thread to get a detailed answer.
The reason for this behavior is React Life Cycle. what happened is that in the Mounting phases.
constructor()
getDerivedStateFromProps()
render()
componentDidMount().
because render is triggering first you see that blank select in your DOM.
Read more about it: https://www.w3schools.com/react/react_lifecycle.asp.
Solution:
in your JSX you can check if you have a value in your state then render your JSX.
{testUsers && <h1>Show</h1>}

How to properly Populate a Dropdown with SQL Query Values in React.Js Hook

I have a react component called Sidebar.jsx. Within it, I am making an API call to get a array of fleets to populate an eventual JSX dropdown element within my Sidebar. This results in a simple JSON array.
I have imported a function called getFleets() from my services folder to make the API call. The service uses the fetch API to make a query call to my backend and looks like this:
export async function getFleets() {
const resp = await fetch("http://localhost:5000/fleets", {
method: 'GET',
headers: {},
mode: 'cors'
});
return resp.json();
};
However, when I use the website, it appears to infinitely make the API call. This is my first time trying to make an API call within a react component so I am a bit confused here. Other guides I've read online seem to be similar but I am obviously missing something.
What can I do to make this API call only once and retrieve my JSON array such that I can later use it to populate the options in my return ?
Sidebar.jsx
import React, { useEffect, useState } from "react";
import { getFleets } from "../services/FleetService";
const Sidebar = () => {
const [data, setData] = useState([]);
useEffect(() => {
const setFleets = async () => {
const fleets = await getFleets();
console.log(fleets);
setData(fleets);
}
setFleets();
}, [data]);
return (
<>
// Add data to <select> </select>
);
};
The way your code works, since data is part of the dependency array sent to useEffect, every time data changes the effect runs, which changes data, which runs the effect again ...resulting in the infinite loop.
The simple fix is to remove data from the dependency array, and explicitly specifying an empty array [] as the second parameter of useEffect. This will make the effect run only exactly once, when the component is first rendered.
You need to explicitly specify an empty array because when the second parameter isn't specified at all, the effect will run on every render, bringing back the infinite loop issue.

what is right way to do API call in react js?

I have recently moved from Angular to ReactJs. I am using jQuery for API calls. I have an API which returns a random user list that is to be printed in a list.
I am not sure how to write my API calls. What is best practice for this?
I tried the following but I am not getting any output. I am open to implementing alternative API libraries if necessary.
Below is my code:
import React from 'react';
export default class UserList extends React.Component {
constructor(props) {
super(props);
this.state = {
person: []
};
}
UserList(){
return $.getJSON('https://randomuser.me/api/')
.then(function(data) {
return data.results;
});
}
render() {
this.UserList().then(function(res){
this.state = {person: res};
});
return (
<div id="layout-content" className="layout-content-wrapper">
<div className="panel-list">
{this.state.person.map((item, i) =>{
return(
<h1>{item.name.first}</h1>
<span>{item.cell}, {item.email}</span>
)
})}
<div>
</div>
)
}
}
In this case, you can do ajax call inside componentDidMount, and then update state
export default class UserList extends React.Component {
constructor(props) {
super(props);
this.state = {person: []};
}
componentDidMount() {
this.UserList();
}
UserList() {
$.getJSON('https://randomuser.me/api/')
.then(({ results }) => this.setState({ person: results }));
}
render() {
const persons = this.state.person.map((item, i) => (
<div>
<h1>{ item.name.first }</h1>
<span>{ item.cell }, { item.email }</span>
</div>
));
return (
<div id="layout-content" className="layout-content-wrapper">
<div className="panel-list">{ persons }</div>
</div>
);
}
}
You may want to check out the Flux Architecture. I also recommend checking out React-Redux Implementation. Put your api calls in your actions. It is much more cleaner than putting it all in the component.
Actions are sort of helper methods that you can call to change your application state or do api calls.
Use fetch method inside componentDidMount to update state:
componentDidMount(){
fetch('https://randomuser.me/api/')
.then(({ results }) => this.setState({ person: results }));
}
This discussion has been for a while and #Alexander T.'s answer provided a good guide to follow for newer of React like me. And I'm going to share some additional know-how about calling the same API multiple times to refresh the component, I think it's probably a common question for beginners.
componentWillReceiveProps(nextProps), from official documentation :
If you need to update the state in response to prop changes (for
example, to reset it), you may compare this.props and nextProps and
perform state transitions using this.setState() in this method.
We could conclude that here is the place we handle props from the parent component, have API calls, and update the state.
Base on #Alexander T.'s example:
export default class UserList extends React.Component {
constructor(props) {
super(props);
this.state = {person: []};
}
componentDidMount() {
//For our first load.
this.UserList(this.props.group); //maybe something like "groupOne"
}
componentWillReceiveProps(nextProps) {
// Assuming parameter comes from url.
// let group = window.location.toString().split("/")[*indexParameterLocated*];
// this.UserList(group);
// Assuming parameter comes from props that from parent component.
let group = nextProps.group; // Maybe something like "groupTwo"
this.UserList(group);
}
UserList(group) {
$.getJSON('https://randomuser.me/api/' + group)
.then(({ results }) => this.setState({ person: results }));
}
render() {
return (...)
}
}
Update
componentWillReceiveProps() will be deprecated.
Here are only some methods (all of them in Doc) in the life cycle I think that they are related to deploying API in the general cases:
By referring to the diagram above:
Deploy API in componentDidMount()
The proper scenario to have API call here is that the content (from the response of API) of this component will be static, componentDidMount() only fire once while the component is mounting, even new props are passed from the parent component or have actions to lead re-rendering.
The component do check difference to re-render but not re-mount.
Quote from doc:
If you need to load data from a remote endpoint, this is a good place to
instantiate the network request.
Deploy API in static getDerivedStateFromProps(nextProps, prevState)
We should notice that there are two kinds of component updating, setState() in current component would not trigger this method but re-rendering or new props from parent component would.
We could find out this method also fires while mounting.
This is a proper place to deploy API if we want to use the current component as a template, and the new parameters to make API calls are props coming from parent component.
We receive a different response from API and return a new state here to change the content of this component.
For example:
We have a dropdown list for different Cars in the parent component, this component needs to show the details of the selected one.
Deploy API in componentDidUpdate(prevProps, prevState)
Different from static getDerivedStateFromProps(), this method is invoked immediately after every rendering except the initial rendering. We could have API calling and render difference in one component.
Extend the previous example:
The component to show Car's details may contain a list of series of this car, if we want to check the 2013 production one, we may click or select or ... the list item to lead a first setState() to reflect this behavior (such as highlighting the list item) in this component, and in the following componentDidUpdate() we send our request with new parameters (state). After getting the response, we setState() again for rendering the different content of the Car details. To prevent the following componentDidUpdate() from causing the infinity loop, we need to compare the state by utilizing prevState at the beginning of this method to decide if we send the API and render the new content.
This method really could be utilized just like static getDerivedStateFromProps() with props, but need to handle the changes of props by utilizing prevProps. And we need to cooperate with componentDidMount() to handle the initial API call.
Quote from doc:
... This is also a good place to do network requests as long as you
compare the current props to previous props ...
I would like you to have a look at redux
http://redux.js.org/index.html
They have very well defined way of handling async calls ie API calls, and instead of using jQuery for API calls, I would like to recommend using fetch or request npm packages, fetch is currently supported by modern browsers, but a shim is also available for server side.
There is also this another amazing package superagent, which has alot many options when making an API request and its very easy to use.
You can also fetch data with hooks in your function components
full example with api call: https://codesandbox.io/s/jvvkoo8pq3
second example: https://jsfiddle.net/bradcypert/jhrt40yv/6/
const Repos = ({user}) => {
const [repos, setRepos] = React.useState([]);
React.useEffect(() => {
const fetchData = async () => {
const response = await axios.get(`https://api.github.com/users/${user}/repos`);
setRepos(response.data);
}
fetchData();
}, []);
return (
<div>
{repos.map(repo =>
<div key={repo.id}>{repo.name}</div>
)}
</div>
);
}
ReactDOM.render(<Repos user="bradcypert" />, document.querySelector("#app"))
1) You can use Fetch API to fetch data from Endd Points:
Example fetching all Github repose for a user
/* Fetch GitHub Repos */
fetchData = () => {
//show progress bar
this.setState({ isLoading: true });
//fetch repos
fetch(`https://api.github.com/users/hiteshsahu/repos`)
.then(response => response.json())
.then(data => {
if (Array.isArray(data)) {
console.log(JSON.stringify(data));
this.setState({ repos: data ,
isLoading: false});
} else {
this.setState({ repos: [],
isLoading: false
});
}
});
};
2) Other Alternative is Axios
Using axios you can cut out the middle step of passing the results of
the http request to the .json() method. Axios just returns the data
object you would expect.
import axios from "axios";
/* Fetch GitHub Repos */
fetchDataWithAxios = () => {
//show progress bar
this.setState({ isLoading: true });
// fetch repos with axios
axios
.get(`https://api.github.com/users/hiteshsahu/repos`)
.then(result => {
console.log(result);
this.setState({
repos: result.data,
isLoading: false
});
})
.catch(error =>
this.setState({
error,
isLoading: false
})
);
}
Now you can choose to fetch data using any of this strategies in componentDidMount
class App extends React.Component {
state = {
repos: [],
isLoading: false
};
componentDidMount() {
this.fetchData ();
}
Meanwhile you can show progress bar while data is loading
{this.state.isLoading && <LinearProgress />}
Render function should be pure, it's mean that it only uses state and props to render, never try to modify the state in render, this usually causes ugly bugs and decreases performance significantly. It's also a good point if you separate data-fetching and render concerns in your React App. I recommend you read this article which explains this idea very well. https://medium.com/#learnreact/container-components-c0e67432e005#.sfydn87nm
This part from React v16 documentation will answer your question, read on about componentDidMount():
componentDidMount()
componentDidMount() is invoked immediately after a component is
mounted. Initialization that requires DOM nodes should go here. If you
need to load data from a remote endpoint, this is a good place to
instantiate the network request. This method is a good place to set up
any subscriptions. If you do that, don’t forget to unsubscribe in
componentWillUnmount().
As you see, componentDidMount is considered the best place and cycle to do the api call, also access the node, means by this time it's safe to do the call, update the view or whatever you could do when document is ready, if you are using jQuery, it should somehow remind you document.ready() function, where you could make sure everything is ready for whatever you want to do in your code...
As an addition/update to Oleksandr T.'s excellent answer:
If you use class components, backend calls should happen in componentDidMount.
If you use hooks instead, you should use the effect hook
For example:
import React, { useState, useEffect } from 'react';
useEffect(() => {
fetchDataFromBackend();
}, []);
// define fetchDataFromBackend() as usual, using Fetch API or similar;
// the result will typically be stored as component state
Further reading:
Using the Effect Hook in the official docs.
How to fetch data with React Hooks? by Robin Wieruch
A clean way is to make an asynchronous API call inside componentDidMount with try/catch function.
When we called an API, we receive a response. Then we apply JSON method on it, to convert the response into a JavaScript object. Then we take from that response object only his child object named "results" (data.results).
In the beginning we defined "userList" in state as an empty array. As soon as we make the API call and receive data from that API, we assign the "results" to userList using setState method.
Inside the render function we tell that userList will be coming from state. Since the userList is an array of objects we map through it, to display a picture, a name and a phone number of each object "user". To retrieve this information we use dot notation (e.g. user.phone).
NOTE: depending on your API, your response may look different. Console.log the whole "response" to see which variables you need from it, and then assign them in setState.
UserList.js
import React, { Component } from "react";
export default class UserList extends Component {
state = {
userList: [], // list is empty in the beginning
error: false
};
componentDidMount() {
this.getUserList(); // function call
}
getUserList = async () => {
try { //try to get data
const response = await fetch("https://randomuser.me/api/");
if (response.ok) { // ckeck if status code is 200
const data = await response.json();
this.setState({ userList: data.results});
} else { this.setState({ error: true }) }
} catch (e) { //code will jump here if there is a network problem
this.setState({ error: true });
}
};
render() {
const { userList, error } = this.state
return (
<div>
{userList.length > 0 && userList.map(user => (
<div key={user}>
<img src={user.picture.medium} alt="user"/>
<div>
<div>{user.name.first}{user.name.last}</div>
<div>{user.phone}</div>
<div>{user.email}</div>
</div>
</div>
))}
{error && <div>Sorry, can not display the data</div>}
</div>
)
}}
As best place and practice for external API calls is React Lifecycle method componentDidMount(), where after the execution of the API call you should update the local state to be triggered new render() method call, then the changes in the updated local state will be applied on the component view.
As other option for initial external data source call in React is pointed the constructor() method of the class. The constructor is the first method executed on initialization of the component object instance. You could see this approach in the documentation examples for Higher-Order Components.
The method componentWillMount() and UNSAFE_componentWillMount() should not be used for external API calls, because they are intended to be deprecated. Here you could see common reasons, why this method will be deprecated.
Anyway you must never use render() method or method directly called from render() as a point for external API call. If you do this your application will be blocked.
You must try "axios" library for API call.
Instead of direct using jQuery.
Thanks.
It would be great to use axios for the api request which supports cancellation, interceptors etc. Along with axios, l use react-redux for state management and redux-saga/redux-thunk for the side effects.

Categories

Resources