How to stop variable re initializing in context - reactjs

I have a variable in my context with the base url for my api and i add parameters to that base call to add parameters, the problem that i have is that i can't concatenate more than one parameter at a time, so if for example set my checkbox for one brand the url updates to that brand filter and the function adds the brand filter parameter, but if i search something the url re-initialites itself and deletes the other parameter.
Some images for more clarity:
If i make a search:
The url console logs like this, the line 87 logs the url before i concatenate the parameter and the 91 after i concatenate the parameter:
And if i check the Nike brand that should only show the Nike Air Yeezy product and url should be "http://localhost:5000/api/v1/products?name=Yeezy&company=Nike" is only "http://localhost:5000/api/v1/products?company=Nike&"
My theory is that as i declarate the base url parameter every time i do one function the context resets and that url var re initializes, but how can i stop this? I searched at how to make a string state and concatenating that state but couldn't find anything.
Here is the part of the context where i handle the new api calls, if something seems weird/missing is because i cut the context to make it more clear
var url = "/api/v1/products?";
const [productList, setProductList] = useState([]);
const [brandFilter, setBrandFilter] = useState([]);
const [search, setSearch] = useState({
text: "",
});
const getProductList = async () => {
const response = await fetch(url);
const responseJson = await response.json();
if (responseJson) {
setProductList(responseJson.products);
}
};
useEffect(() => {
getProductList();
}, []);
useEffect(() => {
setSearchUrl();
getProductList();
}, [search]);
const setBrandUrl = () => {
console.log(url);
if (brandFilter.length > 0) {
for (var i = 0; i < brandFilter.length; i++) {
url += `company=${brandFilter[i]}&`;
}
}
console.log(url);
};
const setSearchUrl = () => {
console.log(url);
if (search.text != "") {
url += `name=${search.text}`;
}
console.log(url);
};
useEffect(() => {
setBrandUrl();
getProductList();
}, [brandFilter]);

It's because you've got the useEffect split into two; one which is reacting to the search changing, and one which is reacting to the brands changing. You need to combine those into one.
If we look at the effect for when the search changes, and we reduce the code to what's actually running, your effect is doing this:
useEffect(() => {
var url = "/api/v1/products?";
if (search.text != "") {
url += `name=${search.text}`;
}
getProductList();
}, [search]);
i.e. it ignores brandsFilter completely. You need to have both the brandsFilter and the search as a dependency for a single effect:
useEffect(() => {
let url = "/api/v1/products?";
if (search.text) {
url += `name=${search.text}`;
}
if (brandsFilter.length) {
const brands = brandsFilter.map(b => `&brand=${b}`).join('');
url += search.text ? brands : brands.substr(1);
}
// NOTE: pass the 'url' to the get as a parameter here!
getProductList(url);
}, [search, brandsFilter]);
Note: this is easier if you use a library like query-string to do the heavy lifting for you:
import qs from 'query-string';
useEffect(() => {
const url = qs.stringifyUrl({
url: "/api/v1/products?",
query: {
name: search.text || undefined,
brands: brandsFilter,
}
});
getProductList(url);
}, [search, brandsFilter]);

Related

Check setState has run before executing API call in React

