Using a callback while dispatching actions - javascript

I am trying to make a GET request for some data.
Here is my action call.
componentDidMount() {
this.props.fetchData(() => {
this.setState({ isLoading: false });
});
}
Prior to completion I'd like to display "Loading..." momentarily as the fetch request is making it's trip. I'm using a callback for this and setting my local state.
Here is my action creator with a 'callback'.
export function fetchData(callback) {
return (dispatch) => {
axios.get(`/api/fetchsomething`)
.then(() => callback())
.catch((err) => {
console.log(err.message);
});
}
}
And here is that same function above but dispatching the action so that I can receive as props and render to my ui.
export function fetchData(callback) {
return (dispatch) => {
axios.get(`/api/fetchsomething`)
.then((response) => dispatch({ type: FETCH_DATA, payload: response }))
.catch((err) => {
console.log(err.message);
});
}
}
My question is how do you make the callback and dispatch the action in the same action creator function? Is that even good practice?

You could do something like this
componentDidMount() {
this.setState({ isLoading: true }, () => {
// ensuring that you make the API request only
// after the local state `isLoading` is set to `true`
this.props.fetchData().then(() => this.setState({ isLoading: false });
});
}
and, fetchData would be defined as follows
export function fetchData(callback) {
return (dispatch) => {
return axios.get(`/api/fetchsomething`)
.then((response) => dispatch({ type: FETCH_DATA, payload: response }))
.catch((err) => console.log(err.message));
}
}
If you're using the redux-thunk middleware to use asynchronous actions, then these actions will return Promises; so you can set your component's local state after that Promise resolves.

In the component:
.....
componentDidMount() {
this.props.fetchData();
}
....
export default connect((state) => ({loading: state.loading, data: state.data}))(Component);
In the actions, you should do :
....
export function fetchData() {
return (dispatch) => {
dispatch({ type: FETCHING_DATA}); //dispatch an action for loading state to set it to true
return axios.get(`/api/fetchsomething`)
.then((response) => dispatch({ type: DATA_FETCHED, payload: response }))
.catch((err) => console.log(err.message));
}
}
....
In the reducer, you should do :
....
case 'FETCHING_DATA':
return {
...state,
loading: true,
}
case 'DATA_FETCHED':
return {
...state,
data: action.payload,
loading: false,
}
....
I personally feel that you shouldn't put any business logic in your component because it can cause some problems later when you want to refactor your app. This means that there shouldn't be any .then in your component and everything should be guided through redux (if there is some side effects in your app). So, you should control your loading state from redux itself and not inside the component.

Related

Cleaning up a useEffect warning with useReducer,

I keep getting these warnings:
Can't perform a React state update on an unmounted component.
This is a no-op, but it indicates a memory leak in your application.
To fix, cancel all subscriptions and asynchronous tasks in a useEffect cleanup
For some of my useEffects that pull data from an API with the help of my useReducer:
export default function HomeBucketsExample(props) {
const {mobileView} = props
const [allDemoBuckets, dispatchAllBuckets] = useReducer(reducer, initialStateAllBuckets)
const ListLoading = LoadingComponent(HomeBucketLists);
useEffect(() =>
{
getAllDemoBuckets(dispatchAllBuckets);
}, [])
return (
<ListLoading mobileView={ mobileView} isLoading={allDemoBuckets.loading} buckets={allDemoBuckets.data} />
);
}
However, Im not sure how to clean up this effect above, I've tried mounting it using True and False, however the error still showed up. How can I fix my function above so the useEffect doesnt throw any warnings
EDIT:
code for my reduer:
export const getAllDemoBuckets = (dispatch) => axiosInstance
.get('demo/all/')
.then(response => {
dispatch({ type: 'FETCH_SUCCESS', payload: response.data })
console.log('fired bucket-data')
})
.catch(error => {
dispatch({ type: 'FETCH_ERROR' })
})
const initialStateAllBuckets = {
loading: true,
error: '',
data: []
}
const reducer = (state, action) =>
{
switch (action.type)
{
case 'FETCH_SUCCESS':
return {
loading: false,
data: action.payload,
error: ''
}
case 'FETCH_ERROR':
return {
loading: false,
data: {},
error: "Something went wrong!"
}
default:
return state
}
}
const [allDemoBuckets, dispatchAllBuckets] = useReducer(reducer, initialStateAllBuckets)
The goal of the warning is to tell you that some action is taking place after the component is unmounted and that the result of that work is going to be thrown away.
The solution isn't to try and work around it with a reducer; the solution is to cancel whatever is happening by returning a callback from useEffect. For example:
useEffect(() => {
const ctrl = new AbortController();
fetchExternalResource(ctrl.signal);
return () => {
ctrl.abort();
}
}, []);
Using flags to determine if a component is mounted (ie using a reducer) to determine whether or not to update state is missing the point of the warning.
It's also okay to leave the warning up if this isn't actually an issue. It's just there to nit pick and tell you that, hey, you may want to clean this up. But it's not an error.
In your case, if you are using fetch, I would modify your code such that the function that dispatches actions can take an AbortSignal to cancel its operations. If you're not using fetch, there's not much you can do, and you should just ignore this warning. It's not a big deal.
It looks like you're using Axios for your requests. Axios supports a mechanism similar to abort signals - This should do the trick.
import { CancelToken } from 'axios';
const getAllDemoBuckets = async (dispatch, cancelToken) => {
try {
const response = await axiosInstance.get('/demo/all', { cancelToken });
dispatch({ type: 'FETCH_SUCCESS', payload: response.data });
} catch (err) {
if ('isCancel' in err && err.isCancel()) {
return;
}
dispatch({ type: 'FETCH_ERROR' });
}
}
const MyComponent = () => {
useEffect(() => {
const source = CancelToken.source();
getAllDemoBuckets(dispatch, source.token);
return () => {
source.cancel();
};
}, []);
}

Why isn't this redux action promise resolving?

I have a redux action that returns a promise, so that the ui can respond accordingly based upon the result. This action sends a request to the backend, and when it gets a successful response from the server, it resolves.
redux action:
export const preauthorizePayment = (amount, connectedAccountId, customerId) => {
console.log("action reached")
return (dispatch) => {
dispatch({ type: types.UPDATING })
return new Promise((reject, resolve) => {
console.log("promise reached")
axios.get(PRE_AUTH_ENDPOINT, {
params: {
amount: amount,
customerId: customerId,
connectedAccountId: connectedAccountId
}
})
.then(res => {
console.log("then reached...")
if (res.data.result == 'success') {
dispatch({ type: types.UPDATE_SUCCESS });
console.log("if reached...");
return resolve();
} else {
console.log("else reached...")
console.log(JSON.stringify(res))
dispatch({
type: types.UPDATE_FAIL,
info: res.data.errorInfo,
errorCode: res.data.errorCode
})
return reject()
}
})
.catch(err => {
dispatch({ type: types.UPDATE_FAIL, errorInfo: err })
return reject()
})
})
}
}
the UI piece looks like:
props.preauthorizePayment(amount, connectedAccountId, customerId)
.then(() => {
console.log("pre-auth then reached")
// pre-auth success
setCheckInModal(false)
props.checkIn(props.evnt.id, props.role)
})
.catch(err => setCheckInModal(true))
What im experiencing is that everything is working properly up until the resolve() portion of the redux action. din the if block of the redux action's then(), the dispatch gets fired, the log fires, but it doesn't resolve. The UI piece's then() does not execute, no log, nothing. initially I didnt have the return keyword in the resolve line, and once I added it, it worked once. but then it stopped.
Whats going on?

Plotting markers on a map using react-native-maps

In short, I'm trying to plot markers on a map using react-native-maps.
I've gone as far as creating an action to fetch the coordinates and respective ID from the server (see code below).
export const getPlacesOnMap = () => {
return dispatch => {
dispatch(authGetToken())
.then(token => {
return fetch("myApp?auth=" + token);
})
.catch(() => {
alert("No valid token found!");
})
.then(res => {
if (res.ok) {
return res.json();
} else {
throw(new Error());
}
})
.then(parsedRes => {
const places = [];
for (let key in parsedRes) {
places.push({
// ...parsedRes[key], // this fetches all the data
latitude: parsedRes[key].location.latitude,
longitude: parsedRes[key].location.longitude,
id: key
});
} console.log(places)
dispatch(mapPlaces(places));
})
.catch(err => {
alert("Oops! Something went wrong, sorry! :/");
console.log(err);
});
};
};
export const mapPlaces = places => {
return {
type: MAP_PLACES,
places: places
};
};
I don't know if I'm using the right words, but I've essentially tested the code (above) using componentWillMount(), and it successfully returned multiple coordinates as an array of objects.
Now, the problem is I don't know what to do next. As much as I understand, I know the end goal is to create a setState(). But I don't know how to get there.
Would be a great help if someone can point me in the right direction.
You need to create an async action. You can dispatch different actions inside an async action based on whether the async function inside it is resolved or rejected.
export function getPlacesOnMap(token) {
return async function(dispatch) {
dispatch({
type: "FETCHING_PLACES_PENDING"
});
fetch("myApp?auth=" + token)
.then(res => {
dispatch({
type: "FETCHING_PLACES_FULFILLED",
payload: res.json()
});
})
.catch(error => {
dispatch({
type: "FETCHING_PLACES_REJECTED",
payload: error
});
});
};
}
If your authGetToken() function is also a promise, you need to dispatch this action after authGetToken() was resolved.
You can use the action.payload in your "FETCHING_PLACES_FULFILLED" case of your reducer(s) to be able to use the retrieved data.
UPDATE
Your reducer should be like this:
export default function reducer(
state = {
loadingMarkers : false,
markers : [],
error : null,
},
action
) {
switch (action.type) {
case "FETCHING_PLACES_PENDING":
return { ...state, loadingMarkers: true };
case "FETCHING_PLACES_FULFILLED":
return { ...state, loadingMarkers: false, markers: action.payload};
case "FETCHING_PLACES_REJECTED":
return { ...state, loadingMarkers: false, error: action.payload };
default:
return state;
}
}
Now you can connect your component to redux and use your markers when they are fetched.
have a look at this example and connect docs

Warning: Can't call setState (or forceUpdate) on an unmounted component in React Native

Full error image:
error image
I am making a fetch request and when I want to set the state to save some errors that happens. How do I fix this?
Code:
onClickLogIn = (username, password) => {
const request = fetch('[SOMEAPILINK]', {
method: 'POST',
headers: {
Accept: 'text/javascript',
'Content-Type': 'text/javascript',
},
body: JSON.stringify({
username: username,
password: password,
login: 1
})
}).then(response => response.json()).then(responseJson => {
console.log(responseJson)
this.setState({
errorCheck: responseJson.error
})
}).catch(error => {
console.log("error")
})
// console.log(errorCheck);
console.log(request);
console.log("----ERROR CHECK ----")
console.log(this.state.errorCheck)
this.props.navigation.navigate("Second")
}
So when I want to set errorCheck that error comes in...
Thanks!
then(response => response.json()).then(responseJson => {
console.log(responseJson)
this.setState({
errorCheck: responseJson.error
})
this.props.navigation.navigate("Second")
})
=> Add this code this.props.navigation.navigate("Second") of navigation inside the then() method so it will call navigation after updating the state then your error will gone.
=> and try to update the state using setState function not an object so try
this.setState(function (prevState, props) {
return { errorCheck: responseJson.error}
})
it will reduce the time , taken by the object to update the state.
=> So your code will look like
then(response => response.json()).then(responseJson => {
console.log(responseJson)
this.setState(function (prevState, props) {
return { errorCheck: responseJson.error}
})
this.props.navigation.navigate("Second")
})
setState is asynchronous. So if you unmount the stateful component (by calling navigate) before updating the state then you'll get the warning.
You should use the callback that setState provides instead
.then(response => response.json()).then(responseJson => {
console.log(responseJson)
this.setState({
errorCheck: responseJson.error
}, () => {
this.props.navigation.navigate("Second")
})
})

Action must be plain object. Use custom middleware

What would be the problem?
Uncaught Error: Actions must be plain objects. Use custom middleware for async actions.
Configure Store:
export default configureStore = () => {
let store = compose(applyMiddleware(ReduxThunk))(createStore)(reducers);
return store;
}
Action
export const menulist = async ({ navigation }) => {
return async dispatch => {
try {
dispatch({ type: types.MENULIST_REQUEST_START })
let response = await menuListByCategories();
dispatch({ type: types.MENULIST_SUCCESS })
} catch (error) {
dispatch({ type: types.MENULIST_FAILED })
}
}
}
You are using it the wrong way,
in Redux every action must return an object, and this is a must!
so, your dispatch, which is a function, should be called this way.
Besides you only need to declare async the function which returns dispatch. The async keyword determines that the following function will return a promise. As your first function (menulist) is returning the promise returned by the second function (dispatch one) you don't have to specify it.
export const menulist = ({ navigation }) => async (dispatch) => {
try {
dispatch({ type: types.MENULIST_REQUEST_START })
let response = await menuListByCategories();
dispatch({ type: types.MENULIST_SUCCESS })
} catch (error) {
dispatch({ type: types.MENULIST_FAILED })
}
}
}

Categories