I am trying to build a conditional dynamic react component where makes an API call based on the user interaction, but if the user types something in the search bar. I want to add search= param the otherwise use /list endpoint without query params. I am using currently Axios , and I would like to know some approach to do the following
const FeedsList = () => {
const [feed, setFeed] = useState([]);
const [currentPageUrl, setCurrentPageUrl] = useState("http://localhost:8001/api/v1/feeds/list/")
const performSearch = () => {
//setLoading(true)
api.get(currentPageUrl).then(res => { // axios call
setLoading(false)
setFeed(res.data.results)
}).catch(function(error){
console.log(error);
});
}
const handleSearch = (e) =>{
console.log(e.target.value)
//performSearch();
}
useEffect(() => {
performSearch()
}, [currentPageUrl]);
if (loading) return "Loading..."
}
export const api = axios.create(
{baseURL : 'http://localhost:8001/api/v1/feeds/list/'}
)
user input
<input type="text" placeholder="Enter keyword" onChange={event => handleSearch(event)}/>
Store user input to state, not URL, and then construct your URL from initial value (list) and user input, if any:
const FeedsList = () => {
const [loading, setLoading] = useState(false);
const [feed, setFeed] = useState([]);
const [searchString, setSearchString] = useState("");
const performSearch = (searchString) => {
setLoading(true);
let url = "http://localhost:8001/api/v1/feeds/list/";
// you might want to escape this value, and sanitize input on the server
if (searchString) url = `${url}?search=${searchString}`;
const cancelTokenSource = axios.CancelToken.source();
return api
.get(url, { cancelToken: cancelTokenSource.token })
.then((res) => {
setLoading(false);
setFeed(res.data.results);
})
.catch(function (error) {
console.log(error);
});
return cancelTokenSource;
};
const handleSearch = (event) => {
setSearchString(event.target.value);
};
useEffect(() => {
let token = performSearch(searchString);
return () => token.cancel();
}, [searchString]);
if (loading) return "Loading...";
};
You might want to debounce or throttle requests, so you will not bombard your server with requests on each keystroke
The Axios api allows for passing a second parameter to the get method which is the config for the request being sent. That config object takes a params property that would be parsed and appended onto the url as a query string. The nice thing is if the params object is empty it will be like not passing anything at all. A fuller example here on their GitHub page.
Passing an empty params object is the same as passing no params object at all in terms of what is requested.
// both lines request url looks like this => https://jsonplaceholder.typicode.com/users
axios.get('https://jsonplaceholder.typicode.com/users')
axios.get('https://jsonplaceholder.typicode.com/users', { params: {} })
To answer your question you could conditionally just create the params based on whether there is a value from the search input, something like the following:
const performSearch = () => {
const config = search === '' ? {
params: {}
} : {
params: { search } // same as { search: search }
}
api.get(currentPageUrl, config).then(res => {
// do something with response
}).catch(function(error){
console.log(error);
});
}
An assumption in the above would be that you stored the search value in state somewhere and add it to your useEffect dependencies list, referencing it in performSearch.
Related
I try to get data from the backend and view data in the frontend. To do this I try this code.
function ViewPost() {
const { postId } = useParams();
console.log(postId);
const [posts, setPosts] = useState({});
useEffect(() => {
getOnePost();
}, []);
const getOnePost = async () => {
try {
const response = await axios.get(`/buyerGetOnePost/${postId}`);
console.log(response);
const allPost = response.data.onePost;
setPosts(allPost);
} catch (error) {
console.error(`Error: ${error}`);
}
};
console.log(posts);
console.log(posts.wasteItemList);
const [offers, getOffers] = useState([]);
useEffect(() => {
getAllOffers();
}, []);
const getAllOffers = async () => {
await axios
.get(`/viewPendingSellerOffers`)
.then((response) => {
const allNotes = response.data.existingOffers;
getOffers(allNotes);
})
.catch((error) => console.error(`Error: ${error}`));
};
console.log(offers);
const wasteItem = offers?.filter(
(wasteItem) =>
wasteItem.status === "accepted" &&
wasteItem.wasteItemsListId === posts?.wasteItemList?._id,
);
console.log(wasteItem);
}
I call the first API and get a specific post data and this post has an array of objects called wasteItemList. When I use this code console.log(posts.wasteItemList), I get length 2 array of objects. This is an image of this result.
Then I call the second API and get length 8 array of objects. This is an image of this result.
Then I try to filter data using this code const wasteItem = offers?.filter(wasteItem => wasteItem.status==='accepted' && wasteItem.wasteItemsListId===posts?.wasteItemList?._id). But this filter function give an empty array. What is the reason for this problem? How do I solve this?
As I mentioned in the comments, your naming seems a little off. A function that supposedly gets one post assigns to a state atom that's plural, and your setter for offers is getOffers.
Here's a simplification/rewrite of your component that assumes post is supposed to be singular and offers is in plural. Also, you were missing the data dependency postId for the useEffect.
In addition, since wasteItem is singular, I assume you want the first matching offer, not all of them, so .find() is the thing.
function ViewPost() {
const { postId } = useParams();
const [post, setPost] = useState(undefined);
const [offers, setOffers] = useState(undefined);
useEffect(() => {
setPost(undefined);
axios
.get(`/buyerGetOnePost/${postId}`)
.then((resp) => setPost(resp.data.onePost))
.catch((err) => console.error(err));
}, [postId]);
useEffect(() => {
axios
.get(`/viewPendingSellerOffers`)
.then((response) => setOffers(response.data.existingOffers))
.catch((err) => console.error(err));
}, []);
if (post === undefined || offers === undefined) {
return <>Loading...</>;
}
const wasteItem = offers.find(
(wasteItem) =>
wasteItem.status === "accepted" &&
wasteItem.wasteItemsListId === post.wasteItemList?._id,
);
return (
<div>
<div>Post: {JSON.stringify(post)}</div>
<div>Offers: {JSON.stringify(offers)}</div>
<div>Waste Item: {JSON.stringify(wasteItem)}</div>
</div>
);
}
As described in comments between my code snippet, the asynchronicity is not working as expected. For each id, an object/item should return but it only returns one item since my async await isn't implemented properly. What could be a possible workaround?
Thanks in advance
useEffect(() => {
axios.get('url-here').then((res) => {
res.data.favProperties?.map((el) => {
console.log(el) // this returns multitple id's of saved/liked items
axios.get('url-here').then(async (r) => {
if (r.data) {
console.log(r.data) // Problem starts here
// This returns the full object of the liked items
// But only one object is returned, not every object for which an id was stored
await storageRef
.child(r.data.firebaseRef + '/' + r.data.images[0])
.getDownloadURL()
.then((url) => {
// Here i need to fetch the image for each object
console.log(url)
})
.catch((err) => console.log(err))
}
})
})
})
}, [])
I think breaking down your operations into functions will prevent this Promise Hell. I would recommend using async await for these kinda operations. Also I was confused about the last part of console logging the download URL, by my guess you're trying to save all the download URLs for these liked items in an array.
useEffect(() => {
firstFunction();
}, []);
const firstFunction = async () => {
const { data } = await axios.get("url-here");
const favProperties = data.favProperties;
const fetchedUrls = await Promise.all(
favProperties?.map(async (el) => (
await secondFunction(el.id) /** use el to pass some ID */
))
);
};
const secondFunction = async (someId) => {
/** your second URL must point to some ID (or some parameters) specific API otherwise
running same op in a loop without variations doesn't make any sense */
const { data } = await axios.get(`some-other-url/${someId}`);
if (data) {
console.log(data);
const fetchedUrl = await storageThing(data);
return fetchedUrl;
}
};
const storageThing = async ({ firebaseRef, images }) => {
try {
const downloadURL = await storageRef
.child(firebaseRef + "/" + images[0])
.getDownloadURL();
console.log(downloadURL);
return downloadURL;
} catch (error) {
console.log(error);
return '';
}
};
I am pretty new to building full-stack applications, and I could like to avoid duplicating code in order to build the following to perform the calls in react my endpoints can be called like the following /api/v1/feeds/list/?page=${page} or api/v1/feeds/list/?search=${query} , but I would like to joing ?page=${page}&?search=${query} since search param is optional . I just want to make a single api call
async function fetchFeed(page) {
return api.get(`http://localhost:8001/api/v1/feeds/list/?page=${page}`);
}
async function searchQuery(query) {
return api.get(`http://localhost:8001/api/v1/feeds/list/?search=${query}`);
}
const Main = () => {
const [currentPage, setCurrentPage] = useState(1);
const [feed, setFeed] = useState([]);
const [feedCount, setfeedCount] = useState(0);
const [visible, setVisible] = useState(3)
const showMoreItems = () => {
setVisible(prevValue => prevValue + 3);
}
const browse = (page) => {
fetchFeed(page)
.then(function(response){
setfeedCount(response.data.count)
setFeed(response.data.results)
})
.catch(function(error){
console.log(error);
});
}
// fetches data
const fetchData = (search) => {
searchQuery(search)
.then((response) => {
setFeed(response.data.results)
})
.catch((error) => {
console.log(error);
});
};
const handleSearch = (e) =>{
fetchData(e.target.value);
}
useEffect(() => {
browse(currentPage)
fetchData(feed);
}, [currentPage]);
}
I'd pass an object with both page and query, which both default to the empty string - and if empty, don't include them in the fetched URL:
async function fetchFeed({ page = '', query = '' }) {
return api.get(`http://localhost:8001/api/v1/feeds/list/?${page ? `page=${page}&` : ''}${query ? `search=${query}` : ''}`);
}
If possible, make your API accept empty query parameters too, allowing you to simplify to
return api.get(`http://localhost:8001/api/v1/feeds/list/?page=${page}&query=${query}`);
Something like this should work for you
const fetchFeed = async (page, query) => {
let url =`http://localhost:8001/api/v1/feeds/list/?page=${page}`
if(query) url += `?search=${query}`
return api.get(url)
}
const browse = (page search) => {
await fetchFeed(page search)
.then(function(response){
!search && setfeedCount(response.data.count)
setFeed(response.data.results)
})
.catch(function(error){
console.log(error);
});
}
useEffect(() => {
browse(currentPage) // just pass page
browse(currentPage, searchQuery); // pass both page and search query
}, [currentPage]);
Beginner with react and struggling with something that I am sure is probably very simple. I am just trying to make a simple component that will fetch data and display a part of it in a div. I am able to get the data and print it to console, but I am having trouble saving to a variable and displaying it. Here is my code (removed the actual url for privacy reasons):
let x = -1;
function getData(apiUrl){
fetch(apiUrl, {credentials: 'same-origin'})
.then((response) => {
if (!response.ok) {
Logging.error(`Did not get an ok. got: ${response.statusText}`);
}
return response.json();
})
.then(json => {x = json.value})
.catch((error) => {
Logging.error(`Error getting ad data: ${error.message}`);
})
}
const MyPage = () => {
getData('my endpoint')
return (
<div>{x}</div>
);
}
My issue is when I load the page it always displays my default value of "-1". So either x is never getting re-assigned, or the return is happening before it does.
Other commenters about setting state is not wrong.
However, you are also not exactly wrong, expecting a value for x.
Your getData function calls fetch, however you did not return anything from fetch. If you want to use x = getData(), you will need to ensure to add a return before the fetch function in order to return the data.
const getData = (apiUrl) => {
return fetch(apiUrl, {credentials: 'same-origin'})
.then((response) => {
if (!response.ok) {
Logging.error(`Did not get an ok. got: ${response.statusText}`);
}
return response.json();
})
.then(json => {x = json.value})
.catch((error) => {
Logging.error(`Error getting ad data: ${error.message}`);
})
}
let x = await getData(apiUrl)
However, fetch is asynchronous so it's you need to use x = await getData().
You cannot use await outside an async function, so you need to use effect, and useState to properly render the data you want.
import React, { useEffect, useState } from 'react';
const MyPage = () => {
const [ data, setData ] = useState();
useEffect(() => {
getData(apiUrl);
},[])
const getData = async (apiUrl) => {
fetch(apiUrl, {credentials: 'same-origin'})
.then((response) => {
if (!response.ok) {
Logging.error(`Did not get an ok. got: ${response.statusText}`);
}
return response.json();
})
.then(json => setData(json)) //setData here
.catch((error) => {
Logging.error(`Error getting ad data: ${error.message}`);
})
}
return (<pre>{ JSON.stringify(data, null, 4)}</pre>)
}
You need to use JSON.stringify to show your JSON results in your return statement.
You need to you use the state in react. Try something like:
import react, { useState, useEfect } from 'react';
const MyPage = () => {
const [data, setData] = useState(null);
const useEfect(() => {
const result = getData('my endpoint');
setData(result);
}, []);
return (
<div>{data}</div>
);
}
I'm trying to get the data of "body" outside of the fetchUserData() function.
I just want to store it in an variable for later use.
Also tried modifying state, but didn't work either.
Thanks for your help :)
const [userData, setUserData] = useState();
async function fetchUserData () {
try {
const result = await fetch(`/usermanagement/getdocent`, {
method: "GET"
});
const body = await result.json();
//setUserData(body);
return(
body
)
} catch (err) {
console.log(err);
}
}
let userTestData
fetchUserData().then(data => {userTestData = data});
console.log(userTestData);
//console.log(userData);
Use useEffect
async function fetchUserData () {
try {
const result = await fetch(`/usermanagement/getdocent`, {
method: "GET"
})
return await result.json()
} catch (err) {
console.log(err)
return null
}
}
const FunctionalComponent = () => {
const [userData, setUserData] = useState()
useEffect(() => {
fetchUserData().then(data => {
data && setUserData(data)
})
}, []) // componentDidMount
return <div />
}
Ben Awad's awesome tutorial
Example:
it seems that you are making it more complicated than it should be. When you get the response i.e the resolved promise with the data inside the async function, just set the state and in the next render you should get the updated data.
Example:
const [userData, setUserData] = useState();
useEffect(() => {
const getResponse = async () => {
try {
const result = await fetch(`/usermanagement/getdocent`, {
method: "GET"
});
const body = await result.json();
setUserData(body);
} catch (err) {
console.log(err)
}
}
getResponse();
}, [])
console.log(userData);
return <div></div>
Assuming the you need to call the function only once define and call it inside a useEffect or 'componentDidMount'. For using async function inside useEffect we need to define another function and then call it.
When you do
let userTestData
// This line does not wait and next line is executed immediately before userTestData is set
fetchUserData().then(data => {userTestData = data});
console.log(userTestData);
// Try changing to
async someAsyncScope() {
const userTestData = await fetchUserData();
console.log(userTestData)
}
Example:
state = {
someKey: 'someInitialValue'
};
async myAsyncMethod() {
const myAsyncValue = await anotherAsyncMethod();
this.setState({ someKey: myAsyncValue });
}
/*
* Then in the template or where ever, use a state variable which you update when
* the promise resolves. When a state value is used, once the state is updated,
* it triggers as a re-render
*/
render() {
return <div>{this.state.someKey}</div>;
}
In your example you'd use setUserData instead of this.setState and userData instead of {this.state.someKey}