Redux - Asynchronous response from web socket request - javascript

I have a websocket interface which I implemented so that I can use to send requests.
The problem is that the response is asynchronous and it initially returns the empty array because retObj is not updated from the callback function that I sent in. How can I make this function so that it will return the populated array when it has been updated.
This is how my Service looks like:
import * as interface from '../webSocket'
const carService = () => {
return {
getCars: () => {
interface.sendRequest(function (returnObject) {
//
}).then(d => d)
}
}
}
export default carService()
And this is how my action looks like:
import { GET_CARS } from '../constants'
import carService from '../carService'
export const getCars = () => async (dispatch) => {
try {
const cars = await carService.getCars()
console.log("At cars actions: ", cars) // logs: Array []
dispatch(getCarsSuccess(cars))
} catch (err) {
console.log('Error: ', err)
}
}
const getCarsSuccess = (cars) => ({
type: GET_CARS,
payload: cars
})

You simply have to wrap your callback into promise, since it was not a promise to begin with, which is why you cannot use then or await
import * as interface from '../webSocket'
const carService = () => {
return {
getCars: () => {
return new Promise(resolve => interface.sendRequest(function (returnObject) {
resolve(returnObject.msg)
}));
}
}
}
export default carService()

The problem is, you cant await a function unless it returns a Promise. So, as you can guess, the problem lies in carService.getCars's definition. Try this:
getCars: () => {
return new Promise((resolve, reject) => {
interface.sendRequest(function(returnObject) {
// if theres an error, reject(error)
resolve(returnObject);
})
})
}
Or, if sendRequest os am async function, simply return the return value of sendRequest:
getCars: () => {
return interface.sendRequest()
}

Related

ReactJS - push method with useState array

