How to call api after screen is loaded react native - react-native

I am developing app using react native, and i have api call that retrieve json. How i can call another api in home screen immediately after it is loaded, this api i want to use for storing json data in order to use it in another screen, because i do not want the user to wait if he clicks.
The home.js that display the home screen is :
export class HomeScreen extends PureComponent {
constructor(props) {
super(props);
this.state = {
isLoading: true,
data: null,
isError: false,
}
}
componentDidMount() {
getArticles().then(data => {
this.setState({
isLoading: false,
data: data
})
}, error => {
Alert.alert("Error", "Something happend, please try again")
})
}
The function that gets articles:
export async function getArticles(){
try {
let articles = await fetch(``);
let result = await articles.json();
return result;
} catch (error) {
throw error
}
}
Can you recommend also tutorials or docs about cashing json response ?.

You can use any of these 3 third-party libraries to cache the response
[Redux] https://redux.js.org/introduction/getting-started
[React Native Async Storage] https://github.com/react-native-community/async-storage
[Context API] https://www.taniarascia.com/using-context-api-in-react/
You can call as many APIs in the componentDidMount. But if the second API is dependent on the first API then call second API in the then function

Related

React Native: AWS Amplify Auth.signOut() not working

React Native noob here, trying to implement AWS Amplify authentication flow into my project. But the Auth.signOut() function is not working at all. Nothing happens when I press the logout button.
const onLogOutPressed = async () => {
//Auth.signOut();
try {
await Auth.signOut({ global: true });
} catch (error) {
console.log('Error Logging Out', error);
}
}
My first version was purely Auth.signOut() as that was what the tutorial I was following did. Another guide suggested using the try method so I commented the first Auth function and added the rest.
Some help would be greatly appreciated. Do let me know if more info is required, thanks in advance.
If you're using React Native UI from Amplify and wrapped your component with withAuthenticator, below code should work.
class App extends React.Component {
constructor(props) {
super(props);
};
...
Auth.signOut().then(() => {
this.props.onStateChange(("signedOut");
});
}
// Functional component
const App = (props) => {
...
Auth.signOut().then(() => {
this.props.onStateChange("signedOut", null);
})
}
If you're not using React Native UI, then you need to handle redirection yourself. Auth.signOut() will only clear session.

How to start rendering before json response is completed react native

I am developing news app using react native, and the problem is the startup time is taking too much time (around 3 seconds to load initially and 5 seconds for the home screen to show up).
I have 36 articles and the json response is big(3000 lines). After analyzing i found that the reason is the waiting time of resolving the function promise that get the articles(after .then).
The function that gets articles:
export async function getArticles(){
try {
let articles = await fetch(``);
let result = await articles.json();
return result;
} catch (error) {
throw error
}
}
The home.js that display the articles is :
export class HomeScreen extends PureComponent {
constructor(props) {
super(props);
this.state = {
isLoading: true,
data: null,
isError: false,
}
}
componentDidMount() {
getArticles().then(data => {
this.setState({
isLoading: false,
data: data
})
}, error => {
Alert.alert("Error", "Something happend, please try again")
})
}
I tried to simplify the response body by extracting specific fields but there was no difference, and I dont know how to fetch only simple number of articles from the url itself like per_page or something else ... .
Can you advice how to start rendering before waiting the json response to complete?.

Dispatching Redux Data and Get the State

I stuck on Redux Implementation during developing an app using React Native and Redux. I do this for the first time and followed this example.
I've already installed Redux and React Native Navigation. I would like to save the state containing data for countries (the user picked a country and would like to keep the choice by the time when it browses to all screens).
Good. I've created a component that could be seen to all screens like this:
LinksScreen.navigationOptions = {
headerTitle: 'Links',
headerRight: <CountriesPickButton/>,
};
Next, I visualize the button and wait for a change in the component. By default, it should show primary country. Next, the user clicks on the button and it opens a modal where has a dropdown menu. For example, I show you the default fetching a country:
import { connect } from 'react-redux'
import store from '../../redux/countries'
export default class CountriesPick extends Component {
render() {.... // here is the button and modal, etc. It's work.
}
constructor(props, context) {
super(props, context);
this.state = store.getState();
store.subscribe(() => {
this.setState(store.getState());
});
this.defaultCountry(251);
}
async defaultCountry(countryId) {
return fetch(URL)
.then((response) => response.json())
.then((responseJson) => {
for (const key of Object.keys(responseJson.result)) {
// this works for current screen: this.setState({ defaultCountry: responseJson.result[key], selectedCountry: responseJson.result[key].country_id });
store.dispatch({ defaultCountry: responseJson.result[key], selectedCountry: responseJson.result[key].country_id , type: 'countries' });
}
return responseJson.result;
})
.catch((error) => {
console.error(error);
});
state = {
showModal: false,
countries: [],
selectedCountry: 0,
defaultCountry: [],
type: 'countries'
};
}
Without store.dispatch({}) I can change the state with the country but it has not to share between screens. That's because I started with Redux.
Here is the Redux code ():
import { createStore } from 'redux'
const defaultState = {
showModal: false,
countries: [],
selectedCountry: 0,
defaultCountry: [],
type: 'countries'
};
function store(state = defaultState) {
return state;
}
export default createStore(store);
Something is not like it should be. When I invoke store.dispatch({...}) it's not changing the state, it returns the default array. I guess I should use <Provider></Provider> in App.js to catch every change but first, I need to understand what I wrong?
Is it connected at all? In the example that I followed, I did not see connect(). Also, I'm not sure I'm using type properly.
Thank you in advance.
Problems here are the following:
Example on the link you provided is bad to say the least. Do not follow it
You said to be using react-native-navigation, but the code you provided comes from react-navigation. I suggest using the latter, especially for starters
Your createStore code is not going to work, as reducer for the store should be a function of state and action
With that being said, you should definitely see Basic Tutorial of redux with examples. You will almost never have to do store.getState() or store.dispatch() while using react with redux, as react-reduxpackage (included in the tutorial I linked) will do this for you. You will instead declare dependency between your store state and props your component receives

React-native passing props from App.js to other files

I'm trying to pass device token from my App.js to my Login component in react-native.
I'm trying something like this :
Here's my app.js :
const RootStack = createStackNavigator(
{
Login: {
screen: Login,
navigationOptions :{ headerLeft: null}
},
Tab: {
screen: Tab,
navigationOptions :{ headerLeft: null }
}
},
{
initialRouteName: 'LoginScreen'
}
);
const MyApp = createAppContainer(RootStack);
constructor(props) {
super(props);
this.state = {
token: ''
}
}
async componentDidMount() {
this.state.token = await firebase.messaging().getToken().then(token=> { return token;});
this.checkPermission();
this.createNotificationListeners();
}
render() {
return (
<MyApp token={this.state.token}></MyApp>
);
}
And my Login.js :
export default class LoginScreen extends React.Component {
constructor(props) {
super(props);
this.state = {
mail:"",
password: "",
token: this.props.token
}
}
async Login(){
console.log(this.state.token)
}
}
Of course it's not working, I don't know how to pass the token via components, or via stacknavigator without using .navigate(). Besides even if I fill the const with a single string, it's not working, so what am I doind wrong ? And is it going to be different with the token ?
screenProps
You need to use screenProps. You should check the documentation to see more about it.
This is how I would use it in your case.
<MyApp screenProps={{ token: this.state.token}} />
Then you can access it in your screens using this.props.screenProps.token.
Here is a snack that I created that shows passing values through a navigator. https://snack.expo.io/#andypandy/passing-props-through-a-navigator
In your current LoginScreen you are trying to set the value of the token in the state. Bear in mind, your page may be constructed before the token is created so the initial value for the token may not exist so you may prefer to capture the value in componentDidUpdate, see the docs for more.
Alternatively you could store the token in AsyncStorage.
Firebase
When getting your token from Firebase, you are mixing promises and async/await notation. Choose one notation and stick with it.
You are setting your state incorrectly. You are mutating state with your line
this.state.token = await firebase.messaging().getToken().then(token=> { return token;});
You should not mutate state as it can get overwritten, and it can cause side effects that you don't expect. You should use this.setState({token}); to set your token value into state.
This is how I would refactor your componentDidMount
async componentDidMount() {
// await functions can throw so always wrap them in a try/catch
try {
// get the token from firebase https://rnfirebase.io/docs/v5.x.x/messaging/device-token
let token = await firebase.messaging().getToken();
if (token) {
// if we have a token then set the state and use a callback
// once the state has been the callback calls checkPermissions and createNotificationListeners
this.setState({token}, () => {
this.checkPermission();
this.createNotificationListeners();
});
}
} catch (err) {
console.log(err);
}
}
Additional reading
The following articles by Michael Chan are a great introduction into state.
https://medium.learnreact.com/setstate-is-asynchronous-52ead919a3f0
https://medium.learnreact.com/setstate-takes-a-callback-1f71ad5d2296
https://medium.learnreact.com/setstate-takes-a-function-56eb940f84b6
The following article shows the differences between promises and async/await
https://medium.com/#bluepnume/learn-about-promises-before-you-start-using-async-await-eb148164a9c8

Validate React Native Component with Asynchronous Work

I have a basic component that calls a webservice during the componentDidMount phase and overwrites the contents value in my state:
import React, {Component} from 'react';
import {Text} from "react-native";
class Widget extends Component {
constructor() {
super();
this.state = {
contents: 'Loading...'
}
}
async componentDidMount() {
this.setState(...this.state, {
contents: await this.getSomeContent()
});
}
render() {
return (
<Text>{this.state.contents}</Text>
)
}
async getSomeContent() {
try {
return await (await fetch("http://someurl.com")).text()
} catch (error) {
return "There was an error";
}
}
}
export default Widget;
I would like to use Jest snapshots to capture the state of my component in each one of the following scenarios:
Loading
Success
Error
The problem is that I have to introduce flaky pausing to validate the state of the component.
For example, to see the success state, you must place a small pause after rendering the component to give the setState method a chance to catch up:
test('loading state', async () => {
fetchMock.get('*', 'Some Content');
let widget = renderer.create(<Widget />);
// --- Pause Here ---
await new Promise(resolve => setTimeout(resolve, 100));
expect(widget.toJSON()).toMatchSnapshot();
});
I'm looking for the best way to overcome the asynchronicity in my test cases so that I can properly validate the snapshot of each state.
If you move the asynchronous call out of setState, you can delay setState until the network call has resolved. Then you can use setState's optional callback (which fires after the state change) to capture the state.
So, something like this:
async componentDidMount() {
var result = await this.getSomeContent()
this.setState(...this.state, {
contents: result
},
// setState callback- fires when state changes are complete.
()=>expect(this.toJSON()).toMatchSnapshot()
);
}
UPDATE:
If you want to specify the validation outside of the component, you could create a prop, say, stateValidation to pass in a the validation function:
jest('loading state', async () => {
fetchMock.get('*', 'Some Content');
jestValidation = () => expect(widget.toJSON()).toMatchSnapshot();
let widget = renderer.create(<Widget stateValidaton={jestValidation}/>);
});
then use the prop in the component:
async componentDidMount() {
var result = await this.getSomeContent()
this.setState(...this.state, {
contents: result
},
// setState callback- fires when state changes are complete.
this.props.stateValidaton
);
}