undefined within the react component - but not in action - reactjs

Have a call to action (backend call) response is some array - getting undefined here - in few other cases where I am mapping over the array I am seeing map of undefined error as well. Is componentDidMount right place to put such calls?
I get the response back when I console log in action creator before dispatching.
componentDidMount() {
this.props.fetchData(test, foo);
console.log(this.props.responseData); //getting here undefined
}
function mapStateToProps(state) {
return {
responseData: state.abc.responseData,
};
}
Mycomp.propTypes = {
responseData: React.PropTypes.array,
fetchData: React.PropTypes.func,
};
export default connect(mapStateToProps, actions)(Mycomp);

It is undefined because the request has not been made at the time you're trying to log the response – it's async.
You can either call the action in here:
componentDidMount() {
this.props.fetchData(test, foo);
}
componentWillReceiveProps(props) {
if (props.responseData) { console.log(props.responseData); } // should be getting the data if the request works
}
or define a callback that is executed after the request was successful.

I think your fetchData() is returning Promise which is async
So you can get data from componentWillReceiveProps() which is called when ever you have new props.
componentWillReceiveProps(nextProps){
console.log(nextProps.responseData); //do whatever you want with response
}
Since componentDidMount() is called at only once. It's not reliable place to do something with your props.

Related

Why does my axios call return undefined even after then is resolved?

I'm currently using functions to predefine all of my axios calls so for example:
export const getClients = () => {
axios.get("/client/")
.then(response=>{
return response;
})
.catch(error=>{
return error;
});
};
Now, I want to call this in a class-based component in the componentDidMount like this:
componentDidMount(){
this.setState({
clients: getClients()
});
}
I can't seem to figure out why when I try to console.log(this.state.clients) at the end of componentDidMount I'm getting an undefined error. I'm new to React and from what I understand, the then in the function of the axios call should resolve the promise and return the actual response from the API call so when I call getClients(), the clients state should be the response.
What am I doing wrong?
componentDidMount(){
fetchClients();
}
const fetchClients = () => {
getClients().then( (response)=> {
// handle success
this.setState({clients:response});
});
};
Okay there is some stuff that needs to be cleared out here :-
You need to change getClients like so :-
export const getClients = () => {
return axios.get("/client/")
.then(response=>{
return response;
})
.catch(error=>{
return error;
});
};
Why ?
Because the value that you returned from the callback of then is wrapped inside a Promise implicitly and has to be returned for consumption as you do in a function like function sum(a,b) {return a+b}
componentDidMount will change like so :-
componentDidMount(){
const fetchClients = async () => {
const clients = await getClients();
this.setState({clients});
}
fetchClients();
}
Why ?
If you want to use getClients in a statement fashion instead of .then().then() promise chain, you will first wrap it inside an async function and then call await on getClients() (remember this function returns a Promise) and then set the state inside that async function.
Even if you console.log the clients state after fetchClients() within componentDidMount, you probably won't see the value set because setState works asynchronously. So never rely on the console.log of your state just after setting it.

How to wait for a saga to finish calling an api to execute a next function?

I have a question regarding the use of sagas.
I have a button that when clicked, triggers a function that calls an action:
Component.js
onClickChainIdentifier = (event) => {
//action called
this.props.getChains();
//next function to be called
this.teste();
}
}
Action.js
export function getChains(){
return {
type: GET_CHAINS,
}
}
When this action is dispatched, it fires a constant GET_CHAINS, which calls a saga:
Saga.js
export function* getAllChains() {
const requestURL = process.env.PATH_API.GET_CHAINS;
try {
const response = yield call(requestGet, requestURL);
yield put(getChainsSuccess(response));
} catch (err) {
yield put(getChainsError(err));
}
}
export default function* sagasApp() {
yield [
fork( takeLatest, GET_CHAINS, getAllChains ),
]
}
I would like that after the api return (of success or error), I could call the this.teste function that is inside the component.
How do I make this happen?
Thanks in advance for your help.
You could pass a callback to your getAllChains function:
onClickChainIdentifier = (event) => {
this.props.getChains(() => {
this.teste();
});
}
export function* getAllChains(callback) {
const requestURL = process.env.PATH_API.GET_CHAINS;
try {
const response = yield call(requestGet, requestURL);
yield put(getChainsSuccess(response));
if (callback) {
callback();
}
} catch (err) {
yield put(getChainsError(err));
}
}
You can use flags in order to control when and if your components should render. This is a common solution for rendering a fallback UI (e.g: a spinner or a text) in order to wait until an async process (saga, thunk, API service etc) is finished and the component has all it needs to render itself.
Check the solution I have posted here, you can visit this CodeSandBox which shows how you can use flags in order to solve it.
As jank pointed out, you can test component's state in the lifecycle methods and call a function when some condition is true. For example leveraging jank's example:
componentDidUpdate (prevProps) {
if (this.props.pending && !prevProps.pending) {
this.props.test()
}
}
Will call test every time the pending prop is changed from false to true. The test function can have side effects like fetching from server or using some browser API. Same functionality can be achieved using the newer useEffect of the Hooks API.

React Meteor How to execute a function after withTracker data arrives?

