How do I use the get request of axios by sending a query from the user and getting the values from the database for that particular query? - reactjs

function AdminMemberSearchFirstName({ name }) {
const [data, setData] = useState([]);
const [query, setQ] = useState("");
function queryGiven(query) {
setQ(query);
}
async function getFilteredData() {
axios.get(`admin/member/firstname/${query}`).then((response) => {
console.log(response.data);
setData(data);
});
}
useEffect(() => {
getFilteredData(name);
}, []);
return (
<div>
<FirstNameForm queryGiven={queryGiven} />
<h1>{query}</h1>
</div>
);
}
I am using axios to get the data from the database using the given API. Instead of using the query variable, if I use the actual name, then it works. When I use the query variable, I think it passes an empty string when the page loads because of which I get 400 error code in the console. The value of query comes from the FirstNameForm component, and that one works. How can I fix this issue?

name is not defined anywhere. If you want to pass a value, pass query
If you want to make the request when query changes, list it in the effect hook dependencies
If you don't want to make requests for empty query, then wrap the call in a condition
// this could even be defined in another module for easier testing
const getFilteredData = async (name) =>
(await axios.get(
`admin/member/firstname/${encodeUriComponent(name)}`
)).data;
useEffect(() => {
// check for empty query
if (query.trim().length > 0) {
getFilteredData(query)
.then(setData)
.catch(console.error);
} else {
setData([]); // clear data for empty query
}
}, [ query ]); // this effect hook will run when query changes

Related

Unexpected result from apollo react's useQuery method

For a project, where i've implemented authentication by running a GraphQL query inside a AuthenticationProvider from a context, I noticed the query is fetching data twice.
const AuthenticationProvider: FC = props => {
const {
loading,
data
} = useQuery(MeQuery)
if (loading) return null
return <AuthenticationContext.Provider value={{user: data?.me || null}} {...props} />
}
However the query runs perfect, I still wanted to know why it fetches the data twice. I did some googling, and came across this issue, where this answer was provided. I tried the same thing, with the skip option, based if the data is loaded.
const [skip, setSkip] = useState(false)
const {
loading,
data
} = useQuery(MeQuery, { skip })
useEffect(() => {
if (!loading && data?.me) {
setSkip(true)
}
}, [loading, data])
// ...
But when logging in, it stopped working.
const useLoginMutation = () => useMutation(LOGIN_QUERY, { update: (cache, { data }) => {
if (!data) {
return null
}
cache.writeQuery({ query: MeQuery, data: { me: data.login } })
}
})
The cache still get's updated with the right values, but doesn't retrieve the user anymore (null).
const { user } = useContext(AuthenticationContext)
What am I missing here? It seems the query did run and fetched the correct data.
You don't need to use context when you are using apollo useQuery. If you make a query first, then the data fetched will be stored in the cache. You can directly access the data from the cache for the second you run the query. Since useQuery has default fetchPolicy cache-first. Mean its check in the cache first, if no query made before it makes a network request.
If you want to avoid network loading. You can make a top-level component AuthWrapper.
const useUserQuery = () => useQuery(ME_QUERY);
const AuthWrapper = ({children}) => {
const {loading, data} = useUserQuery();
if(loading) return null
return children;
}
export GetUsetInThisComponent = ({}) => {
// Since we have fetched the user in AuthWrapper, the user will be fetched from the cache.
const {data} = useUserQuery();
// No you can access user from data?.user
// Rest of the application logic
}
// Wrap the component like this to avoid loading in the children components
<AuthWrapper>
<GetUserInThisComponent />
</AuthWrapper>

In React, fetch data conditional on results of an initial fetch

