Await not working when pass async function as params LODASH - reactjs

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

Related

How to re-run useEffect after a submit function?

Hello Guys!
So in my Project, I do a fetch data function in useeffect but when I add a new element to the firestore I want that the useEffect to run again so in the list will contain the added element, somebody can give me advice on how can I do it ?
useEffect(() => {
if (session) {
fetchTodos();
}
}, [session]);
const fetchTodos = async () => {
const fetchedtodos = [];
const querySnapshot = await getDocs(collection(db, session.user.uid));
querySnapshot.forEach((doc) => {
return fetchedtodos.push({ id: doc.id, data: doc.data() });
});
setTodos(fetchedtodos);
};
const submitHandler = async (todo) => {
const data = await addDoc(collection(db, session.user.uid), {
todo,
createdAt: serverTimestamp(),
type: "active",
});
}
I want that when I run the submitHandler the useeffect run again so the list will be the newest
The only way to get a useEffect hook to run again, is to change something in the dependency array, or to not provide an array at all, and get the component to re-render (by changing props or state). See useEffect Documentation
You could just call fetchTodos directly after you call addDoc:
const submitHandler = async (todo) => {
const data = await addDoc(collection(db, session.user.uid), {
todo,
createdAt: serverTimestamp(),
type: "active",
});
return fetchTodos();
}
In my experience, the best way to do what you are trying to do is to have any requests that modify your data in the backend return the difference and then modify your state accordingly:
const submitHandler = async (todo) => {
const data = await addDoc(collection(db, session.user.uid), {
todo,
createdAt: serverTimestamp(),
type: 'active',
});
setTodos((prev) => [...prev, data]);
};
This way you don't have to do any large requests for what is mostly the same data within the same session.
Of course, this method is not ideal if multiple clients/users can modify your backend's data, or if you do not control what the endpoint responds with.
Hope this helps.

Recoil Async data request with atomFamily

I'm using an atomFamily with a default value of a selectorFamily to get some order data:
export const orderState = atomFamily<Order | undefined, string>({
key: 'orderFamily',
default: selectorFamily({
key: 'orderSelectorFamily',
get:
orderId =>
async ({ get }) => {
try {
const response = await getOrder(orderId);
return response.data;
} catch (e) {
console.log('error', e);
}
},
}),
});
This is used when the page loads and id is captured from the URL and used in a React component:
export const useGetOrderValue = (orderId: string) => {
return useRecoilValue_TRANSITION_SUPPORT_UNSTABLE(orderState(orderId));
};
And in the Component
const order = useGetOrderValue(id);
I also need to be able to get the order data from an order search that'll then redirect to the order page. So I'm getting the order data from a request and setting it manually using a useRecoilCallback function:
const getOrder = useRecoilCallback(
({ set }) =>
async (orderId: string) => {
try {
const response = await requestGetOrder({ orderId });
set(orderState(orderId), response.data);
} catch (e) {
console.log('error', e);
}
},
[],
);
It all seems to work fine but I feel like I'm duplicating effort within the useRecoilCallback. Is there a better way to do this?

Async/await not working in a for-of loop defined in createAsyncThunk

I'm having trouble trying to get an async await to work inside a for loop when using createAsyncThunk. I expected that dispatch(performRunAllCells()) will call the API updateBrowser() synchronously for each cell in the editor.cells array in order. Instead, the dispatch resulted in updateBrowser() being called asynchronously all at once. What is happening here?
export const performRunAllCells = createAsyncThunk(
'editor/runAllCells',
async (_, { dispatch, getState, rejectWithValue }) => {
const { notebook: { selectedDataset } } = getState() as {notebook: {selectedDataset: string}};
const { editor } = getState() as {editor: EditorState};
try {
let results: DataEntity | undefined;
for (const cell of editor.cells) {
dispatch(setCellStatus({ id: cell.id, execStatus: '*' }));
results = await updateBrowser(selectedDataset, cell.editorContent);
dispatch(setCellStatus({ id: cell.id }));
}
return results;
} catch (e) {
return rejectWithValue(e.response.data);
}
},
);
Edit
Currently I'm testing updateBrowser() with a setTimeout:
export async function updateBrowser(selectedDataset: string, editorContent: string): Promise<DataEntity> {
return new Promise((resolve) => {
setTimeout(() => {
console.log('test');
resolve({
raw: editorContent, html: 'Test', console: 'Test',
});
}, 3000);
});
}
I was able to know if it's synchronous/asynchronous through the console log above. Currently, it is printing multiple "test" at once.
Nevermind. I made a mistake somewhere else and the code wasn't actually being called. It is working now after fixing it.