I am publishing data from server and catching it using withTracker.
export default withTracker(() => {
let towndatasub = Meteor.subscribe("userTownDataPublisher",Meteor.userId());
let resourcedatasub = Meteor.subscribe("userResourcePublisher",Meteor.userId());
return{
townData : Towns.find({"ownerId":Meteor.userId()}).fetch(),
resourceData : Resources.find({"ownerId":Meteor.userId()}).fetch()
}
})(TownPage);
The problem is i would like to run a function when townData and resourceData arrives.If i call updateResources in componentDidMount i get undefined on this.props.componentWillReceive props not called.townData and this.props.resourceData
updateResources = () =>{
Meteor.call("updateUserResources",Meteor.userId(),(err,result)=>{
if(err){
console.log(err)
}else{
console.log("asdasd");
//console.log(this.props.resourceData); undefined
// here i will do something with this.props.resourceData
}
})
}
So where should i call updateResources function to not get undefined ?
Firstly, componentDidMount is only called once when a page is loaded, right after the first call to render finishes. Therefore, you shouldn't call updateResources there since there's a chance that the collections haven't finished loading from the server by then. I would recommend calling it in render because render will be called once before the data has arrived and again after the data arrives.
Secondly, if you want to be even more accurate with when the data arrives, you can return two more properties in withTracker involving the ready function like so,
export default withTracker(() => {
let towndatasub = Meteor.subscribe("userTownDataPublisher",Meteor.userId());
let resourcedatasub = Meteor.subscribe("userResourcePublisher",Meteor.userId());
return{
townData : Towns.find({"ownerId":Meteor.userId()}).fetch(),
resourceData : Resources.find({"ownerId":Meteor.userId()}).fetch(),
townsReady : towndatasub.ready(),
resourcesReady : resourcedatasub.ready()
}
})(TownPage);
And then in render, you can call updateResources only when the data has arrived,
if(this.props.townsReady && this.props.resourcesReady) {
this.updateResources();
}

dispatching an action returns undefined in callback

In my parent component, I have :
getCatalogItems(id = 12, sessionId = "anythingForNow", filterBy = []) {
return this.props.actions.getCatalogInfo(id, sessionId, filterBy)
.then(catalogItemsReturned=> {
console.log("CATALOG ITEMS RETURED", catalogItemsReturned) //always undefined
})
}
componentWillMount() {
console.log("component will mount called")
this.getCatalogItems();
}
render function of this component gets called like 5-6 times for some reason even when I am not changing any state at all.
I pass result from the action to this component as a prop ( I am using redux) through map state to props and the result from this triggered actions becomes available in render function when it calls itself for 3rd or 4th time.
What is the reason? How could I fix it?
My triggered action is getting the result through API call, but for some reason, I am not getting it in then part of dispatch action in getCatalogItems function
This is what my action looks like: Anything wrong with this?
export function getCatalogItemsSuccess(catalogItems) {
return {
type: types.LOAD_CATALOG_SUCCESS,
catalogItems //courses:courses
}
}
export function getCatalogInfo(id = 12, sessionId="anythingForNow", filterBy = []){
return function (dispatch) {
return catalogProductsAPI.getProductsByCategoryId(id, sessionId, filterBy)
.then(
catalogItems =>{
console.log("catalog INFO in actinnnnnnnnnnnnnnnnn", catalogItems) // I get the right results here
dispatch(getCatalogItemsSuccess(catalogItems));
}).catch(err=>{
console.log("errorin get catalog", err);
throw(err);
}); //the function from API would return a promise
};
}

ReactNative: this.setState Error: null is not an object

React script
class TransactionsList extends Component {
constructor(props) {
super(props);
this.state = {
activeAccountId: "",
accessToken: "",
TransactionsData: "",
};
}
replaceRoute(route, passProps) {
this.props.replaceRoute(route, passProps);
}
async _getToken() {
try {
let accessToken = await AsyncStorage.getItem('AUTH_TOKEN');
if(!accessToken) {
this.replaceRoute('login');
} else {
this.setState({accessToken: accessToken})
}
} catch(error) {
Alert.alert('Print Errorr', error.message)
this.replaceRoute('login');
}
}
componentWillMount(){
this._getToken()
let token = 'Token '+this.state.accessToken
this.load_data(token)
}
render() {
return (
<Container>
// other code
</Container>
)
}
}
Got error in setState in getToken below is catch(error) block output
Print Error null is not an object(evaluating
prevComponentInstance._currentElement)
But same above code works in other screens.
It is not advisable to make api calls in componentWillMount because it is possible that the component will not have been mounted when the api call has finished and you call setState.
Instead, you should make api calls in componentDidMount. According to the documentation:
componentDidMount() is invoked immediately after a component is
mounted. Initialization that requires DOM nodes should go here. If you
need to load data from a remote endpoint, this is a good place to
instantiate the network request. Setting state in this method will
trigger a re-rendering.
And, you also need to bind _getToken as #Jazib mentioned.
You need to bind _getToken method using something like this:
this._getToken().bind(this)
Or you can do this in the constructor for better code (I prefer this one):
constructor(props) {
super(props);
this.state = {
activeAccountId: "",
accessToken: "",
TransactionsData: "",
};
this._getToken() = this._getToken().bind(this)
}
Hope this helps
I know I am replying a bit late but a better way is to use an arrow function instead of using bind on a named function. So you could write your _getToken method like this:
const _getToken = async () => {
// your logic here
}
The arrow function here implicitly assigns the current instance of the component to this keyword whereas in the named function you have to give the this keyword the context by using bind method as mentioned by others.
Also, componentWillMount is now deprecated and its better if you call your method in componentDidMount

Resources