We have written a custom data fetching hook useInternalApi which is similar to the useDataApi hook at the very bottom of this fairly decent tutorial on data fetching with react hooks. Our app fetches a lot of sports data, and in particular, we are trying to figure out the right data-fetching pattern for our use case, which is fairly simple:
Fetch general info for a specific entity (an NCAA conference, for example)
Use info returned with that entity (an array of team IDs for teams in the specific conference), and fetch info on each team in the array.
For this, our code would then look something like this:
import `useInternalApi` from '../path-to-hooks/useInternalApi';
// import React... and other stuff
function ComponentThatWantsTeamInfo({ conferenceId }) {
// use data fetching hook
const [conferenceInfo, isLoading1, isError1] = useInternalApi('conferenceInfo', { conferenceId: conferenceId })
// once conferenceInfo loads, then load info from all teams in the conference
if (conferenceInfo && conferenceInfo.teamsArray) {
const [teamInfos, isLoading2, isError2] = useInternalApi('teamInfo', { teamIds: conferenceInfo.teamIds })
}
}
In the example above, conferenceId is an integer, teamIds is an array of integers, and the combination of the 2 parameters to the useInternalApi function create a unique endpoint url to fetch data from. The two main problems with this currently are:
Our useInternalApi hook is called in an if statement, which is not allowed per #1 rule of hooks.
useInternalApi is currently built to only make a single fetch, to a specific endpoint. Currently, it cannot handle an array of teamIds like above.
What is the correct data-fetching pattern for this? Ideally, teamInfos would be an object where each key is the teamId for one of the teams in the conference. In particular, is it better to:
Create a new internal hook that can handle an array of teamIds, will make the 10 - 20 fetches (or as many as needed based on the length of the teamsArray), and will use Promise.all() to return the results all-together.
Keep the useInternalApi hook as is, and simply call it 10 - 20 times, once for each team.
Edit
I'm not sure if the underlying code to useInternalApi is needed to answer this question. I try to avoid creating very long posts, but in this instance perhaps that code is important:
const useInternalApi = (endpoint, config) => {
// Set Data-Fetching State
const [data, setData] = useState(null);
const [isLoading, setIsLoading] = useState(true);
const [isError, setIsError] = useState(false);
// Use in lieu of useEffect
useDeepCompareEffect(() => {
// Token/Source should be created before "fetchData"
let source = axios.CancelToken.source();
let isMounted = true;
// Create Function that makes Axios requests
const fetchData = async () => {
// Set States + Try To Fetch
setIsError(false);
setIsLoading(true);
try {
const url = createUrl(endpoint, config);
const result = await axios.get(url, { cancelToken: source.token });
if (isMounted) {
setData(result.data);
}
} catch (error) {
if (isMounted) {
setIsError(true);
}
} finally {
if (isMounted) {
setIsLoading(false);
}
}
};
// Call Function
fetchData();
// Cancel Request / Prevent State Updates (Memory Leaks) in cleanup function
return () => {
isMounted = false; // set to false to prevent state updates / memory leaks
source.cancel(); // and cancel the http request as well because why not
};
}, [endpoint, config]);
// Return as length-3 array
return [data, isLoading, isError];
};
In my opinion, if you need to use a hook conditionally, you should use that hook inside of a separate component and then conditionally render that component.
My understanding, correct me if I'm wrong, is that the initial API call returns an array of ids and you need to fetch the data for each team based on that id?
Here is how I'd do something of that sorts.
import `useInternalApi` from '../path-to-hooks/useInternalApi';
// import React... and other stuff
function ComponentThatDisplaysASpecificTeam(props){
const teamId = props.teamId;
const [teamInfo] = useInternalApi('teamInfo', { teamId });
if(! teamInfo){
return <p>Loading...</p>
}
return <p>do something with teamInfo...</p>
}
function ComponentThatWantsTeamInfo({ conferenceId }) {
// use data fetching hook
const [conferenceInfo, isLoading1, isError1] = useInternalApi('conferenceInfo', { conferenceId: conferenceId })
if (! conferenceInfo || ! conferenceInfo.teamsArray) {
return <p>this is either a loading or an error, you probably know better than me.</p>
}
// Let the data for each team be handled by its own component. This also lets you not have to use Promise.all
return (
<div>
{conferenceInfo.teamIds.map(teamId => (
<ComponentThatDisplaysASpecificTeam teamId={teamId} />
))}
</div>
)
}

