React Native setState(…) warning with both componentWillMount and componentDidMount - react-native

I'm starting with react-native and in my project I got to a point where everything works but there's this warning:
Warning: setState(...): Can only update a mounted or mounting component.
So, I've looked several QA, tried a few solutions(changing the setState() call from componentWillMount and componentDidMount) but... the warning is always there.
Here is part of the code:
REQUEST_URL = 'http://url/users.php';
(...)
module.exports = React.createClass({
getInitialState: function() {
return {
uid: null,
bid: null,
username: null,
}
},
componentDidMount: function() {
this.fetchData();
},
fetchData: function() {
fetch(REQUEST_URL)
.then( (response) => response.json() )
.then( (json) => {
console.log('setState called');
this.setState({
uid: json.users[0].user_id,
bid: json.users[0].building_id,
username: json.users[0].username
});
})
.done();
},
render: function() {
if (!this.state.uid) { //user is not defined
console.log('not rendered');
return <Text>chargement...</Text>
}
// else
console.log('rendered');
var userId = this.state.uid;
var buildingId = this.state.bid;
var username = this.state.username;
return (
<View style={styles.content}>
<Text style={styles.label}>User Id</Text>
<Text>{userId}</Text>
<Text style={styles.label}>Building Id</Text>
<Text>{buildingId}</Text>
<Text style={styles.label}>Username</Text>
<Text>{username}</Text>
</View>
)
},
});
The users.php returns a json content-type.
Any clues?
Thanx.

The problem may be that react re-mounts certain components multiple times in one render (think that has something to do with the representation of initial values, could not find the question here), therefore your state would be set to a component that is not mounted.
If you set your state in a decoupled timeout that can be cleared when the component unmounts, you avoid setting state on a unmounted component.
componentDidMount() {
this.mounted = true;
// this.fetchTimeout = setTimeout(()=>{
this.fetchData();
// });
},
componentWillUnmount() {
// clearTimeouts(this.fetchTimeout);
this.mounted = false;
},
fetchData() {
fetch(REQUEST_URL)
.then( (response) => response.json() )
.then( (json) => {
console.log('setState called');
if (this.mounted === true){
this.setState({
uid: json.users[0].user_id,
bid: json.users[0].building_id,
username: json.users[0].username
});
}
})
.done();
},
I still don't know if we are supposed to use TimerMixins but this way works without those.
(TimerMixins take care of clearing any timeout or interval set in the component)
EDIT: update sample to only call setState of the component is still mounted.
I do not know if there is a better way, but as far as I know until now you can not cancel a fetch request.

Related

React Native setState doesn't effect immediately data

I am developing a project with react native.After using axios fetch data , then I changed my billingList state using returning data,but after that when I want logged this state,my array is blank.Data is returning but state doesn't change immediately.
axios.get(url, config)
.then(function (response) {
if (response.data.status === true) {
console.log(response.data.data);
setBillingList(response.data.data)
console.log(billingList);
}
})
.catch(function (error) {
console.log(error);
})
}
So using this data in responsive table,table is blank .
import { TableView } from "react-native-responsive-table"
return (
<View>
<TableView
headers={[
{
name: "S.no.",
reference_key: "no",
},
{
name: "Name",
reference_key: "name",
},
{
name: "Age",
reference_key: "age",
},
]}
rows={billingList}
/>
</View>
)
The reason is that state update does not happen immediately. This is because when we call the setState function, the entire component gets re-rendered – so React needs to check what all needs to be changed using the Virtual DOM algorithm and then perform various checks for an efficient update of the UI.
This is the reason you may not get the updated value instantly.
Try putting your axios request in useEffect:
useEffect(() => {
axios.get(url, config)
.then(function (response) {
if (response.data.status === true) {
console.log(response.data.data);
setBillingList(response.data.data)
console.log(billingList);
}
})
.catch(function (error) {
console.log(error);
})
}
}, [])
I have done this multiple times and it should work!

How can i get token in componentDidMount from redux?