react select load async options does not load

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
}))
}

Testing Observable epic which invokes other epic

I am trying to test a Redux Observable epic which dispatches an action to invoke an other epic. Somehow the invoked epic is not called.
Lets say my epics looks like this;
const getJwtEpic = (action$, store, { api }) =>
action$.ofType('GET_JWT_REQUEST')
.switchMap(() => api.getJWT()
.map(response => {
if (response.errorCode > 0) {
return {
type: 'GET_JWT_FAILURE',
error: { code: response.errorCode, message: response.errorMessage },
};
}
return {
type: 'GET_JWT_SUCCESS',
idToken: response.id_token,
};
})
);
const bootstrapEpic = (action$, store, { api }) =>
action$.ofType('BOOTSTRAP')
.switchMap(() =>
action$.filter(({ type }) => ['GET_JWT_SUCCESS', 'GET_JWT_FAILURE'].indexOf(type) !== -1)
.take(1)
.mergeMap(action => {
if (action.type === 'GET_JWT_FAILURE') {
return Observable.of({ type: 'BOOTSTRAP_FAILURE' });
}
return api.getProfileInfo()
.map(({ profile }) => ({
type: 'BOOTSTRAP_SUCCESS', profile,
}));
})
.startWith({ type: 'GET_JWT_REQUEST' })
);
When I try to test the bootstrapEpic in Jest with the following code;
const response = {};
const api = { getJWT: jest.fn() };
api.getJWT.mockReturnValue(Promise.resolve(response));
const action$ = ActionsObservable.of(actions.bootstrap());
const epic$ = epics.bootstrapEpic(action$, null, { api });
const result = await epic$.toArray().toPromise();
console.log(result);
The console.log call gives me the following output;
[ { type: 'GET_JWT_REQUEST' } ]
Somehow the getJwtEpic isn't called at all. I guess it has something to do with the action$ observable not dispatching the GET_JWT_REQUEST action but I can't figure out why. All help is so welcome!
Assuming actions.rehydrate() returns an action of type BOOTSTRAP and the gigya stuff is a typo,
getJwtEpic isn't called because you didn't call it yourself 🤡 When you test epics by manually calling them, then it's just a function which returns an Observable, without any knowledge of the middleware or anything else. The plumbing that connects getJwtEpic as part of the root epic, and provides it with (action$, store) is part of the middleware, which you're not using in your test.
This is the right approach, testing them in isolation, without redux/middleware. 👍 So you test each epic individually, by providing it actions and dependencies, then asserting on the actions it emits and the dependencies it calls.
You'll test the success path something like this:
const api = {
getProfileInfo: () => Observable.of({ profile: 'mock profile' })
};
const action$ = ActionsObservable.of(
actions.rehydrate(),
{ type: 'GET_JWT_SUCCESS', idToken: 'mock token' }
);
const epic$ = epics.bootstrapEpic(action$, null, { api });
const result = await epic$.toArray().toPromise();
expect(result).toEqual([
{ type: 'GET_JWT_REQUEST' },
{ type: 'BOOTSTRAP_SUCCESS', profile: 'mock profile' }
]);
Then you'll test the failure path in another test by doing the same thing except giving it GET_JWT_FAILURE instead of GET_JWT_SUCCESS. You can then test getJwtEpic separately as well.
btw, ofType accepts any number of types, so you can just do action$.ofType('GET_JWT_SUCCESS', 'GET_JWT_FAILURE')

Resources