I'm building an app in React Native fetching data from an API.
The API data is information about flights departures/arrivals.
What I'm trying to do is very simple, I want that the app has 2 tabs which switch screens between Arrivals and Departure. The 2 screens will show all the flights departures or arrivals.
At the moment I did the app only to show the departures flights and getting data from the URL like URL/flights/departures. What I cannot understand as I'm new in Native is how can I fetch data based on the URL params. What I mean is that I have the API URL and I would like to fetch the data if it is departures or arrivals adding to the URL like API/flights/{flightType} so when a screen changes the correct data is fetched. What I did for now is below but only for departures and I would like to understand how to change it to do as I need.
import axios from 'axios';
import { apiBaseURL } from "../Utils/Constants";
import {
FETCHING_FLIGHTS_DATA
,FETCHING_FLIGHTS_DATA_SUCCESS,
FETCHING_FLIGHTS_DATA_FAIL
} from "../Utils/ActionTypes";
export default function FetchFlightData() {
return dispatch => {
dispatch({ type: FETCHING_FLIGHTS_DATA});
return axios.get(`${apiBaseURL}/departures`)
.then(res => {
dispatch({ type: FETCHING_FLIGHTS_DATA_SUCCESS, payload: res.data})
})
.catch(err => {
dispatch({ type: FETCHING_FLIGHTS_DATA_FAIL, payload: err.data})
})
}
}
You can pass the flight type as a parameter to your function and when switching the tab call the FetchFlightData with the right type. You probably should separate the fetch success for each type or add the flight type as a parameter to your payload and then in the reducer make the difference between data of departures or data of arrivals
import axios from 'axios';
import { apiBaseURL } from "../Utils/Constants";
import {
FETCHING_FLIGHTS_DATA,
FETCHING_FLIGHTS_DATA_SUCCESS,
FETCHING_FLIGHTS_DATA_FAIL
} from "../Utils/ActionTypes";
export default function FetchFlightData(flightType) {
return dispatch => {
dispatch({ type: FETCHING_FLIGHTS_DATA});
return axios.get(`${apiBaseURL}/${flightType}`)
.then(res => {
dispatch({ type: FETCHING_FLIGHTS_DATA_SUCCESS, payload: {data:res.data,type:flightType}})
})
.catch(err => {
dispatch({ type: FETCHING_FLIGHTS_DATA_FAIL, payload: err.data})
})
}
}
//somewhere in the reducer
case FETCHING_FLIGHTS_DATA_SUCCESS:
return {
...state,
arrivals: action.payload.type === 'arrival' ? action.payload.data: state.arrivals,
departures: action.payload.type === 'departures' ? action.payload.data:state.departures
}
break;
// somewhere in your code when you detect a change of tab
FetchFlightData('arrivals');
FetchFlightData('departures');
Related
I am using an api call to get information for my app which I display to the user. The problem is that when I open the screen for the first time the app displays the information but when I go to a different screen and then comeback I dont see the information unless I restart the app.
This function makes the apiCall for me:
async function getOrders() {
var retrieveData = async () => {
try {
var value = await AsyncStorage.getItem("user");
var data = JSON.parse(value);
return data.user.email;
} catch (error) {
alert(error);
}
};
retrieveData().then((usr) => {
setUser(usr)
fetch(URL + "/api/order/quoted", {
method: "POST",
body: "user=" + usr,
headers: { "Content-type": "application/x-www-form-urlencoded" },
})
.then((response) => response.json())
.then((responseJson) => {
if (responseJson.error === null) {
setOrders(responseJson.orders);
}
});
});
}
First I use the retriveData function to get the used id, based on that information is server to the user.
You are using react-navigation version 5, so you need to wrap your logic fetch data in useFocusEffect hook react navigation docs
import { useFocusEffect } from '#react-navigation/native';
useFocusEffect(
React.useCallback(() => {
getOrders()
}, [getOrders])
);
The problem can be solved in the following steps:
If you want the data fetched from your endpoint to be used even if you move to other screen use Redux.
If you use redux or not and want to fetch the api every time you open a specific screen then you need to add an onfocus listener. An example is here https://reactnavigation.org/docs/navigation-events/
class Profile extends React.Component {
componentDidMount() {
this._unsubscribe = navigation.addListener('focus', () => {
// do something
});
}
I have implemented the redux-thunk which works just fine in my react-native application. I have some 'this.counterValue', which value must be updated after getting the response from the api. As api fetch methods are implemented in another actions files, and response is achieved in that file. So, how must it be implemented to make this work fine.I don't want the change in 'this.counterValue' results in re-render of my application. I am new to react native, it would be great to be helped. Thanks.
Component file:
this.counterValue = 75; //local variable
this.props.fetchData('onStart'); // call to fetch data from actions files
Action file:
export const fetchData = (fetchType) => {
return async dispatch => {
dispatch(fetchingDataRequest());
fetch(AllApi),
{
method: 'GET',
headers:
{
'Accept': 'application/json',
'Content-Type': 'application/json',
'Authorization': 'Bearer '+ global.apiToken,
},
})
.then(response => {
return response.json()
})
.then(RetrivedData => {
dispatch(fetchingDataSuccess(RetrivedData.data));
})
.catch(error => {
dispatch(fetchingNotificationFailure(error));
});
}
}
using dispatch send the data to the reducer and in reducer update the state value which then you could use it in the component.
Reducer
import { reducerWithInitialState } from 'typescript-fsa-reducers'
import { DemoListViewActions } from './Actions'
export interface DemoListViewState {
data: any[]
selectedData: any
}
const initialState: DemoListViewState = {
data: [],
selectedData: undefined
}
const dataListRequestSuccessHandler = (state: DemoListViewState, payload: any[]): DemoListViewState => {
return {
...state,
data: payload
}
}
export const DemoListViewReducer = reducerWithInitialState(initialState)
.case(DemoListViewActions.dataListRequestSuccess, dataListRequestSuccessHandler)
.build()
Container
const mapStateToProps: MapStateToProps<IDemoListViewStateProps, OwnProps, RootState> = (state: RootState, ownProps: OwnProps) => {
const {data} = state.demoListView
return {
listData: data
}
}
Component
export interface IDemoListViewStateProps {
listData: any[]
}
just store it in props and state and you could manipulate it easily
After dispatching your action,you have to update your reducer
Import Action from './Action'
const initialState = {
people :[ ] // initiate empty array
}
export default function(state=initialState,action){
switch(action.type){
case Action.test:
return testUpdate(state,action)
default :
return state
}
}
//Update the result from the api,people array will be populated
with latest data
function testUpdate(state,action){
return {...state,people:action.payload.people}
}
I am trying to get a json response from an api and get the data successfully but when I call a action within another action using redux-thunk, my data is not available inside the reducer. I need data in "data" property inside my reducer get in component. Check the code below.
This is my action
import { GET_REPORT, GET_DATA_SUCCESS } from './types';
export const getData = (text) => {
return (dispatch) => {
dispatch ({ type: GET_REPORT})
fetch('http://api.openweathermap.org/data/2.5/forecast?q='+text+'&cnt=1&units=metric&APPID={key}')
.then(response => response.json())
.then(data => getDataSuccess(dispatch, data.list[0]))
.catch((error) => console.log(error));
};
};
const getDataSuccess = (dispatch, data) => {
//console.log(data);
dispatch({
type: GET_DATA_SUCCESS,
payload: data
});
}
this is my reducer
import { GET_REPORT } from'../actions/types';
const INITIAL_STATE = {
data: '',
}
export default (state = INITIAL_STATE, action) => {
switch(action.type){
case GET_REPORT:
console.log(action.payload); // getting undefined
return {...state, data: action.payload};
default:
return state;
}
}
I need data in "data" property get in component.
you are missing GET_DATA_SUCCESS in your reducer
The action dispatch ({ type: GET_REPORT}) , doesn't contain a payload hence undefined. Either you need to make reducer to handle action GET_DATA_SUCCESS or modify the existing one.
To simplify, dispatch({
type: GET_DATA_SUCCESS,
payload: data
}); contains a payload whereas dispatch ({ type: GET_REPORT}) doesn't
Resolved it by adding new switch case for GET_DATA_SUCCESS and get the payload from getDataSuccess and removing the payload from GET_REPORT case.
Now switch case looks like this
switch(action.type){
case GET_REPORT:
return {...state};
case GET_DATA_SUCCESS:
console.log(action.payload);
return{...state, data: action.payload}
default:
return state;
}
I have a number of actions and reducers setup for different content types, e.g. pages, events and venues. These actions and reducers get data which has been saved to AsyncStorage, by another action called sync, and puts it into the store.
Sync performs an async call to Contentful and retrieves any new/updated/deleted entries, which I then save to AsyncStorage.
What is the best way to ensure the view correctly is re-rendered after the async call is finished?
Should syncReducer merge data into the store that would normally be pulled out by pagesReducer, venuesReducer etc or should there be some kind of event emitted after syncReducer is done?
Data is pulled in asynchronously for offline viewing and keeping things fast, so I really don't want to wait for the sync before rendering.
data/sync.js
import { AsyncStorage } from 'react-native';
import database from './database';
const cache = {
getByType: async (query) => {
return new Promise(async(resolve, reject) => {
// Get results from AsyncStorage
resolve(results);
});
},
sync: async () => {
return new Promise(async(resolve, reject) => {
database
.sync(options)
.then(async results => {
// Save results to AsyncStorage
resolve(results);
});
});
}
};
export default cache;
actions/sync.js
import actionTypes from '../constants/actionTypes';
import cache from '../data/cache';
export function sync() {
return dispatch => {
dispatch(syncRequestedAction());
return cache
.sync()
.then(() => {
dispatch(syncFulfilledAction());
})
.catch(error => {
console.log(error);
dispatch(syncRejectedAction());
});
};
}
function syncRequestedAction() {
return {
type: actionTypes.SyncRequested
};
}
function syncRejectedAction() {
return {
type: actionTypes.SyncRejected
};
}
function syncFulfilledAction(data) {
return {
type: actionTypes.SyncFulfilled,
data
};
}
actions/getPages.js
import actionTypes from '../constants/actionTypes';
import cache from '../data/cache';
export function getPages() {
return dispatch => {
dispatch(getPagesRequestedAction());
return cache
.getByType('page')
.then(results => {
dispatch(getPagesFulfilledAction(results));
})
.catch(error => {
console.log(error);
dispatch(getPagesRejectedAction());
});
};
}
function getPagesRequestedAction() {
return {
type: actionTypes.GetPagesRequested
};
}
function getPagesRejectedAction() {
return {
type: actionTypes.GetPagesRejected
};
}
function getPagesFulfilledAction(settings) {
return {
type: actionTypes.GetPagesFulfilled,
pages
};
}
reducers/pagesReducer.js
import { merge } from 'lodash';
import actionTypes from '../constants/actionTypes';
const pagesReducer = (state = {}, action) => {
switch (action.type) {
case actionTypes.GetPagesRequested: {
return merge({}, state, { loading: true });
}
case actionTypes.GetPagesRejected: {
return merge({}, state, { error: 'Error getting pages', loading: false });
}
case actionTypes.GetPagesFulfilled: {
const merged = merge({}, state, { error: false, loading: false });
return { ...merged, data: action.pages };
}
default:
return state;
}
};
export default pagesReducer;
In the end I was able to solve this by importing the other actions into my sync action, and dispatching depending on which data needs to be updated.
import { getEvents } from './getEvents';
import { getPages } from './getPages';
import { getVenues } from './getVenues';
export function sync() {
return dispatch => {
dispatch(syncRequestedAction());
return cache
.sync()
.then(results => {
dispatch(syncFulfilledAction());
if (results.includes('event')) {
dispatch(getEvents());
}
if (results.includes('page')) {
dispatch(getPages());
}
if (results.includes('venue')) {
dispatch(getSettings());
}
})
.catch(error => {
console.log(error);
dispatch(syncRejectedAction());
});
};
}
Your sync action should be a thunk function (redux middleware) that makes the call to Contentful, resolves the promise, and contains the data, or error. Then you can dispatch another action, or actions to reduce the data into the store.
On each component that you want to re-render (based on the data being updated in the store via the actions we just dispatched and reduced), if you have connect(mapStateToProps, mapDispatchToProps) and have included those parts of the store in MSTP, those props will be updated which will re-render the components.
You can even be more explicit about the resolution of data if necessary by creating another action where you can dispatch and reduce to some part of your store the current state of the fetch.
So when you make the call, you could dispatch 'FETCH_IN_PROGRESS', then either 'FETCH_ERROR' or 'FETCH_SUCCESS' and if that was mapStateToProps into your component, you could choose to evaluate it in shouldComponentUpdate() and based on where in the process it is, you could either return true or false based on if you wanted to rerender. You could also force render in componentWillReceiveProps. I'd start with just relying on props changing and adding this if necessary.
You should use Redux Persist for this kind of thing, it supports AsyncStorage and a range of other options.
https://github.com/rt2zz/redux-persist
Actions and Reducers should be just designed to update the Redux store. Any other action is known as a side effect, and should be managed in a Middleware or Store Enhancer.
I would strongly advise against using Redux-Thunk it is way too powerful for the few things that it is useful for and very easy to create unmaintainable anti-patten code as it blurs the boundaries between actions and middleware code.
If you think you need to use Redux-Thunk first look to see if their is already a middleware that does what you need and if not learn about Redux-Sagas.
I have an action creator that is called from my React component:
// ...
import { connect } from 'react-redux';
// ...
import { submitProfile } from '../actions/index';
// ...
onSubmit() {
const profile = {
name: this.state.name
// ...
};
this.props.submitProfile(profile)
.then(() => { // I keep getting an error here saying cannot read property 'then' of undefined...
console.log("Profile submitted. Redirecting to another scene.");
this.props.navigator.push({ ... });
});
}
export default connect(mapStateToProps, { submitProfile })(MyComponent);
The definition of the action creator is something like the following. Note I am using the redux-thunk middleware.
export function submitProfile(profile) {
return dispatch => {
axios.post(`some_url`, profile)
.then(response => {
console.log("Profile submission request was successful!");
dispatch({ ... }); // dispatch some action
// this doesn't seem to do anything . . .
return Promise.resolve();
})
.catch(error => {
console.log(error.response.data.error);
});
};
}
What I want to be able to do is call the action creator to submit the profile and then after that request was successful, push a new route into the navigator from my component. I just want to be able to determine that the post request was successful so I can push the route; otherwise, I would not push anything, but say an error occurred, try again.
I looked up online and found Promise.resolve(), but it doesn't not seem to solve my problem. I know that I could just do a .then after calling an action creator if I was using the redux-promise middleware. How do I do it with redux-thunk?
The return value from the function defined as the thunk will be returned. So the axios request must be returned from the thunk in order for things to work out properly.
export function submitProfile(profile) {
return dispatch => {
return axios.post(`some_url`, profile) // don't forget the return here
.then(response => {
console.log("Profile submission request was successful!");
dispatch({ ... }); // dispatch some action
return Promise.resolve();
})
.catch(error => {
console.log(error.response.data.error);
});
};
}