The data source for my app only provides data in XML format.
I use axios to get the XML data. It ends up as a string in the data section of the result.
I have tried to use xml2js to convert it, but it just fires off a async job and returns, so I dont get the redux-promise middelware to work. The payload is nothing when the reducers sends the data to the component that should render it.
Not sure if this makes sense, but can I make the reducer wait for the new function call to return before sending the data on the the component?
action index.js
export function fetchData(jobid, dest) {
const url = `${DATA_URL}jobid=${jobid}&refdist=${dest}`;
const request = axios.get(url);
console.log(request);
return {
type: FETCH_DATA,
payload: request
}
}
my reducer
export default function (state = [], action) {
console.log(action);
switch (action.type) {
case FETCH_DATA:
console.log("pre");
parseString(action.payload.data, function (err, result) {
// Do I need some magic here??? or somewhere else?
console.dir(result);
});
return [action.payload.data, ...state];
}
return state;
}
you should change your action creator code, because axios is async. And dispatch action after receive data.
You don't need this logic in reducer.
For async actions you may use redux-thunk
export const fetchData = (jobid, dest)=>dispatch =>{
const url = `${DATA_URL}jobid=${jobid}&refdist=${dest}`;
const request = axios.get(url).then(res=>{
parseString(res, function (err, result) {
if(result){
dispatch({
type: FETCH_DATA,
data:result
})
}
if(err) throw err
});
}).catch(err=>console.error(error))
};
///clean reducer
export default function (state = [], action) {
switch (action.type) {
case FETCH_DATA:
return [...state, action.data ];
}
return state;
}
Also you may need to know about fetching process: loading, success , failure.Then action creator may looks like:
export const fetchData = (jobid, dest)=>dispatch =>{
const url = `${DATA_URL}jobid=${jobid}&refdist=${dest}`;
dispatch({
type: FETCH_DATA_REQUEST,
data:result,
isFetching:true
})
const request = axios.get(url).then(res=>{
parseString(res, function (err, result) {
if(result){
dispatch({
type: FETCH_DATA_SUCCESS,
data:result,
isFetching:false
})
}
if(err) throw err
});
}).catch(err=>{
dispatch({
type: FETCH_DATA_FAILURE,
err:err,
isFetching:false
})
console.error(error)
})
};
Related
I have a custom hook like so for getting data using useQuery. The hook works fine, no problem there.
const getData = async (url) => {
try{
return await axios(url)
} catch(error){
console.log(error.message)
}
}
export const useGetData = (url, onSuccess) => {
return useQuery('getData', () => getData(url), {onSuccess})
}
However, if I call this hook twice in my component it will only fetch data from the first call even with a different URL. (Ignore the comments typo, that's intentional)
The call in my component:
const { data: commentss, isLoading: commentsIsLoading } = useGetData(`/comments/${params.id}`)
const { data: forumPost, isLoading: forumPostIsLoading } = useGetData(`/forum_posts/${params.id}`)
When I console.log forumPost in this case, it is the array of comments and not the forum post even though I am passing in a different endpoint.
How can I use this hook twice to get different data? Is it possible? I know I can just call parallel queries but I would like to use my hook if possible.
Since useQuery caches based on the queryKey, use the URL in that name
const getData = async(url) => {
try {
return await axios(url)
} catch (error) {
console.log(error.message)
}
}
export const useGetData = (url, onSuccess) => {
return useQuery('getData' + url, () => getData(url), {
onSuccess
})
}
//........
const {
data: commentss,
isLoading: commentsIsLoading
} = useGetData(`/comments/${params.id}`)
const {
data: forumPost,
isLoading: forumPostIsLoading
} = useGetData(`/forum_posts/${params.id}`)
Say for example when a login function calls an API and it returns an error because of something like invalid credentials. I have noticed that it still goes through the fulfilled case in the extra reducers part. Should I add an if statement to check if response code is 200 or is there a way for the thunk to go through the rejected case?
extraReducers: builder => {
builder.addCase(login.pending, (state, action) => {
state.fetchingError = null;
state.fetchingUser = true;
});
builder.addCase(login.fulfilled, (state, {payload}) => {
console.log(payload, 'hello?');
state.user = payload.data.user;
});
builder.addCase(login.rejected, (state, action) => {
state.fetchingUser = false;
state.fetchingError = action.error;
});
},
You can use rejectWithValue in createAsyncThunk to customize the reject action.
It also takes an argument which will be "action.payload" in the reject action.
In createAsyncThunk:
const updateUser = createAsyncThunk(
'users/update',
async (userData, { rejectWithValue }) => {
const { id, ...fields } = userData
try {
const response = await userAPI.updateById(id, fields)
return response.data.user
} catch (err) {
// Use `err.response.data` as `action.payload` for a `rejected` action,
// by explicitly returning it using the `rejectWithValue()` utility
return rejectWithValue(err.response.data)
}
}
)
https://redux-toolkit.js.org/api/createAsyncThunk#handling-thunk-errors
I am trying to load an array I saved using AsyncStorage back into state but I cant seem to get it working. I am passing the array from AsyncStorage back into context and calling the load_state case.
function loadList() {
try {
const data = AsyncStorage.getItem('data')
console.log(data)
loadState(JSON.parse(data))
}
catch (error) {
console.log(error)
}
}
const loadState = dispatch => {
return (value) => {
dispatch({ type: 'load_state', payload: value})
}}
case 'load_state':
console.log(action.payload.value)
return [...state, ...action.payload.value]
I need to do a dynamic action. In other words, it can be reused for differents actions.
I tried to create a function that loads type and payload, but an error appears.
I'm trying make this function works:
export function getData(url, type) {
const request = Server.get(url)
return (dispatch) =>
request.then((response) => {
dispatch({
type: type,
payload: response.data
})
}).catch(function (error) {
console.log(error)
});
}
But I got an error when I call this function this way:
export function getClientes() {
Actions.getData('ClientesEFornecedores', GET_CLIENTES)
}
It's showing:
Uncaught Error: Actions must be plain objects. Use custom middleware for async actions.
I'm Calling the getClientes() function this way:
function ClientesTable(props)
{
const dispatch = useDispatch();
const clientes = useSelector(({erpCliente}) => erpCliente.clientes.data);
useEffect(() => {
dispatch(Actions.getClientes());
}, [dispatch]);
How can I make an action be reusable?
Try something like this
export const getData=(url, type) =>async dispatch=>{
try{
const response = await Server.get(url);
dispatch({ type: type,payload: response.data })
} catch(err){
console.log(err)
}
}
getClientes function
export const getClientes=() => dbActions.getData('ClientesEFornecedores', GET_CLIENTES);
In fact I had almost succeeded.
All that remained was to return the function call.
This is the way that works:
export function getClientes() {
return dbActions.getData('ClientesEFornecedores', GET_CLIENTES)
}
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