How can I dispatch async data with Vue 3 store - javascript

I am pulling data from an api as async and transferring the payload value returned from this api to the state field in the store with store.dispatch.
But at first this state is empty. When I make a change on the page and render it, the state is filled.
the function I pulled the api
const getTransferredOrder = async () => {
isLoading.value = true;
return await TransferredOrderService.getTransferredOrderSummary()
.then((payload) => {
store.dispatch('GetTransferredList',payload)
return payload;
})
.catch(() => {
return [];
}).finally(() => {
isLoading.value = false
});
}
i call this function first in onmounted
onMounted(async () => {
await getTransferredOrder()
})
my actions,mutations and state js files
actions.js
const actions={
GetTransferredList({commit},payload){
commit('GET_TRANSFERRED_ORDER_LIST',payload)
},
}
export default actions
mutations.js
const mutations={
GET_TRANSFERRED_ORDER_LIST(state,payload){
state.transferredOrderList = payload;
},
}
export default mutations
state.js here
const state={
transferredOrderList:[],
}
export default state
Since the state is empty in the place where I store.dispatch, it is also empty on other pages.
But when I write a small console.log on the page and compile it, the store.state fills the page. What exactly does that have to do with it?

Can you look at console and write errors here.
Why are you returns? Can you change code to ->
const getTransferredOrder = async () => {
isLoading.value = true;
await TransferredOrderService.getTransferredOrderSummary()
.then((payload) => {
store.dispatch('GetTransferredList',payload);
})
.catch(() => {
store.dispatch('GetTransferredList',[]);
}).finally(() => {
isLoading.value = false
});
}

Related

How can I use redux saga function action inside getServerSideProps function of nextjs

