react select load async options does not load - reactjs

I want to load options from backend. So i have to fetch data from API and then update options.
But i don't know how to do it. Can someone help? Here's my code:
function myComponent() {
const loadOptions = () => {
console.log('on load options function')
axios.get(`/api/admin/roles`)
.then((response) => {
const options = []
response.data.permissions.forEach((permission) => {
options.push({
label: permission.name,
value: permission.id
})
})
return options
})
}
return (
<AsyncSelect
isMulti
cacheOptions
loadOptions={loadOptions}
/>
)
}
By the way nothing gets logged at all and that means the loadOptions function does not run. Here's my response from API:
response: {
data: {
permissions: [{
id: 13,
name: 'deposit'
}, {
id: 14,
name: 'withdraw'
}]
}
}

The issue you're experiencing seems to be due to the fact that you're not returning anything at the top-level of the loadOptions function.
The documentation highlights two ways to define your loadOptions function.
Using callbacks:
const loadOptions = (inputValue, callback) => {
setTimeout(() => {
callback(options);
}, 1000);
};
Using promises:
const loadOptions = inputValue =>
new Promise(resolve => {
setTimeout(() => {
resolve(options);
}, 1000);
});
In your case, it might be simplest to try the callback option first since your existing syntax is conducive with it.
const loadOptions = (inputValue, callback) => {
console.log('on load options function')
axios.get(`/api/admin/roles`)
.then((response) => {
const options = []
response.data.permissions.forEach((permission) => {
options.push({
label: permission.name,
value: permission.id
})
})
callback(options);
})
}
In the future you can optionally leverage the inputValues parameter to down-filter results.

Your loadOptions function must return a promise. Also you can pass defaultOptions as true to make the request fire for initial set of options
const loadOptions = () => {
console.log('on load options function')
return axios.get(`/api/admin/roles`)
.then((response) => {
const options = []
response.data.permissions.forEach((permission) => {
options.push({
label: permission.name,
value: permission.id
})
})
return options
})
}
function myComponent() {
return (
<AsyncSelect
isMulti
cacheOptions
defaultOptions
loadOptions={loadOptions}
/>
)
}
P.S For performance reasons, you can declare your loadOptions function outside of the component so that it doesn't get recreated on every re-render

AsyncSelect expects a defaultOptions prop, which you have not provided. The docs are unclear about what behavior it should exhibit in this case, but I'd guess it defaults to loading on filter.

Try this
const loadOptions = async () => {
const response = await axios.get(`/api/admin/roles`)
const result = await response.data
return await result.permissions.map((permission) => ({
label: permission.name,
value: permission.id
}))
}

Related

Pausing react query and re-fetching new data

I have a useQuery which is disabled in a react function component. I have another useQuery that uses mutate and on the success it calls refetchMovies(). This all seems to work well but I'm seeing old data in the refetchMovies. Is there a way for to get the refetchMovies to always call fresh data from the server when its called ?
const MyComponent = () => {
const {data, refetch: refetchMovies} = useQuery('movies', fetchMovies, {
query: {
enabled: false
}
})
const {mutate} = useQuery('list', fetchList)
const addList = useCallback(
(data) => {
mutate(
{
data: {
collection: data,
},
},
{
onSuccess: () => refetchMovies(),
onError: (error) => console.log('error')
}
)
},
[mutate, refetchMovies]
)
return (
<div onClick={addList}> {data} </div>
)
}
Try to invalidate the query in your onSuccess callback instead of manually refetching it:
https://tanstack.com/query/v4/docs/react/guides/query-invalidation
Example:
// Invalidate every query with a key that starts with `todos`
queryClient.invalidateQueries({ queryKey: ['todos'] })

React does not rerender on updated state of nested array