I have a form with a text field and a form input which accepts multiple files. OnSubmit the files are sent to Firebase Storage, which sends back a URL for each file. These URLs are then stored in a 'photosURL' array in the form object, which is then posted to MongoDB.
The problem is, every time I post the form object data to Mongo, the photos array is empty, despite the console log showing it to be populated before I call the post-to-Mongo code. This leads me to think the post-to-Mongo code is using the form object value before it has been populated with the photo URLs.
The question is, how do I check that the photo array has been populated before I run the code to push the data to MongoDB? I'm already using a Promise.all to in theory wait for all the files to be sent and the URLs returned, but I can't work out why else the photoURLs array is empty every time data is sent to Mongo.
Here's the code:
const [form, setForm] = useState({
userId: '',
post: '',
createdAt: createdAt,
photoURLs: [],
})
const handleSubmit = (e) => {
e.preventDefault()
newPost ? postData(form) : ...
}
// SEND FILE TO FIREBASE AND GET BACK THE URL
async function handleUpload(file) {
const storageRef = useStorage.ref("PostImages");
const fileRef = storageRef.child(`${nanoid()}`);
return fileRef.put(file).then(() => {
return fileRef.getDownloadURL().then(function (url) {
photoArray.push(url);
setForm(prevState => ({ ...prevState, photos: photoArray }))
});
});
}
// POST FUNCTION
const postData = async (form) => {
setLoading(true)
let thisFileArray = fileInput.current.files;
const uploadTasks = [];
for (let i = 0; i < thisFileArray.length; i++) {
uploadTasks.push(handleUpload(thisFileArray[i]));
}
Promise.all(uploadTasks).then(() => {
axios.post('/api/posts', form)
.then(response => {
...
})
.catch(error => {
...
})
})
}
Can anyone see what's going wrong, please?
EDIT: This is a consolel log of the form object, called before the axios.post code (it's showing the photosURL as populated):
createdAt: 1630072305502
photos:
0: "https://firebasestorage.googleapis.com/..."
1: "https://firebasestorage.googleapis.com/..."
post: "sample text"
userId: "1iNGV..."
I think that you are running into a timing issue.
Don't forget that React state updates are asynchronous, as described here.
I suggest to pass your URLs directly instead of going through your component's state:
async function handleUpload(file) {
const storageRef = useStorage.ref("PostImages");
const fileRef = storageRef.child(`${nanoid()}`);
await fileRef.put(file);
const url = await fileRef.getDownloadURL();
return url; // Send back the download URL
}
const postData = async (form) => {
setLoading(true);
let thisFileArray = fileInput.current.files;
const uploadTasks = [];
for (let i = 0; i < thisFileArray.length; i++) {
uploadTasks.push(handleUpload(thisFileArray[i]));
}
const photos = await Promise.all(uploadTasks); // Get all URLs here
await axios.post('/api/posts', {...form, photos}); // Send URLs to your server
setLoading(false);
}
If I understood correct, You want to upload files first and when you get your urls array populated then only you want to call postData function?
If that's the case, then you can use useEffect to detect the urls change.
useEffect(() => {
// Here you call your postData function.
postData();
}, [form.photoURLs])
What this will do is, Whenever your form.photoURLs gets populated this useEffect will run and will make the request to the server with proper data.

Get or save axios.get response to setState realtime when button clicked

I'm working on edit/update function and what I want to do is to save the response of axios.get to setState right after I click the a specific row from the table.
const [dataSystem, setdataSystem] = useState([])
const [systemDetails, setsystemDetails] = ({
ID: '',
.....,
})
const getAllSystems = async() => {
......
}
const getDependentSystems = async() => { //this is being triggered by a handleselectedSysDep when a row selected
const response = await axios.get('/API' + ID) //ID is being passed by handleselectedSysDep from the selected row
console.log('LIST OF SYSTEM', response.data)
setdataSystem(response.data)
}
const [selectedSystemID, setselectedSysID] = useState('');
let selectedSysID;
const handleselectedSysDep = (ID, action) => { //will be triggered when a row selected
setsystemDetails(ID);
selectedSysID = {...selectedSystemID, 'ID': ID.ID}
getDependentSystems();
}
useEffect(() => {
getAllSystems ();
})
What I want is when getDependentSystems() was called, setdataSystem will be populated right away to display the response data of the API since I'm doing edit. Hope you can help me with this.
Here's the result of console.log('LIST OF SYSTEM', response.data)
Thank you
This looks like a good use-case to use the useEffect hook to watch for changes in systemDetails and execute your getDependentSystems() side-effect.
For example
const [dataSystem, setDataSystem] = useState([])
const [systemDetails, setSystemDetails] = ({
ID: '',
//.....,
})
const handleselectedSysDep = (ID, action) => {
setSystemDetails(ID); // assuming `ID` is an object
}
// Now add an effect to watch for `systemDetails` changes
useEffect(() => {
if (systemDetails.ID) {
axios.get(`/API/${systemDetails.ID}`).then(({ data }) => setDataSystem(data))
}
}, [ systemDetails ]) // 👈 note the dependencies array
You can also use this answer to only fire the useEffect hook after the initial render.
FYI, if you only want getAllSystems() to execute once when your component is mounted, you should use
useEffect(() => {
getAllSystems()
}, []) // 👈 note the empty array

How to pass a dynamic value to getStaticPaths

I have problem with sending a dynamic value to getStaticPaths. I want to change the url based on my context. But, is this possible i don't know. If not how can I reorganize my code. Here is the thing that I want to do which I point out below as 'query'. I know I can not use my context values within the getStaticPaths. So, how can I handle it?
export const getStaticPaths = async () => {
const url = `https://www.omdbapi.com/?apikey=[Mykey]=${query}`
const movies = await axios.get(url)
const data = movies.data.Search
const paths = data.map(movie => {
return {
params: {id: movie.imdbID}
}
})
return{
paths,
fallback:false
}
}

how do I perform conditial query params in axios?

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.

String useState Value Is Empty after Storing a Value in an API Response to It

What I am trying to do here is to extract the id (Number) from an API response, parse it into String, and set it to its state variable. However, when I console log it, it gives me an empty string, which is the default value of it. If anyone could give me a suggestion or tell me what's wrong, that would be greatly appreciated. My briefly reproduced code snippet is down below:
const [userId, setUserId] = useState<string>("");
const getMyUserId = async () => {
const { data: objects } = await axios.get("/");
const user_id = objects.objects[0].id.toString();
setUserId(user_id);
console.log("userId", userId); <- output is empty string
};
const getMyCalendarId = async () => {
const url = `/${userId}/cal/calendars`;
const { data: objects } = await axios.get(`${url}`);
const calendar_id = objects.objects[0].id.toString();
setCalendarId(calendar_id);
};
useEffect(() => {
getMyUserId(); <- render every time page is loaded
getMyCalendarId
}, []);
To retrieve the user id you should access data, instead of objects. Like this:
const user_id = data.objects[0].id.toString();
objects is the typing of data, it is not the actual property.

Resources