in next.js, i want to operate the saga function inside getServerSideProps, it is dispatched to the serverSide store, but there is a problem that it is not shared to the client store when the hydrate action is executed. My guess is that rehydrate runs before takeEvery runs, is it a version issue?
I want to move the asynchronous logic to sagaFunction and handle it.
it's my code
//saga.ts
export function* getProductDetailSaga({ payload }: ReturnType<typeof getProductDetailAction>) {
try {
const { data } = yield* call(getProductDetail, payload);
yield put(setFetchedProductDetails(data.productDetail));
} catch (error) {
console.error(error);
}
}
export default function* productDetailSaga() {
yield all([
takeEvery(getProductDetailAction.type, getProductDetailSaga),
}
//[productId].tsx
export const getServerSideProps = wrapper.getServerSideProps((store) => async (ctx) => {
const { productId } = ctx.query;
const {
osCode,
deviceId,
session,
} = store.getState().user;
store.dispatch(
getProductDetailAction({
osCode,
deviceId,
session,
productId,
})
);
store.dispatch(END);
await store.sagaTask.toPromise();
});
i think END action and sagaTask are not functioning properly.
but this works fine.
export const getServerSideProps = wrapper.getServerSideProps((store) => async (ctx) => {
const { productId } = ctx.query;
const { osCode, deviceId, session } = store.getState().user;
const { data } = await getProductDetail({
osCode,
deviceId,
session,
productId,
});
store.dispatch(setFetchedProductDetails(data.productDetail || null));
});

React context returns promise

I have a todo app. Im trying to use context api(first time). I have add, delete and get functions in context. I can use add and delete but cant return the get response to state. It returns promise if i log; context. Im using async await. I tried almost everything i know but cant solve it. Where is my fault ?
Thank you.
task-context.js
import React, { useReducer } from "react";
import TaskContext from "./task-actions";
import { TaskReducer, ADD_TASK, GET_TASKS, REMOVE_TASK } from "./reducers";
const GlobalState = (props) => {
const [tasks, dispatch] = useReducer(TaskReducer, { tasks: [] });
const addTask = (task) => {
dispatch({ type: ADD_TASK, data: task });
};
const removeTask = (taskId) => {
dispatch({ type: REMOVE_TASK, data: taskId });
};
const getTasks = () => {
dispatch({ type: GET_TASKS });
};
return (
<TaskContext.Provider
value={{
tasks: tasks,
getTasks: getTasks,
addTask: addTask,
removeTask: removeTask,
}}
>
{props.children}
</TaskContext.Provider>
);
};
export default GlobalState;
reducers.js
import taskService from "../Services/tasks-service";
export const ADD_TASK = "ADD_TASK";
export const GET_TASKS = "GET_TASKS";
export const REMOVE_TASK = "REMOVE_TASK";
const addTask = async (data, state) => {
console.log("Adding : " + data.title);
try {
let task = {
title: data.title,
description: data.description,
comment: data.comment,
progress: data.status
};
const res = await taskService.addNewTask(task);
console.log(res);
if (res) {
getTasks();
}
} catch (err) {
console.log(err);
}
return;
};
const getTasks = async () => {
let response = {}
try {
const res = await taskService.loadTasks();
response = res.data
} catch (err) {
console.log(err);
}
return { tasks: response }
};
const removeTask = async (data) => {
try {
await taskService.deleteTask(data.id);
} catch (err) {
console.log(err);
}
};
export const TaskReducer = (state, action) => {
switch (action.type) {
case ADD_TASK:
return addTask(action.data);
case GET_TASKS:
console.log(getTasks());
return getTasks();
case REMOVE_TASK:
return removeTask(action.data);
default:
return state;
}
};
task-actions.js
import React from "react";
export default React.createContext({
addTask: (data) => {},
removeTask: (data) => {},
getTasks: () => {}
});
To start with, you are getting promises returned because you are explicitly returning promises: return addTask(action.data). All your actions are returning promises into the reducer.
A reducer should be a pure function, meaning that it does not have any side effects (call code outside its own scope), or contain any async functionality, and it should return the same data given the same inputs every single time. You've essentially got the workflow back to front.
There's a lot to unpick here so I'm going to provide pseudocode rather than try and refactor the entire service, which you will have a more complete understanding of. Starting with the reducer:
export const TaskReducer = (state, action) => {
switch (action.type) {
case ADD_TASK:
return [...state, action.data];
case GET_TASKS:
return action.data;
case REMOVE_TASK:
return state.filter(task => task.id !== action.data.id);
default:
return state;
}
};
This reducer describes how the state is updated after each action is complete. All it should know how to do is update the state object/array it is in charge of. When it comes to fetching data, calling the reducer should be the very last thing you have to do.
Now on to the actions. The add action is a problem because its not actually returning any data. On top of that, it calls getTasks when really all it ought to do is return one added task (which should be getting returned from await taskService.addNewTask). I would expect that res.data is actually a task object, in which case:
export const addTask = async (data) => {
try {
const task = {
title: data.title,
description: data.description,
comment: data.comment,
progress: data.status
};
const res = await taskService.addNewTask(task);
return res.data;
} catch (err) {
return err;
}
};
Similarly for getTasks, I'm going to assume that await taskService.loadTasks returns an array of task objects. In which case, we can simplify this somewhat:
export const getTasks = async () => {
try {
const res = await taskService.loadTasks();
return res.data;
} catch (err) {
return err;
}
};
Your removeTask action is essentially fine, although you will want to return errors instead of just logging them.
Notice we're now exporting these actions. That is so we can now call them from within GlobalState. We're running into issues with name collision so I've just underscored the imported actions for demo purposes. In reality, it might be better to move all the functionality we did in the last step into your taskService, and import that straight into GlobalState instead. Since that's implementation specific I'll leave it up to you.
import {
TaskReducer,
ADD_TASK,
GET_TASKS,
REMOVE_TASK,
addTask as _addTask,
getTasks as _getTasks,
removeTask as _removeTask,
} from "./reducers";
const GlobalState = (props) => {
const [tasks, dispatch] = useReducer(TaskReducer, { tasks: [] });
const addTask = async (task) => {
const added = await _addTask();
if (added instanceof Error) {
// handle error within the application
return;
};
dispatch({ type: ADD_TASK, data: added });
};
const removeTask = async (taskId) => {
const removed = await _removeTask(taskId);
if (removed instanceof Error) {
// handle error within the application
return;
};
dispatch({ type: REMOVE_TASK, data: taskId });
};
const getTasks = async () => {
const tracks = await _getTracks();
if (tracks instanceof Error) {
// handle error within the application
return;
};
dispatch({ type: GET_TASKS, data: tracks });
};
...
}
Hopefully now you can see how the workflow is supposed to progress. First we call for data from our backend or other API, then we handle the response within the application (for instance, dispatching other actions to notify about errors or side effects of the new data) and then finally dispatch the new data into our state.
As stated at the beginning, what I've provided is essentially pseudocode, so don't expect it to work out of the box.

Test component calling API inside useEffect

I want to test the api call and data returned which should be displayed inside my functional component. I have a component that calls an API when it is first loaded and when certain things change i.e when typing.
I have a useEffect calling the API like so:
useEffect(() => {
const query = queryString;
const apiRequest = async () => {
try {
setIsFetching(true);
const response = await getData(query, page);
setData(response.data);
} catch (error) {
// Do some error
console.log('error');
} finally {
setIsFetching(false);
}
};
apiRequest();
}, [queryString, page]);
getData is an axios function like so:
let getDataRequest;
const getData = (searchQuery = null, page = PAGE, size = PAGE_SIZE) => {
if (getDataRequest) getDataRequest.cancel();
getDataRequest = axios.CancelToken.source();
return axios.get('url_to_api', {
params: {
page,
searchQuery,
size,
},
cancelToken: getDataRequest.token,
});
};
When trying to test this component I am running into the error When testing, code that causes React state updates should be wrapped into act(...):
I have been trying to follow first link also second link third link and many more pages but still no luck.
Here is one version of the test I am still seeing the error:
it('should render data', async () => {
let container;
act(async () => {
await getData.mockResolvedValueOnce({
data: { things: data, meta: { current_page: 1 } },
});
container = render(<Component />);
});
await waitFor(() => container.queryByText('Text I would expect after api call'));
})
I have also tried mocking the function in my test file like so:
import { getData } from './dataAccess';
const mockedData = { data: { things: data } };
jest.mock('./api', () => ({
getData: jest
.fn()
.mockImplementation(() => new Promise(resolve => resolve(mockedData))),
}));
I am using import { act } from 'react-dom/test-utils'; with enzyme and jest. I also am using '#testing-library/react';

How to return an array received from fetching api data in a .then statement?

I'm trying to export an array inside a .then statement but its not working. I have no clue how to make it work otherwise. Actually I'm just trying to set my initial state in redux to this static data I am receiving from the movie database api.
import { API_URL, API_KEY } from '../Config/config';
const urls = [
`${API_URL}movie/popular?api_key=${API_KEY}&language=en-US&page=1`,
`${API_URL}movie/popular?api_key=${API_KEY}&language=en-US&page=2`,
]
Promise.all(urls.map(items => {
return fetch(items).then(response => response.json())
}))
.then(arrayOfObjects => {
var arr1 = arrayOfObjects[0].results;
var arr2 = arrayOfObjects[1].results;
export var movieData = arr1.concat(arr2);
}
)
You can try with a function. like this:
import { API_URL, API_KEY } from '../Config/config';
export const getMovies = () => {
const urls = [
`${API_URL}movie/popular?api_key=${API_KEY}&language=en-US&page=1`,
`${API_URL}movie/popular?api_key=${API_KEY}&language=en-US&page=2`,
]
const promises = urls.map(url => {
return new Promise((reject, resolve) => {
fetch(url).then(res => res.json())
.then(res => resolve(res.results))
})
})
return Promise.all(promises)
}
// other file
import {getMovies} from 'YOUR_API_FILE.js';
getMovies().then(moviesArr => {
// your business logics here
})
It's not clear where this code is in relation to your state/reducer, but ideally you should be using action creators to deal with any API calls and dispatch state updates, and those action creators can be called from the component.
So, initialise your state with an empty array:
const initialState = {
movies: []
};
Set up your reducer to update the state with MOVIES_UPDATE:
function reducer(state = initialState, action) {
const { type, payload } = action;
switch (type) {
case 'MOVIES_UPDATE': {
return { ...state, movies: payload };
}
}
}
You can still use your function for fetching data:
function fetchData() {
return Promise.all(urls.map(items => {
return fetch(items).then(response => response.json());
}));
}
..but it's called with an action creator (it returns a function with dispatch param), and this action creator 1) gets the data, 2) merges the data, 3) and dispatches the data to the store.
export function getMovies() {
return (dispatch) => {
fetchData().then(data => {
const movieData = data.flatMap(({ results }) => results);
dispatch({ type: 'MOVIES_UPDATE', payload: movieData });
});
}
}
And it's called from within your component like so:
componentDidMount () {
this.props.dispatch(getMovies());
}
You can modify the code as below:
import { API_URL, API_KEY } from '../Config/config';
let movieData='';
exports.movieData = await (async function(){
const urls = [
`${API_URL}movie/popular?api_key=${API_KEY}&language=en-US&page=1`,
`${API_URL}movie/popular?api_key=${API_KEY}&language=en-US&page=2`,
];
const arrayOfObjects = await Promise.all(urls.map(items => {
return fetch(items).then(response => response.json())
}));
return arrayOfObjects[0].results.concat(arrayOfObjects[1].results);
})();

Javascript - Redux actions don't run consecutively

I have a situation when I need 2 Redux Actions to be run consecutively.
The context is a user clicks on a Preview button, and I want to display a loader until the puzzle is done generating.
function mapDispatchToProps(dispatch) {
return {
onPreview: () => {
dispatch(generatePreview());
},
};
}
In order to do it, I use the middleware redux-thunk and the action I want to be executed first returns a Promise.resolve() and my second action is in the then():
export function generatingPreview() {
return dispatch => {
dispatch({
type: GENERATING_PREVIEW,
});
return Promise.resolve();
};
}
export function generatePreview() {
return (dispatch, getState) => {
dispatch(generatingPreview()).then(() => {
const state = getState();
const conf = state.getIn(['login', 'conf']).toJS();
const wordList = state.getIn(['login', 'wordList']);
try {
const newPuzzle = Wordfind.newPuzzleLax(wordList, conf);
dispatch(generatePreviewSuccess(newPuzzle));
} catch (err) {
dispatch(generatePreviewError(err.message));
}
});
};
}
export function generatePreviewError(error) {
return {
type: GENERATE_PREVIEW_ERROR,
error,
};
}
export function generatePreviewSuccess(payload) {
return {
type: GENERATE_PREVIEW_SUCCESS,
payload,
};
}
Unfortunately, the loader never appears. I console.logged the state setting the loading to true when my component renders, and it changes! I can see the log but not the loader, the component doesn't really re-render until the actions generatePreviewSuccess() or generatePreviewError() are dispatched. And it's not an issue from the loader, if I replace the newPuzzleLax function by a loop in order to make enough time to see it, I can see it!
My theory is this function Wordfind.newPuzzleLax(wordList, conf) that I use to generate the puzzle is blocking the queue of actions because on the Chrome Redux Tools I an see the first action appearing at the same time that the second one:
Link to the function.
If I add a 1-microsecond delay between the dispatch of the two actions, the loader appears... but I would really like to understand what is happening. Thank you in advance. If it's any help, I use the react-boilerplate
I also tried to transform the function generating the puzzle as an async one by doing this:
const wordFindAsync = async (wordList, conf) =>
Wordfind.newPuzzleLax(wordList, conf);
export function generatePreview() {
return (dispatch, getState) => {
dispatch(generatingPreview())
.then(() => {
const state = getState();
const conf = state.getIn(['login', 'conf']).toJS();
const wordList = state.getIn(['login', 'wordList']);
wordFindAsync(wordList, conf);
})
.then(res => dispatch(generatePreviewSuccess(res)))
.catch(err => {
dispatch(generatePreviewError(err.message));
});
};
}
In your second version you're not returning the Promise from wordFindAsync(wordList, conf) back into your original Promise chain, and so its not being resolved/waited on by then next then.
export function generatePreview() {
return (dispatch, getState) => {
dispatch(generatingPreview())
.then(() => {
const state = getState();
const conf = state.getIn(['login', 'conf']).toJS();
const wordList = state.getIn(['login', 'wordList']);
return wordFindAsync(wordList, conf); // 🌟 return your promise here
})
.then(res => dispatch(generatePreviewSuccess(res)))
.catch(err => {
dispatch(generatePreviewError(err.message));
});
};
}
Here's a simple example demoing the behavior I'm refering to.
This one will only wait 1 second until logging "done":
const waitOneSec = () =>
new Promise(resolve => {
console.log("waiting 1 secoond");
setTimeout(resolve, 1000);
});
waitOneSec()
.then(() => {
waitOneSec(); // Promise not returned
})
.then(() => console.log("done"));
Whereas this one will wait full 2 seconds until logging "done":
const waitOneSec = () =>
new Promise(resolve => {
console.log("waiting 1 secoond");
setTimeout(resolve, 1000);
});
waitOneSec()
.then(() => {
return waitOneSec(); // 🌟 Promise returned
})
.then(() => console.log("done"));
Hope that helps.

Categories

Resources