I have an array of objects like so:
const [categories, setCategories] = React.useState([
{
id: 1,
title: 'Top Picks',
subTitle: "Today's hottest stuff",
images: [],
searchQuery: 'shoes',
},
...]);
Which I update with values in useEffect once like so:
React.useEffect(() => {
const newCategories = categories.map(category => {
fetch(`https://api.pexels.com/v1/search?query=${category.searchQuery}&per_page=10`, {
headers: {
'Authorization': apiKey,
},
}).then(r => {
r.json().then(convertedJson => {
category.images = convertedJson.photos;
});
});
return category;
});
setCategories(newCategories);
}, []);
however the child components here never rerender and I cannot figure out why. My understanding is that .map creates a new array anyhow, so the spread syntax isn't necessary in setCategories() but regardless it does not work.
{categories.map((category, i) => (
<CategorySlider {...category} key={i}/>
))}
There's a few issues but the primary issue I see is you're returning the category before the fetch can complete - so even when those fetch calls inside your map complete, you already returned the category below before the fetch completes.
Try using the .finally() block:
React.useEffect(() => {
const newCategories = categories.map(category => {
const c = {...category}; // <--- good practice
fetch(`https://api.pexels.com/v1/search?query=${category.searchQuery}&per_page=10`, {
headers: {
'Authorization': apiKey,
},
}).then(r => {
r.json().then(convertedJson => {
category.images = convertedJson.photos;
});
}).catch((err) => {
console.error(err);
}).finally(() => {
return category;
});
});
setCategories(newCategories);
}, []);
Thanks! Using setState before the promises resolved was indeed the problem. The solution looks like this now:
React.useEffect(() => {
async function fetchImages() {
const promises = categories.map(async category => {
const response = await fetch(
`https://api.pexels.com/v1/search?query=${category.searchQuery}&per_page=10`,
{
headers: {
Authorization: apiKey,
},
}
);
const convertedJson = await response.json();
category.images = convertedJson.photos;
return category;
});
setCategories(await Promise.all(promises));
}
fetchImages();
}, []);

Await not working when pass async function as params LODASH

I created a custom function with params using callback to reuse, but await not working
const _debounceSearch = debounce(
async (callback: (params: RequestParams) => any, value: string | undefined, language: string | undefined) => {
const data = await callback({ q: value, lang: language });
console.log('data on search', data);
return data;
},
300,
);
const dataCompanies = await _debounceSearch(apiSearchCompany, searchValue, languageParam);
console.log('dataCompanies', dataCompanies);
if (dataCompanies) {
setCompanySearchTags(
data.items.map((item: Company) => ({ value: item.id, label: item.name, checked: false })),
);
}
I only got undefined result although i pass await prefix.
I don't think debounce function itself can be a promise. debounce(fn) is just a regular function, but you can make a Promise whenever you want.
Maybe checkout, Using lodash debounce to return a promise

Redux async call with then does not wait for api response

I'm defining a Redux service to call an api endpoint:
export const TrackersApi = {
getBasicsTrackers: async (): Promise<ReturnType<typeof recreator>> => {
const url = "/api/getbasictrackers"
const {data, statusText} = await axios.get(url, { withCredentials: true });
if(statusText !== 'OK' && statusText !== 'No Content') throw new Error('Wrong response from getbasictrackers')
const result = recreator(data)
console.log({result})
return result
},
The log returns the json response.
Then I inject this in a component on mount:
componentDidMount = () => {
store.dispatch(getBasicTrackers()).then(() => {
if(this.props.trackers) {
this.setState({
sortedAndFilteredTrackers : this.props.trackers
})
}
if(this.props.folders) {
this.setState({
sortedAndFilteredFolders: this.props.folders
})
}
console.log('trackers', this.props.trackers)
})
}
However the log here returns an empty array. I tried first without the then and I had the same issue.
How can I make it so that the setState is called only once the API response is received?
Additional info: This props is then used to fill in a table. However the table remains empty, which is the key issue here
It is mapped through this:
const mapStateToProps = (state: ReduxStore.State) => ({
trackers: state.trackersData.rawTrackers ? Object.values(state.trackersData.rawTrackers).map(item => ({...item, checked: false})) : [],
folders: state.trackersData?.folders ? Object.values(state.trackersData.folders).map((folder: any) => ({...folder.summary, checked: false})) : []
})

how to call reactQuery refetch in input onchange event in reactjs

In my React application, I need to call does exit API. API call should happen when change event in the input for that, I am using the reactQuery refetch to do that.
I had tried with below code
const [createObj, setCreateObj] = useState(mCreateObj);
const { data: doexit, refetch: doexitRefetch } = useQuery('getDoexit', () => api.doexitAPI(createObj.c_name), { enabled: false });
const handleInput = ({ target: { name, value } }) => { setCreateObj(state => ({ ...state, [name]: value }), []); }
export const doexitAPI= (value) => axios.get(/doexist/${value}, { headers: setHeader }).then(res => res);
useEffect(() => { console.log(createObj) doexitRefetch(); }, [createObj.mx_name])
How to call in input onchange event
You can invalidate your query and handle fetch data again with query keys.
https://react-query.tanstack.com/guides/query-keys#if-your-query-function-depends-on-a-variable-include-it-in-your-query-key
const { data: doexit, refetch: doexitRefetch } = useQuery(['getDoexit', createObj.mx_name], () => api.doexitAPI(createObj.c_name), { enabled: false });

Resources