React Recoil - Call Delete API Endpoint async on Set - javascript

I am just trying to get to grips with React Recoil to replace Redux in my application. I want to be able to Delete a Season record so have set the following:
Selector Family
export const deleteSeason = selectorFamily({
key: "deleteSeason",
set:
() =>
async ({ get, set }, seasonId) => {
return await seasonApi.deleteSeason(seasonId);
},
});
Where seasonApi.deleteSeason carries out the actual API call.
The Select-Family is referenced here:
const setSeasonDelete = useSetRecoilState(deleteSeason());
And called in an async function here:
const onDeleteSeason = async (selectedSeason) => {
setInEditMode(false);
try {
await setSeasonDelete(selectedSeason.Id);
} catch (error) {
deleteSeasonFailNotification("error");
}
};
When the method is called (from a button click) the API Appears to be hit and the DB deletes the record, however the TRY-CATCH drops out with the error:
"Recoil: Async selector sets are not currently supported."
I presume there is a better/correct way of doing what I am trying to do so any help is greatly appreciated.

Related

How to access data outside of a Promise

I'm making a react app that sends an API call to OpenWeather to get the weather data for a city (specified by the user). Here's what the request for that call looks like:
async function getAPI() {
const apiCall = await axios.get(apiLink).then(res => {
res = {
temp : res.data.main.temp - 273.15,
weatherIcon : res.data.weather[0].icon,
windSpeed : res.data.wind.speed
}
return res
});
return apiCall
}
const weatherData = getAPI()
Notice that I try to store the data I want from the API response in a variable called weatherData. That way I can simply call that variable whenever I need, heres an example of HTML code that uses this variable:
<p>
temperature is {weatherData.temp} Celcius
</p>
This results in weatherData.temp simply not showing up on the browser side for some reason. A console.log(weatherData) prints this in the console:
Promise {<pending>}
[[Prototype]]: Promise
[[PromiseState]]: "fulfilled"
[[PromiseResult]]: Object
temp: 29.53
weatherIcon: "04d"
windSpeed: 1.59
[[Prototype]]: Object
How do I extract the data from the promise in a way that allows me to easily refer to said data for use in HTML code?
Answer below is if you are using functional components and react hooks.
You can can go two directions:
Using a try catch block:
const fetchWeather = async () => {
try {
const res = await axios.get(apiLink);
console.log(res);
setWeather(res.data); //Im not sure what the exact response is, but you can access the keys you need.
// you can then set the data you need to your state to render it.
} catch (error) {
// handle error
}
}
Or you can use .then .catch
const fetchWeather = async () => {
axios.get(apiLink)
.then((res) => {
setWeather(res.data); //Im not sure what the exact response is, but you can access the keys you need.
// set the data you need from the respones to your state.
})
.catch((err) => {
// handle error
})
}
In both cases you can just call the function in your useEffect hook.
useEffect(() => {
fetchWeather()
}, [])
In general my preference goes to set the response you get from the Api into the local state (meaning the state of your page/component). And then rendering the state to your jsx.
So if you are using react hooks, your state could look like this:
const [weather, setWeather] = useState({});
Last Edit:
Finally you can just refer to your state within your jsx/html. Assuming your weather state looks like this:
{
temp: '50 degrees'
}
In your JSX you can just refer to it this way:
<>
<div>{weather.temp}</div>
</>

Why can't I memoize in NextJS' getServerSideProps?