React JS Not Read and Write to localStorage in useEffect No Errors

Ultimate goal is to store the JSON data. That way, if the same github user is sent to the GitHubUser component, instead of making a fresh call to the API, it should load the details from the local storage, preventing a network call.
Key Points about the problem.
do a simple fetch from github public api (no issues, working fine)
store the data to local storage with the github username as key (not working)
retrieve the data from local storage by providing a github username as key (not working)
display json data after render is complete using useEffect (working fine)
I get no errors of any kind with localStorage but nothing gets saved. I have tried this on both Firefox and Edge. The network call happens on every change of login, for the same user, which it should not.
Further, this code is from a textbook I am following, and this is a exact copy from the page that discusses fetch and useEffect. The author goes on to explain that it should work and so far the book has been correct with no errors.
I have put the code in a sandbox here - https://codesandbox.io/s/bold-http-8f2cs
Also, the specific code below.
import React, { useState, useEffect } from "react";
const loadJSON = key =>
key && JSON.parse(localStorage.getItem(key));
const saveJSON = (key, data) =>
localStorage.setItem(key, JSON.stringify(data));
function GitHubUser({ login }) {
const [data, setData] = useState(
loadJSON(`user:${login}`)
);
useEffect(() => {
if (!data) return;
if (data.login === login) return;
const { name, avatar_url, location } = data;
saveJSON(`user:${login}`, {
name,
login,
avatar_url,
location
});
}, [data]);
useEffect(() => {
if (!login) return;
if (data && data.login === login) return;
fetch(`https://api.github.com/users/${login}`)
.then(response => response.json())
.then(setData)
.catch(console.error);
}, [login]);
if (data)
return <pre>{JSON.stringify(data, null, 2)}</pre>;
return null;
}
//Jay-study-nildana
//MoonHighway
export default function App() {
return <GitHubUser login="Jay-study-nildana" />;
}
Note : I get a couple of warnings related to useEffect but I have already isolated that they are not the issue but I dont think they are the problem. it simple tells me not to use a dependency array since there is only one element for both useEffects. I am using the array on purpose.
Update 1
One thing I noticed is, in developer tools, nothing is getting stored in Local Storage after a successfull call to the API. So, right now, I am thinking, saving is not working. Unless I get that working and see the stored data in developer tools, I wont know if load is working or not.
First, if the initial state is the result of some computation, you may provide a function instead, which will be executed only on the initial render:
// instead of this
const [data, setData] = useState(
loadJSON(`user:${login}`)
);
// you better have this
const [data, setData] = useState(() => {
return loadJSON(`user:${login}`);
});
Second, you can achieve what you need with this single useEffect:
const [data, setData] = useState(() => { return loadJSON(`user:${login}`); });
useEffect(() => {
if (!data) {
fetch(`https://api.github.com/users/${login}`)
.then((response) => response.json())
.then((val) => {
saveJSON(`user:${login}`, val); // put data into localStorage
setData(val); // update React's component state
})
.catch(console.error);
}
});
if (data) return <pre>{JSON.stringify(data, null, 2)}</pre>;
return <div>no data</div>;
You will get your data in localStorage. Don't forget that you need to use key user:${login} if you need to get it from there.

react promise in functional component with UseEffect and UseState doesn't work

