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

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`)
...
}

Related

React setState doesn't re-render components

I have a form submit function to call api do something, it will be like:
Form_Submit = (e) => {
//...
Data_Request({
url: "/bill_product_data",
element: this,
data: formData,
cb: (_response) => {
//...
},
fail: (_response) => {
Alert_message('error', _response['message']);
this.setState({
all_cart: _response['data'].data,
}, this.$forceUpdate())
}
})
}
And after the message alert, I expected the component will re-render because all_cart is changed, when I use console.log to print "all_cart" before and after setState, I can see it has been change by setState, but, why the component does not re-render?

REACT JS class Lifecycles: How to put http request in ComponentdidUpdate?

I have a http request to call once the prop taken from redux store updates as shown below:
const mapStateToProps = state => {
console.log(state.queryBuild);
return {
queryBuilderObject: state.queryBuild,
}
}
export default connect(mapStateToProps, null)(SummaryView);
Here is my componentdidupdate function:
async componentDidUpdate()
{
//console.log("Component unmount detected");
//console.log(this.props.queryBuilderObject);
this.setState({state: {
...this.state,
isLoading: true,
}});
await axios.post(ApiEndPoints.getSummaryDataByQueryBuilder,this.props.queryBuilderObject,{timeout: axiosTimeOut})
.then(response => {
console.log("REsponse:");
console.log(response);
this.setState({state: {
...this.state,
isLoading: false,
}});
})
.catch(error => console.log("Error: " + error.message));
}
now here's the problem... somehow I want to only make an http request if props.queryBuilderObject changes that comes from redux store. But when I am going this way, I am entering into an infinite loop as I am setting state and hence componentdidupdate is triggered everytime.
Can someone suggest the right way to do so?
componentDidUpdate receives the previous props and state as arguments, you can check the previous props' queryBuilderObject against the current props' queryBuilderObject and if they are not equal do the POST request.
componentDidUpdate(prevProps, prevState, snapshot)
You may call setState() immediately in componentDidUpdate() but note
that it must be wrapped in a condition
If you update state from this lifecycle function without a conditional check then it will likely cause infinite render looping.
There is also no need to spread in existing state in the setState function; setState does a shallow merge of state updates.
async componentDidUpdate(prevProps) {
if (prevProps.queryBuilderObject !== this.props.queryBuilderObject) {
this.setState({ isLoading: true });
await axios
.post(
ApiEndPoints.getSummaryDataByQueryBuilder,
this.props.queryBuilderObject,
{ timeout: axiosTimeOut }
)
.then((response) => {
this.setState({ isLoading: false });
})
.catch((error) => console.log("Error: " + error.message));
}
}

How can i render app screen again after componentdidMount

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

react -redux component does not re-render after state change

I have been trying and trying by my component wont re-render itself . Below is my reducer code and I have tried everything to not mutate the state. In my component code ,inside render method, I have a log statement console.log("Check Here"); I know the component does not re-render because this log works first time the component renders but after reducer changes the state the log statement is not called . In logs I can clearly see that prev state and next state are different by just that one SearchType that I am changing. Please help!!
const initState = {
searchType: ""
};
const techniqueReducer = (state = initState, action) => {
switch (action.type) {
case actionTypeConstants.GET_SEARCH:
{
return { ...state, searchType: "new string" };
}
default: {
return state;
}
}
};
export default myReducer;
My component code is below
import React, { Component } from "react";
import { connect } from "react-redux";
import * as tDispatchers from "../actions/Actions";
const mapStateToProps = state => {
  return {
  
    searchType: state.searchType
  };
};
class SearchCollection extends Component {
  Search= () => {
    this.props.dispatch(tDispatchers.getSearch(document.getElementById("txtSearch").value));
  }
 
  render() {
console.log("Check Here")
    return (
      <div class="container-fluid">
        <div>
          <input
            type="text"
            id="txtSearch"
            class="form-control"
            placeholder="Enter Search Keywords Here..."
          />
        </div>
        <div>
  <button
            className="btn btn-light btn-sm m-1"
            onClick={this.Search}
          >
            Search
          </button>
         
        </div>
  
      </div>
    );
  }
}
export default connect(mapStateToProps)(SearchCollection);
GetSearch looks like below
I plan to pass payload to reducer eventually but currently I am not
import * as actionTypeConstants from "../action_type_constants";
import axios from "axios";
export function getSearch(searchtext) {
return dispatchFunction => {
axios
.get("<api call>"+searchtext)
.then(response => {
dispatchFunction({
type: actionTypeConstants.GET_SEARCH,
payload: response.data.data
});
})
};
}
ActionTypeConstant
export const GET_SEARCH = "GET_SEARCH";
I suppose you are using redux-thunk to work with async actions. But you don't return an async function from getSearch. I believe it should be
export function getSearch(searchtext) {
return dispatchFunction => {
return axios
.get("<api call>"+searchtext)
.then(response => {
dispatchFunction({
type: actionTypeConstants.GET_SEARCH,
payload: response.data.data
});
})
};
}
or
export function getSearch(searchtext) {
return async dispatchFunction => {
const response = await axios
.get("<api call>"+searchtext);
dispatchFunction({
type: actionTypeConstants.GET_SEARCH,
payload: response.data.data
});
};
}
You are not updating searchType value, which is hardcoded to string new string. Try setting the new state from the action, for example:
return { ...state, searchType: action.payload};
Or check this, https://jsfiddle.net/xt3sqoc6/1/ and open your dev tools to see the rerenders.
You can use componentDidUpdate(prevProps, prevState). It is invoked immediately after updating occurs & you can compare the current props to previous props. Using that you can re-render your component by changing state
componentDidUpdate(prevProps) {
if (this.props.SearchType !== prevProps.SearchType) {
//Do whatever needs to happen!
}
}
You may call setState() immediately in componentDidUpdate but note that it must be wrapped in a condition like in the example above, or you’ll cause an infinite loop.
Hope this helps you. Feel free for doubts.

React accessing state before ComponentDidMount

When I try to access a state variable which is set in ComponentDidMount, react throws an undefined error. This is because I believe when I'm calling the fetch api and setState in ComponentDidMount, the value isn't ready yet (async stuff). Is there a proper way to either delay the render until the setState call is done or some other way to get the state updated fully before render is called?
I think the code below will give you a basic idea how fetch data and render work.
class App extends Component {
state = {
data:{},
loading:true,
error:null,
}
componentDidMount = () => {
fetch('https://example.com/api/article')
.then((response) => {
return response.json();
})
.then((json) => {
this.setState({
data:json,
loading:false,
})
.catch(error => {
this.setState({
error,
loading:false,
})
});
});
}
render() {
const {data,error,loading} = this.state;
if(loading){
return "Loading ..."
}
if(error){
return "Something went wrong."
}
return 'your actual render component or data';
}
}
export default App;

Resources