I'm trying to add a props inside a componentDidMount from redux.
If i try to log in in to my app with componentDidUpdate i'm able to see the data loaded, but if i close the app and after i try to re open it, i can't see the data.
class Profile extends Component {
constructor(props) {
super(props);
this.state = {
results: []
};
}
componentDidUpdate = () => {
this.getMyWeather();
};
getMyWeather = () => {
const {
getUser: { userDetails }
} = this.props;
axios
.get(
settings.host +
'my_api_url',
{
headers: { Authorization: 'Token ' + userDetails.token },
}
)
.then(({ data }) => {
this.setState({
results: data.results
});
})
.catch(error => alert(error));
};
render() {
return (
<View style={styles.container}>
{this.state.results &&
this.state.results.map((data, index) => (
<Text key={index}>{data.title}</Text>
))}
</View>
);
}
}
let mapStateToProps;
mapStateToProps = state => ({
getUser: state.userReducer.getUser
});
let mapDispatchToProps;
mapDispatchToProps = dispatch => ({
dispatch
});
export default connect(
mapStateToProps,
mapDispatchToProps
)(Profile);
How i can fetch the data also after closing and re-open the app?
Try this way
async componentDidMount() {
// GET request using axios with async/await
const {userDetails} = this.props.getUser; <-- Try this way -->
const data = await this.getMyWeather(userDetails);
this.setState({
results: data
});
}
getMyWeather = async (userDetails) => {
await axios
.get(
settings.host +
'my_api_url',
{
headers: { Authorization: 'Token ' + userDetails.token },
}
)
.then(({ data }) => {
return data.results;
})
.catch(error => alert(error));
};
Why to save to token in your redux in the first place?
personally I save it in local storage it's easy.
as you know redux is a state management of react this is mean when the you close the website the data store in redux die and because of this I think you should save in the local storage so you can get access to it really easy.
If you save the JWT in the DB you just need in the useEffect in the app.js call the action in redux that extract the JWT and save it

Reset useLazyQuery after called once