The purpose here is to have an array of channels id where I can populate him with information that is coming from my firebase.
I have my component like this:
export default function Component() {
const [channelsId, setChannelsId] = useState([])
// and I call this function passing my state
useEffect(() => {
getChannelsIds(someId, channelsId, setChannelsId)
}, [])
The function:
export const getChannelsIds = (someId, channelsId, setChannelsId) => {
try {
firestore.collection("channels").where("someId", "==", someId).get().then(querySnapshot => {
querySnapshot.forEach(doc => {
setChannelsId([...channelsId, doc.data().id])
})
})
} catch (err) {
toast.error('Error while trying to get the channel.')
}
}
It's not working, because my channelsId state is being override and I only have the last channelId, console.log screenshot:
You should either use functional updates
export const getChannelsIds = (someId, channelsId, setChannelsId) => {
try {
firestore.collection("channels").where("someId", "==", someId).get().then(querySnapshot => {
querySnapshot.forEach(doc => {
setChannelsId(ids => [...ids, doc.data().id]);
})
})
} catch (err) {
toast.error('Error while trying to get the channel.')
}
}
Or even better you could create first an array with the new data and only update the state once.
export const getChannelsIds = (someId, channelsId, setChannelsId) => {
try {
firestore.collection("channels").where("someId", "==", someId).get().then(querySnapshot => {
const newChannelIds = querySnapshot.map(doc => doc.data().id);
setChannelsId([...channelsId, ...newChannelIds);
});
}
catch (err) {
toast.error('Error while trying to get the channel.')
}
}

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

How to return Promise using Rxjs in React?

I am learning RxJS/Redux-Observable in React.
But I have a question about return Promise.
Without using RxJS/Redux-Observable
so that I can return promise to component let it can use .then() for next action
In the Action of React
export const getData = () => (dispatch) => {
try {
const dataResponse = await dataAPI.getData();
dispatch(getDataAction(dataResponse));
return Promise.resolve(dataResponse);
} catch (error) {
return Promise.reject(error);
}
}
In the Component of React
componentDidMount = () => {
const {
getData
} = this.props;
getData().then(function(response) {
// I can use this response action for some UX Action.
})
}
With using RxJS/Redux-Observable
I don't know how to return promise
In the Epic Action of React
export const getDataEpic = (action$, state$) => {
return action$.pipe(
ofType(FETCH_DATA),
mergeMap(action => {
let _response = ajax.getJSON(dataAPI.getData());
return _response.pipe(
delay(3000),
map(response => {
return fetchDataFulfilledAction(response);
}),
takeUntil(action$.pipe(
filter(
action => action.type === CANCEL_FETCH_DATA
)
))
)
})
);
}
In the Component of React
componentDidMount = () => {
const {
getData
} = this.props;
getData().then(function(response) {
// How to get this response result ?
})
}
I know using Reducer is the one of way to handle, but I still want to know how to return promise.
Thanks guys
So you made a classic mistake when we are returning a promise you should not return a new Promise for the success or error but return two function:
resolve -> for the sucess chaining it with then
reject -> for the faileure chaining it with catch !
Hope this code will help you, tell me if you need clarification
export const getData = () => (dispatch) => new Promise((resolve, reject) => {
const dataResponse = await dataAPI.getData();
dispatch(getDataAction(dataResponse))
if(dataResponse.status === '200') {
return resolve(dataResponse) }
else {
return reject(dataResponse.error)
}
})

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.

Testing Redux Thunk Action Creator

I've got a redux action creator that utilizes redux-thunk to do some logic to determine what to dispatch to the store. Its not promise-based, like an HTTP request would be, so I am having some issues with how to test it properly. Ill need a test for when the value meets the condition and for when it doesn't. Since the action creator does not return a promise, I cannot run a .then() in my test. What is the best way to test something like this?
Likewise, I believe it would be pretty straightforward testing the getRemoveFileMetrics() action creator as it actually does return a promise. But how can I assert that that will called if the value is removeFiles and meets the condition? How can that be written in the test?
Thanks in advance as this has had me stuck for the last couple of days.
Action Creators
export const handleSelection = (value, cacheKey) => {
return dispatch => {
if (value === "removeFiles") {
dispatch(getRemoveFileMetrics(cacheKey));
}
dispatch({ type: HANDLE_SELECTION, value });
};
};
export const getRemoveFileMetrics = cacheKey => {
return dispatch => {
dispatch({ type: IS_FETCHING_DELETE_METRICS });
return axios
.get(`../GetRemoveFileMetrics`, { params: { cacheKey } })
.then(response => {
dispatch({ type: GET_REMOVE_FILE_METRICS, payload: response.data });
})
.catch(err => console.log(err));
};
};
Jest
it("should dispatch HANDLE_SELECTION when selecting operation", () => {
const store = mockStore({});
const value = "switchVersion";
const expectedAction = [{
type: MOA.HANDLE_SELECTION,
value,
}]; // TypeError: Cannot read property 'then' of undefined
return store.dispatch(MOA.handleSelection(value)).then(() => {
const returnedActions = store.getActions();
expect(returnedActions).toEqual(expectedAction);
});
});
NEW EDIT
So based off of Danny Delott's answer to return a promise, I acheived a passing test as follows:
export const handleSelection = (value, cacheKey) => {
return dispatch => {
if (value === "removeFiles") {
return dispatch(getRemoveFileMetrics(cacheKey));
}
return new Promise((resolve, reject) => {
resolve(dispatch({ type: HANDLE_SELECTION, value }));
});
};
};
Is there a reason to explicitly NOT return a promise in your action creator? It looks like getRemoveFileMetrics is returning the promise, it just gets swallowed in handleSelection...
Easiest solution is to just return the promise:
export const handleSelection = (value, cacheKey) => {
return dispatch => {
if (value === "removeFiles") {
return dispatch(getRemoveFileMetrics(cacheKey));
}
dispatch({ type: HANDLE_SELECTION, value });
return new Promise();
};
};
Otherwise, you'll need make your assertions after the event loop is finished. You can do with a setTimeout wrapped in a Promise to get the .then behavior.
it("should dispatch HANDLE_SELECTION when selecting operation", () => {
const store = mockStore({});
const value = "switchVersion";
const expectedAction = [{
type: MOA.HANDLE_SELECTION,
value,
}];
store.dispatch(MOA.handleSelection(value));
// flush outstanding async tasks
return new Promise(resolve => {
setTimeout(resolve, 0);
})
.then(() => {
const returnedActions = store.getActions();
expect(returnedActions).toEqual(expectedAction);
});
});

Categories

Resources