I'm using React + NextJS to display a list of products on a category page.
I can get the products just fine using getServerSideProps, but I don't like that it re-requests the product list on each visit to the same page. I'm trying to memoize a function that gets the list, and while that seems to work (meaning there are no errors thrown), the supposedly memoized function is still called on subsequent visits to the same page.
See the code below, and note that the "get category" console log is shown in the terminal window when I revisit a page, and in Chrome's network tools I see a fetch request made by NextJS.
How can I make it cache the result of my getCategory function so it doesn't keep fetching it?
export async function getServerSideProps(context) {
let config = await import("../../config/config");
let getCategory = memoize(async (url) => {
console.log("getting category");
let response = await axios.get(url);
if ( response.status ) {
return response.data;
} else {
return false;
}
});
let response = await getCategory(`${config.default.apiEndpoint}&cAction=getCTGY&ctgyCode=${context.params.code}`);
if ( response ) {
return {
props: {
category: response
}
};
} else {
return {
props: {
category: null
}
};
}
}
This doesn't work becuase nextjs api routes are "serverless", which means the state that memoize is supposed to remember is destroyed after HTTP call.
The serverless solution is to use a separate service for caching, which is accessible from your api route.
Otherwise, you may need to look at using a custom server.

React: String automatically converted to [object promise] when called from another component

I'm developing the front-end for my spring boot application. I set up an initial call wrapped in a useEffect() React.js function:
useEffect(() => {
const getData = async () => {
try {
const { data } = await fetchContext.authAxios.get(
'/myapi/' + auth.authState.id
);
setData(data);
} catch (err) {
console.log(err);
}
};
getData();
}, [fetchContext]);
The data returned isn't comprehensive, and needs further call to retrieve other piece of information, for example this initial call return an employee id, but if I want to retrieve his name and display it I need a sub-sequential call, and here I'm experiencing tons of issues:
const getEmployeeName = async id => {
try {
const name = await fetchContext.authAxios.get(
'/employeeName/' + id
);
console.log((name["data"])); // <= Correctly display the name
return name["data"]; // return an [Object promise],
} catch (err) {
console.log(err);
}
};
I tried to wrap the return call inside a Promise.resolve() function, but didn't solve the problem. Upon reading to similar questions here on stackoverflow, most of the answers suggested to create a callback function or use the await keyword (as I've done), but unfortunately didn't solve the issue. I admit that this may not be the most elegant way to do it, as I'm still learning JS/React I'm open to suggestions on how to improve the api calls.
var output = Object.values(data).map((index) =>
<Appointment
key={index["storeID"].toString()}
// other irrelevant props
employee={name}
approved={index["approved"]}
/>);
return output;
Async functions always return promises. Any code that needs to interact with the value needs to either call .then on the promise, or be in an async function and await the promise.
In your case, you should just need to move your code into the existing useEffect, and setState when you're done. I'm assuming that the employeeID is part of the data returned by the first fetch:
const [name, setName] = useState('');
useEffect(() => {
const getData = async () => {
try {
const { data } = await fetchContext.authAxios.get(
"/myapi/" + auth.authState.id
);
setData(data);
const name = await fetchContext.authAxios.get(
'/employeeName/' + data.employeeID
);
setName(name.data);
} catch (err) {
console.log(err);
}
};
getData();
}, [fetchContext]);
// ...
var output = Object.values(appointmentsData).map((index) =>
<Appointment
key={index["storeID"].toString()}
// other irrelevant props
employee={name}
approved={index["approved"]}
/>);
return output;
Note that the above code will do a rerender once it has the data (but no name), and another later when you have the name. If you want to wait until both fetches are complete, simply move the setData(data) down next to the setName

React Apollo Hooks - Chain mutations

