How can i render app screen again after componentdidMount - reactjs

I have 3 screens in my react native app:
Settings - [button ()=>this.props.navigation.navigate("Payments) ]
Payments - In this screen I fetch data from API with componentDidMount and I API sends back credit card info it will be rendered in screen, it no data came back [Button ()=>this.props.navigation.navigate("Add Credit Card")
Add Credit Card : In this screen users adds credit cards and sent it to server and goes back to Payments screen.
Here is the issue:
When I go from screen 1 to 2 (2 renders and does API call in componentDidMount), but when I go back from screen 3 to 2 (2 does not render and does not do any API call in componentDidMount)
In order to render the newly added credit card I need to go from Screen 3 to 1 then to 2.
How can I render screen 2?
Here is my screen 2 componentDidMount:
componentDidMount() {
this.setState({ ...this.state, activity: true });
Axios.get(GETCARDLIST_API, {
headers: {
appversion: 1.4,
apisecret: this.props.api_secret
}
})
.then(response => {
this.setState({ ...this.state, activity: false });
if (response.status == 200 && response.data.cards != null) {
this.setState({
...this.state,
showCards: true,
cards: response.data.cards,
isCardPresent: true
//numberOfCards: response.data.cards.length
});
} else {
}
console.log(response);
})
.catch(error => {
console.log({ error });
this.setState({ ...this.state, activity: false });
Alert.alert("Support", error.response.data.error);
});
}
I am using Stack Navigator to switch between screens.

You can listen when the second screen is focused, call the API and render the contents:
componentDidMount() {
this.listener = this.props.navigation.addListener('didFocus', this.getData)
}
componentWillUnmout() {
this.listener.remove();
}
getData = () => {
this.setState({ ...this.state, activity: true });
Axios.get(GETCARDLIST_API, {
headers: {
appversion: 1.4,
apisecret: this.props.api_secret
}
})
.then(response => {
this.setState({ ...this.state, activity: false });
if (response.status == 200 && response.data.cards != null) {
this.setState({
...this.state,
showCards: true,
cards: response.data.cards,
isCardPresent: true
//numberOfCards: response.data.cards.length
});
} else {
}
console.log(response);
})
.catch(error => {
console.log({ error });
this.setState({ ...this.state, activity: false });
Alert.alert("Support", error.response.data.error);
});
}

You could use unmountInactiveRoutes prop:
const stackNavigator = createStackNavigator(
{
stack1: {
screen: Somescreen,
},
stack2: {
screen: Somescreen2,
},
},
{
headerMode: 'none',
initialRouteName: 'stack1',
unmountInactiveRoutes: true,
},
);

componentDidMount(). This method is called once all our children Elements and our Component instances are mounted onto the Native UI and it is only called one time.(run the initial fetch)
if you need to fetch again you should use componentDidUpdate as below
componentDidUpdate(prevProps,prevState) {
if (prevProps.params.id !== this.props.params.id) {
// functon
}
}
Or call the function inside the constructor as below
constructor(props) {
super(props)
this.state = {
};
this.navigationWillFocusListener = props.navigation.addListener('willFocus', async ()=> {
// fetch()
});
}
Feel free for doubts

Your API call is Async function, componentdidMount call your API but your render function invoke before the response of API result. If you put a break-point on componentdidMount function and render function (view), you see the working of these functions.
Solution: you call your API method on screen 3 after adding card before to navigate Screen 2 and store your data in a global variable and render it. please also read react-native official docs, here is the link: https://reactnative.dev/docs/network#using-fetch

Related

React: how to fix this spinner?

I have a spinner but it works while the page is rendering and I also need it to work when I click the LoadMore button, I don't understand how
Here is the link Spinner
You can call this.setState with setting status to pending in first lines of loadMore method, or better call it in fetchImageApi, just like this:
fetchImageApi = () => {
const { searchbar, page } = this.state;
this.setState({status: 'pending'}, () => {
imageApi(searchbar, page)
.then((images) => {
if (images.total === 0) {
this.setState({ error: "No any picture", status: "rejected" });
} else {
this.setState((prevState) => ({
result: [...prevState.result, ...images.hits],
status: "resolved",
page: prevState.page + 1,
searchbar: searchbar,
}));
}
})
.catch((error) => this.setState({ error, status: "rejected" }));
}
};
There is callback as a second argument of setState method, which means that your callback code will be executed right after react updates its state, so you don't have to write await logic for your code.

React - get data with axios

In my react app that is based on class components, My response API got from open weather fixes after several lags.
this is my state
class Weather extends Component {
constructor(props) {
super(props);
this.state = {
weatherData: undefined,
weatherDescription: undefined,
};
}
My thinking was that when my componentDidMount,
weather API getting from openWeather and set it in state
componentDidMount() {
axios
.get(
`http://api.openweathermap.org/data/2.5/weather?id=someCityId&units=metric&appid=myApiKey`
)
.then((response) => {
if (response.request.status === 200) {
this.setState({
weatherData: response.data.main.temp,
weatherDescription: response.data.weather[0].description,
weatherTextDisplay: this.state.airConditionsText.filter((item)=>{
return item["id"] === response.data.weather[0].id
})
});
}else{throw Error('No internet')}
})
.catch(error => Error.message)
and I want to update data when the city is changing, in componentDidUpdate the data get again from the openWeather
componentDidUpdate() {
axios
.get(
`http://api.openweathermap.org/data/2.5/weather?id=someCityId&units=metric&appid=myApiKey`
)
.then((response) => {
if (response.request.status === 200) {
this.setState({
weatherData: response.data.main.temp,
weatherDescription: response.data.weather[0].description,
weatherTextDisplay: this.state.airConditionsText.filter((item)=>{
return item["id"] === response.data.weather[0].id
})
});
}else{throw Error('No internet')}
})
.catch(error => Error.message)
}
But the problem is that when my response receives, it faces a lag that causes data jumps several times to previous data and new data until it fixes
I do not completely understand the question, but this 'lags' because the action of fetching something from an external source is async and needs time to complete.
As for the second 'part' of displaying the loading text you have to set a variable (preferably in state which indicates the loading state of this component)
eg.
constructor(props) {
super(props);
this.state = {
loading: false,
airConditionsText: null,
// Other stuff you have in state
};
}
componentDidUpdate() {
this.setState({loading: true}) // Start of loading
axios
.get(
`http://api.openweathermap.org/data/2.5/weather?id=${this.state.inputId}&units=metric&appid=myApiKey`
)
.then((response) => {
if (response.request.status === 200) {
this.setState({
weatherData: response.data.main.temp,
weatherDescription: response.data.weather[0].description,
weatherTextDisplay: this.state.airConditionsText.filter((item)=>{
return item["id"] === response.data.weather[0].id
})
});
}else{throw Error('No internet')}
})
.catch(error => Error.message)
.finally(() => this.setState({loading: false})) // End of loading
.finally is being trigger once the async operation (fetching the data from weatherAPI) finishes with either error or success which is the time to stop loading.
Then you can use this.state.loading in component render to show loading text
eg.
render() {
return (
<div>
{this.state.loading
? <div> Loading... </div>
: <div>{this.state.airConditionsText}</div> // other stuff you want to display
}
</div>
);
}

Unable to update a state variable twice in same function for showing/hiding loader

I am trying to show a loader when a button is clicked service is invoked and to hide that loader once the data is loaded. But somehow i am not able to achieve this. Pretty sure its a dumb mistake but unfortunately i am not able to locate. Please find below a part of the code.
/* button click function, this function will be carried to a child component, which will invoke once the button in child component is clicked */
constructor(props) {
super(props);
this.state = {
loadingPoints: false
}
}
searchWithChosenFilter = (data) => {
this.setState(state => { // THIS IS NOT WORKING
return {
...this.state,
loadingPoints: true
}
})
Service.someService()
.then(response => {
this.setState(state => { // THIS IS WORKING
return {
...this.state,
loadingPoints: false
}
});
})
.catch(error => {
this.setState({
...this.state,
loadingPoints: false
});
})
}

Why is a Component method using fetch and settingState in response not causing a componentDidMount()?

I want to generate a menu dynamically depending on user connected state and user role. I have a json file from which feeds the React app with all menu choices. The problem is that it does offer the "login" and "contact" options, which don't require any specific role or for a user to be logged in, but when I log in with the App's login() method, in which I use the fetch API and set the new state in the response, it doesn't refresh the menu choices (which is done in componentDidMount() method. It keeps serving me the login and contact options. I want to switch the login for logout when a user is connected.
I tried a bunch of stuff, but putting logs and debuggers in my code, I noticed the component doesn't re-render after the setState that's called in the login() fetch operation, but the state is indeed getting changed. I'm curious as to why the setState is not firing componentDidMount()?
menu.json
[
{
"txt": "login",
"idRole": null
},
{
"txt": "logout",
"idRole": null
},
{
"txt": "register",
"idRole": [
1
]
},
{
"txt": "profile",
"idRole": [
1,
2,
3
]
},
{
"txt": "contact",
"idRole": null
}
]
App.js
import React, { Component } from 'react'
import Header from 'container/Header.js'
import Footer from './container/Footer'
import Login from './container/Login'
import menu from '../json-form-file/menu.json'
export default class App extends Component {
constructor (props) {
super(props)
this.state = {
isMenuOpen: false,
isLoggedIn: false,
menu: null,
page: null,
user: null
}
this.toggleMenu = this.toggleMenu.bind(this)
this.selectPage = this.selectPage.bind(this)
this.login = this.login.bind(this)
this.logout = this.logout.bind(this)
}
toggleMenu () {
this.setState({ isMenuOpen: !this.state.isMenuOpen })
}
selectPage (event) {
this.setState({ isMenuOpen: !this.state.isMenuOpen, page: event.target.textContent })
const toggler = document.getElementsByClassName('toggler')[0]
toggler.checked = !toggler.checked
}
login (event) {
event.preventDefault()
const requestBody = createLoginRequestBody(Array.from(event.target.parentElement.children))
clearLoginFields(Array.from(event.target.parentElement.children))
if (requestBody.username !== undefined && requestBody.pwd !== undefined) {
fetch('www.someLoginUrl.login', {
method: 'post',
body: JSON.stringify(requestBody),
headers: { 'Content-Type': 'application/json'
}
})
.then(response => response.json())
.then(response => this.setState({ user: response, isLoggedIn: true, page: null }))
}
}
logout (event) {
event.preventDefault()
const toggler = document.getElementsByClassName('toggler')[0]
toggler.checked = !toggler.checked
this.setState({ user: null, isLoggedIn: false, page: null, isMenuOpen: !this.state.isMenuOpen })
}
componentDidMount () {
console.log('im mounting')
const newMenu = this.refreshMenuSelection(menu)
this.setState({ menu: newMenu })
}
refreshMenuSelection (list) {
const newMenu = []
list.map((item) => {
if (item.txt === 'login' && this.state.isLoggedIn === false) newMenu.push(item)
if (item.txt === 'logout' && this.state.isLoggedIn === true) newMenu.push(item)
if (item.idRole === null && item.txt !== 'login' && item.txt !== 'logout') newMenu.push(item)
if (this.state.user !== null && item.idRole.includes(this.state.user.id_role)) newMenu.push(item)
})
return newMenu
}
render () {
return (
<div>
<Header
menu={this.state.menu}
toggleMenu={this.toggleMenu}
selectPage={this.selectPage}
logout={this.logout}
color={this.state.isMenuOpen ? secondaryColor : primaryColor} />
{this.state.page === 'login' ? <Login login={this.login} /> : null}
<Footer color={this.state.isMenuOpen ? secondaryColor : primaryColor} />
</div>
)
}
}
const createLoginRequestBody = (inputs) => {
const requestObject = {}
inputs.map((input) => {
if (input.id === 'username') Object.assign(requestObject, { username: input.value })
if (input.id === 'pwd') Object.assign(requestObject, { pwd: input.value })
})
return requestObject
}
When a user is not logged in, he could see only login and contact. When logged in, he could see logout instead of login, contact and all other choices relevant to his role.
Nothing causes a componentDidMount to run again, it's a lifecycle hook which runs only one time through the component's lifecycle. Everything that goes inside componentDidMount will only run once (after the first render), so if you need to react to a change to perform imperative code, take a look at componentDidUpdate. You should also take a look in the documentation
As said, componentDidMount only runs once when the component is first mounted. If you use setState() after the first mount, the function will not respond to it. If you want to do that, maybe you ought to use componentDidUpdate which does react to this type of change. And also, there's another thing wrong with your code. If you use setState() and use componentDidUpdate then it will change the state again and calling the function again until the program crashes. So if you don't want to cause that, maybe also remove that or move it to a new componentDidMount function.
Thanks to everyone who guided me to the componentDidUpdate() method. This modified bit of code helped me achieve what I wanted. In the future I'll clean up the code to remove the justLoggedIn, as that might not be necessary, but without that it was giving my the setState depth error.
login (event) {
event.preventDefault()
const requestBody = createLoginRequestBody(Array.from(event.target.parentElement.children))
clearLoginFields(Array.from(event.target.parentElement.children))
if (requestBody.username !== undefined && requestBody.pwd !== undefined) {
fetch('http://127.0.0.1:8080/user/login', {
method: 'post',
body: JSON.stringify(requestBody),
headers: { 'Content-Type': 'application/json'
}
})
.then(response => response.json())
.then(response => this.setState({ user: response, justLoggedIn: true, isLoggedIn: true }))
}
}
logout (event) {
event.preventDefault()
const toggler = document.getElementsByClassName('toggler')[0]
toggler.checked = !toggler.checked
this.setState({ user: null, justLoggedOut: true, isLoggedIn: false, isMenuOpen: !this.state.isMenuOpen })
}
componentDidMount () {
if (this.state.user === null) this.setState({ menu: this.refreshMenuSelection(menu) })
}
componentDidUpdate () {
if (this.state.user !== null && this.state.justLoggedIn) this.setState({ menu: this.refreshMenuSelection(menu), justLoggedIn: false, page: null })
if (this.state.user === null && this.state.justLoggedOut) this.setState({ menu: this.refreshMenuSelection(menu), justLoggedOut: false, page: null })
}

ComponentWillReceiveProps is not called when we navigate between stack navigator components?

export default (DrawNav = createStackNavigator(
{
Home: { screen: Home },
QuestionDetail: { screen: QuestionDetail },
QuestionAsk: { screen: QuestionAsk }
},
{
initialRouteName: "Home",
headerMode: "none"
}
));
Home component lists questions and QuestionDetail shows detail information of the questions but here is the problem that i faced, whenever you back to home from QuestionDetail or other component i want to grab the questions and here is what i did in Home component,
componentDidMount() {
this.getQuestions();
}
componentWillReceiveProps() {
this.setState({ questions: [] }, () => {
this.getQuestions();
});
}
getQuestions() {
this.setState({ isLoading: true });
axios.get(`http://${IP_ADDRESS}/api/questions`)
.then(response => {
console.log('response data: ', response.data);
this.setState({ questions: response.data, isLoading: false })
})
.catch((err) => {
this.setState({ isLoading: false });
console.log('QUESTIONS ERR: '+err);
// this.props.history.push('/');
})
}
but componentWillReceiveProps is not called when you navigate from QuestionDetail to Home?
componentWillReceiveProps is triggered only when component prop updates and not on initial render. As the documentation states,
React doesn’t call UNSAFE_componentWillReceiveProps() with initial props during mounting. It only calls this method if some of component’s props may update. Calling this.setState() generally doesn’t trigger UNSAFE_componentWillReceiveProps().
componentWillReceiveProps is deprecated, particularly because it's often misused. For asynchronous actions componentDidMount and componentDidUpdate are supposed to be used instead of componentWillMount and componentWillReceiveProps:
If you need to perform a side effect (for example, data fetching or an animation) in response to a change in props, use componentDidUpdate lifecycle instead.
If same logic is applicable to both hooks, there should be a method to reuse. There's already such method, getQuestions:
componentDidMount() {
this.getQuestions();
}
componentDidUpdate() {
this.getQuestions();
}
getQuestions() {
this.setState({ isLoading: true, questions: [] });
axios.get(`http://${IP_ADDRESS}/api/questions`)
...
}

Resources