I'm using useLazyQuery to trigger a query on a button click. After the query is called once, the results (data, error, etc) are passed to the component render on each render. This is problematic for example when the user enters new text input to change what caused the error: the error message keeps reapearing. So I would like to "clear" the query (eg. when user types new data into TextInput) so the query results return to there inital state (everything undefined) and the error message goes away.
I can't find any clear way to do this in the Apollo docs, so how could I do that?
(I though of putting the query in the parent component so it does not update on each rerender, but I'd rather not do that)
This is how I have my component currently setup:
import { useLazyQuery } from 'react-apollo'
// ...
const [inputValue, setInputValue] = useState('')
const [getUserIdFromToken, { called, loading, data, error }] = useLazyQuery(deliveryTokenQuery, {
variables: {
id: inputValue.toUpperCase(),
},
})
useEffect(() => {
if (data && data.deliveryToken) {
onSuccess({
userId: data.deliveryToken.vytal_user_id,
token: inputValue,
})
}
}, [data, inputValue, onSuccess])
// this is called on button tap
const submitToken = async () => {
Keyboard.dismiss()
getUserIdFromToken()
}
// later in the render...
<TextInput
onChangeText={(val) => {
setInputValue(val)
if (called) {
// clean/reset query here? <----------------------
}
})
/>
Thanks #xadm for pointing out the solution: I had to give onCompleted and onError callbacks in useLazyQuery options, and pass the variables to the call function, not in useLazyQuery options. In the end the working code looks like this:
const [inputValue, setInputValue] = useState('')
const [codeError, setCodeError] = useState<string | undefined>()
const [getUserIdFromToken, { loading }] = useLazyQuery(deliveryTokenQuery, {
onCompleted: ({ deliveryToken }) => {
onSuccess({
userId: deliveryToken.vytal_user_id,
token: inputValue,
})
},
onError: (e) => {
if (e.graphQLErrors && e.graphQLErrors[0] === 'DELIVERY_TOKEN_NOT_FOUND') {
return setCodeError('DELIVERY_TOKEN_NOT_FOUND')
}
return setCodeError('UNKNOWN')
},
})
const submitToken = () => {
Keyboard.dismiss()
getUserIdFromToken({
variables: {
id: inputValue
},
})
}

React native _this.state.data.map is not a function

In console, I can get this.state.data in render. Everything looks normal. But I get this.state.data.map is not a function error. What am I doing wrong?
I would be very happy if there is someone who can help. Thanks in advance
export default class ProfileScreen extends Component {
constructor(props) {
super(props);
this.state = {
data: [],
hash: '',
};
}
_retrieveData = async () => {
try {
const value = await AsyncStorage.getItem('token');
console.log(value);
this.setState({
hash: value,
});
} catch (error) {
// Error retrieving data
}
};
getInfo = async () => {
try {
const response = await axios.get('http://yakakartim.net/museb/kollektif/loginInfo?hash=' + this.state.hash);
this.setState({
data: response.data.message
})
} catch (e) {
console.log(e)
}
};
componentDidMount() {
this._retrieveData();
this.getInfo()
}
list = () => {
return this.state.data.map(info => {
return (
<View style={{ margin: 10 }}>
<Text>{info.email}</Text>
</View>
);
});
};
render() {
console.log('render',this.state.data)
console.log('render',this.state.hash)
return <View>{this.list()}</View>;
}
}
This is because you are updating the data variable which is initially an array in state but later in getInfo function you have update it like this
this.setState({
data: response.data.message
})
I dont know what is in "message". But if it is not an array, then map function will not work with "data" as it only works with variables which are iterate-able. I mean which are of array data type.
thanks, the incoming data is not an array. I found the solution like this.
this.setState({
data: [response.data.message]
})

How to create component in render part from fetched API?

I'm trying to fetch data from api and I used componentDidMount lifecycle for that, But I have a list in my view which need to be created from that API, so I use map function for received data to get all items and show in render part, But when I run my code I get
this.state.matchInfo.map in not a function
Please help me to solve this problem, I knew that componentDidMount will run after first render so I create an empty state first and hoped that after fetching data, component will render again and will show my data. but it keeps getting me the error
here is my code:
constructor(props) {
super(props);
this.state = {
userName: '',
userToken: '',
userID: '',
firstTime: true,
loading: true,
showAlert : false,
alertType : true,
alertMessage : '',
animatedMatchBtn : new Animated.Value(1),
matchInfo: []
};
}
componentDidMount() {
this._bootstrapAsync(true);
}
_bootstrapAsync = async (timeOutStat = null) => {
const userToken = await AsyncStorage.getItem('userToken');
const userName = await AsyncStorage.getItem('userName');
const userID = await AsyncStorage.getItem('userID');
await this.setState({
userName: userName,
userToken: userToken,
userID: userID,
})
if(timeOutStat) {
this.timeOut = setTimeout(() => {
this.setState({
loading: false,
showAlert: true,
alertType: true,
alertMessage: ErrorList.matchMakingError
});
}, 20000)
}
console.log('token', userToken)
await fetch(getInitUrl('getMatchInfo','',this.props.navigation.getParam('matchID', 0)), {
method: 'GET',
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
Authorization : `Bearer ${userToken}`
}
}).then(response => Promise.all([response.ok, response.status ,response.json()]))
.then(([responseOk,responseStatus, body]) => { //
this.setState({loading : false});
clearTimeout(this.timeOut);
if (responseOk) {
console.log('match Info', body);
this.setState({
matchInfo : body
})
} else {
console.log(responseStatus, body);
this.setState({
showAlert : true,
alertType : true,
alertMessage : ErrorList[body.tag]
});
}
})
.catch(error => {
console.error(error);
});
};
render() {
//console.log(puzzleSizes)
let rows = this.state.matchInfo.map((item , index)=>{
return
<MatchDetailBox
/>
})
console.log(rows)
<View>
{rows}
</View>
}
Even though this.setState() is asynchronous, it's not promisified hence it wont't work using promise's .then() or its syntactic sugar async/await. So there's no use for await in front of it. But I guess it creates a one tick delay.
Also why do you have await in front of fetch() and also .then() after that. Shouldn't either of them do?
The error this.state.matchInfo.map is not a function would occur only when this.state.matchInfo is not an array but you have initialized it to be one, so at any point of time matchInfo gets modified it must be becoming non-array like an object or something which doesn't have a native .map().
Have you checked response coming from API? I hope this helps.
this.setState({ matchInfo: body });
mapData = () => {
this.state.matchInfo.map((item , index)=>{
return(
<View>
<MatchDetailBox />
<View>
{item}
</View>
</View>
)
})
}
render() {
return {this.mapData()}
}