I have two mutations:
const [createRecord, {data}] = useMutation(createRecordQuery); which returns the ID of the newly created record
const [saveValue, {data: dataSave}] = useMutation(saveValueQuery); which save some values on a record
My saveValue mutation requires a record ID. If I open my form on a new record, I don't have this ID yet, so on submit I need to call createRecord first to retrieve the ID and then saveValue to save values on my record.
This simple approach doesn't work:
const onSave = async values => {
if (!recordId) {
// Call createRecord to retrieve recordId (code is simplified here)
const recordId = await createRecord();
}
// Save my values
return saveValue({variables: {recordId, values});
}
But I don't really know how should I deal with the loading and data of the first mutation and wait for it to run the second mutation.
Thanks!
The mutate function returns a promise that resolves to the mutation response data, so you should simply be able to use to achieve what you want.
From the source code:
If you're interested in performing some action after a mutation has
completed, and you don't need to update the store, use the Promise
returned from client.mutate
I'm not sure why this didn't work in your initial tests, but I tried it locally and it works as expected. You should essentially be able to do what you wrote in your question.
I'm not sure if there is a way to postpone execution(as well as we cannot pause <Mutation>). So how about moving second part into separate useEffect?
const [recordId, setRecordId] = useState(null);
const [values, setValues] = useState({});
const onSave = async _values => {
if (!recordId) {
// Call createRecord to retrieve recordId (code is simplified here)
setRecordId(await createRecord());
}
setValues(_values);
}
useEffect(() => {
saveValue({variables: {recordId, values});
}, [recordId, _values]);
Another workaround is utilizing withApollo HOC:
function YourComponent({ client: { mutate } }) {
onSave = async values => {
let recordId;
if (!recordId) {
recordId = await mutate(createRecordQuery);
}
await mutate(saveValueQuery, values);
// do something to let user know saving is done
};
export withApollo(YourComponent);

Using a read-once variable loaded async through react-hook

I'm currently playing around with Reacts hooks but currently I'm stuck at mixing different use-cases.
The following scenario is what I am trying to get working. There should be one hook called useNationsAsync which is retrieving a list of available nations from the server.
Inside the hook I check if the list has already been loaded/stored to the localStorage in order to load it only once.
For the remote-call I use axios' get call together with the await keyword. So this "hook" has to be async. I've implemented it the following:
export async function getNationsAsync({ }: IUseNationsProps = {}): Promise<NationResource[]> {
const [storedNations, setStoredNations] = useLocalStorage<NationResource[]>("nations", null);
if (storedNations == null) {
const nationsResponse = await Axios.get<NationsGetResponse>("/v1/nations/");
setStoredNations(nationsResponse.data.nations);
}
return storedNations;
}
The useLocalStorage-hook is the one which can be found here (only typed for use with TypeScript).
In my final FunctionalComponent I only want to read the nations once so I thought using the useEffect hook with an empty array would be the place to be (as this is mainly the same as componentDidMount).
However, on runtime I get the following error on the first line of my getNationsAsync-hook:
Uncaught (in promise) Invariant Violation: Invalid hook call.
The usage in my FunctionalComponent is:
const [nations, setNations] = React.useState<NationResource[]>([]);
const fetchNations = async () => {
const loadedNations = await getNationsAsync();
setNations(loadedNations);
};
React.useEffect(() => {
fetchNations();
}, []);
I know that the issue is for calling useHook inside the method passed to useEffect which is forbidden.
The problem is, that I don't get the right concept on how to use the nations at a central point (a hook sharing the result, not the logic) but only load them once in the components which do need nations.
The hook you are creating manages the state of nations and returns it.
Instead of useState you are using useLocalStorage which, as far as I could read from the source, uses as initial state a localStorage value or the given value (null in your case) if there is no local one.
export const useNations = ():
| NationResource[]
| null => {
const [storedNations, setStoredNations] = useLocalStorage<
NationResource[] | null
>("nations", null);
useEffect(() => {
// If storedNations has a value don't continue.
if (storedNations) {
return;
}
const fetch = async () => {
// Check the types here, Im not sure what NationsGetResponse has.
const nationsResponse = await Axios.get<NationsGetResponse>(
"/v1/nations/"
);
setStoredNations(nationsResponse.data.nations);
};
fetch();
}, []);
return storedNations;
};
Then you can use this hook in any component:
export const MyComponent: React.ComponentType<Props> = props => {
const nations = useNations();
return // etc.
};
You can add async to custom hooks. I think this is what you're looking for.
export function getNations(props) {
const [storedNations, setStoredNations] = useLocalStorage("nations",[]);
async function getNations() {
const nationsResponse = await Axios.get("/v1/nations/");
setStoredNations(nationsResponse.data.nations);
}
useEffect(() => {
if (storedNations.length === 0) {
getNations()
}
}, [])
return storedNations;
}
You can call it like this
function App() {
const nations = getNations()
return (
// jsx here
)
}

Categories