I'm having issue fetching data and setting them to state in a functional component using useEffect and useState.
My problem is that I would like to keep the data fetching done with axios async/await in a separate file for improving application scalability but then I don't understand how to update the state in case the promise is resolved (not rejected).
In particular I'm trying to retrieve from the promise an array of table rows called data in state, but I can't figure out how to set the result of the responce in the state
Here's the code in the component file:
const [data, setData] = React.useState([]);
useEffect(() => {
const { id } = props.match.params;
props.getTableRows(id).then((res) => {
setData(res);
});
//or is it better:
//props.getTableRows(id).then(setData); ?
}, []);
and my action.js:
export const getTableRows = (id, history) => async (dispatch) => {
try {
const res = await axios.get(`/api/test/${id}`);
dispatch({
type: GET_TEST,
payload: res.data.rows,
});
} catch (error) {
history.push("/test");
}
};
In the above picture it can be seen that the rows array inside the promise response called in action.js is present.
This code unfortunately doesn't work, error: Uncaught (in promise) TypeError: Cannot read property 'forEach' of undefined
I've found out another solution which is the define the promise in the useEffect method like this:
useEffect(() => {
const { id } = props.match.params;
const fetchData = async () => {
const result = await axios.get(`/api/test/${id}`);
setData(result.data.rows);
};
fetchData();
}, []);
this code is working in my app but as I said I don't like having the promises in the components files I would like instead to have them all the promise in action.js for app scalability (in case url change I don't have to change all files) but in that case I don't know where to put the setData(result.data.rows); which seems the right choise in this last example
Any suggestions?
Thanks
You still need to use async/await. The .then() is executed when the value is returned, however your function will continue rendering and won't wait for it. (causing it to error our by trying to access forEach on a null state). After it errors the promise via .then() will update the values and that is why you can see them in the console.
useEffect(() => {
async function getData() {
const { id } = props.match.params;
await props.getTableRows(id).then((res) => {
setData(res);
});
}
getData()
}, []);
Additionally, before you access a state you can check for null values (good practice in general).
if (this.state.somestate != null) {
//Run code using this.state.somestate
}
I don't see you return anything from getTableRows. You just dispatch the result, but hadn't return the res for the function call.
And it will be helpful if you provided error trace.

React useEffect infinite loop fetching data from an api

Hi I'm trying to make a twitter clone app. I am using React on the client side and Express on the server side and PostgreSQL as my database. So here's the problem, I'm trying to use the useEffect like this:
const [tweets, setTweets] = useState([]);
const getTweets = async () => {
const res = await api.get("/posts", {
headers: { token: localStorage.token },
});
setTweets(res.data);
};
useEffect(() => {
getTweets();
}, [tweets]);
I have no idea why it's looping infinite times, am I using it correctly though? I want the tweets to be updated every time I post a tweet. It's working fine but it's running infinite times. I just want it to re-render if a tweet got posted.
Here's my server code for getting all the posts:
async all(request: Request, response: Response, next: NextFunction) {
return this.postRepository.find({
relations: ["user"],
order: {
createdAt: "DESC",
},
});
}
The problem is every time you change the tweets it executes useEffect and changes the tweets and so long and so forth, so it's natural that it loops infinitely, the solution is to add a trigger that you set to true when a tweet gets posted, so the solution would be like this
const [tweets, setTweets] = useState([]);
const [isFetching, setIsFetching] = useState(false);
const getTweets = async () => {
const res = await api.get("/posts", {
headers: { token: localStorage.token },
});
setTweets(res.data);
};
useEffect(() => {
getTweets();
setIsFetching(false);
}, [isFetching]);
and set some logic to use setIsFetching(true) in order to execute the useEffect
PS: if you use an empty array in useEffect, it would execute only when the component is mounted (at the start)
useEffect(() => {
getTweets();
}, [tweets]); // [tweets means that hook works every time 'tweets' state changes]
so your getTweets function set tweets => as tweets are changed hook works again => call getTweets => ... = infinite loop
if you want to download tweets, use empty array instead - hook will work once then
Pass empty array as a second arg for calling it once otherwise for changing it on every tweet change it will re-trigger, so whenever state will change only then it will be re-rendered like Tarukami explained. One thing you can do is check the length like mentioned below so not to compare the whole object but just the length
useEffect(() => {
getTweets();
}, [tweets.length]);
This might raise an error react-hooks/exhaustive-deps lint error (that's a bypass you can use it).
But if you want more tighter check you can compare the ids on each re-render (create a hash/key/id from all element in the array and compare them on each render) like so [tweet id here]) // Only re-subscribe if id changes

Resources