asynchronous function in render() method fails (Invariant Violation) - react-native

I'm trying to use an asynchronous function in render():
async _check() {
const token = await AsyncStorage.getItem('myToken');
if(token !== null) {
this.props.navigation.navigate('My Screen', {token: token});
}
else {
return (
<View style={styles.content_container}>
...
</View>
);
}
}
render() {
return (
<View>
{ this._check() }
</View>
);
}
but I'm getting this error:
Invariant Violation: Invariant Violation: Objects are not valid as a React child (found: object with keys {_40, _65, _55, _72}). If you meant to render a collection of children, use an array instead.
Can someone tell me what's wrong with my code ?

You can't use any asynchronous functions or do any sideffects (like navigating) in render. This is directly against React principles. Render should only be rendering elements from props and state. Aside from that, error you are getting is because you are rendering result of async function, which is always a Promise. Also, getting token from AsyncStorage on every render doesn't make sense as render will happen often, up to few times per second
Put your logic into lifecycle methods e.g. componentDidMount, then use this.setState to change state the way you want, and use this.state to render your component in render()
class SomeComponent extends Component {
constructor(props) {
super(props)
this.state = {
message: ''
}
}
async componentDidMount() {
const token = await AsyncStorage.getItem('myToken')
if (token !== null) {
this.props.navigation.navigate('My Screen', { token: token })
} else {
this.setState({ message: 'token is missing' })
}
}
render() {
return (
<View style={styles.content_container}>
<Text>{this.state.message}</Text>
</View>
)
}
}

Related

React Native: TypeError: this.state.schedule.map is not an object

Hey I am new to React Native and currently I'm trying to put data in a picker using data from API. I'm confused that it got error say TypeError: null is not an object (evaluating this.state.schedules.map). Is there something wrong with the state or is there any concept that I misunderstood
Here is fetch API
export function getSchedule (token, resultCB) {
var endpoint = "/api/getList"
let header = {
"Content-Type": "application/x-www-form-urlencoded",
"Authorization": "Bearer " + token
};
return dispatch => {
return fetchAPI(endpoint, 'GET', header)
.then((json) => {
dispatch({ type: t.SCHEDULE, schedules: json.datas.description });
resultCB(json.schedules)
})
.catch((error) => {
dispatch({ type: types.EMPTY_SCHEDULE });
resultCB(error)
})
}
}
this is where i put my picker
export const mapStateToProps = state => ({
token: state.authReducer.token,
message: state.authReducer.message,
schedules: state.authReducer.schedules
});
export const mapDispatchToProps = (dispatch) => ({
actionsAuth: bindActionCreators(authAction, dispatch)
});
class Change extends Component {
constructor(){
super();
this.state={
staffId: "",
schedule: '',
type_absen: 1,
schedules: null
}
}
componentDidMount(){
this.props.actionsAuth.getSchedule(this.props.token);
}
render() {
return (
<View style={styles.picker}>
<Picker
selectedValue={this.state.schedule}
style={{backgroundColor:'white'}}
onValueChange={(sch) => this.setState({schedule: sch})}>
{this.state.schedules.map((l, i) => {
return <Picker.Item value={l} label={i} key={i} /> })}
</Picker>
</View>
);
}
}
export default connect(mapStateToProps, mapDispatchToProps)(Change);
This isn’t a React Native specific error. You initialized schedules to null so on first render, you try to call .map on null. That’s what is causing your error.
You fetch your data correctly in componentDidMount but that lifecycle method will fire after the initial render.
One common way to fix this is to initialize schedules to an empty array.
First initialise schedules: [] in the state with empty array, not with the null.
Fetching data in componentDidMount() is correct. ComponentDidMount() will be called after the first render of component so you have to update the state in the component from the updated store.
you can check whether props is changing or not in componentWillReceiveProps (depreciated) or in the latest alternative of componentWillReceiveProps method that is getDerivedStateFromProps().
Below is the syntax for both
componentWillReceiveProps(nextProps) {
if (this.props.schedules !== nextProps.schedules) {
this.setState({ schedules: nextProps.schedules });
}
}
static getDerivedStateFromProps(nextProps, prevState){
if (nextProps.schedules !== prevState.schedules) {
return { schedules: nextProps.schedules };
}
else return null; // Triggers no change in the state
}
Make sure your component should connected to store using connect

Changing state in ComponentWillMount goes to an infinite loop

In my react native app i'm changing a style at a fixed time from 7 to 7:30pm. I am changing the state for that in ComponentWillMount. But whenever i go to that component,it starts calling that state again and again and doesn't even stop when i go to a different component. I want to stop this infinite loop of calling itself.
Here's the code:
import { withNavigation } from "react-navigation";
class Third extends Component {
constructor(props) {
super(props);
this.state = {
toggle: 0,
live: false
}
}
componentWillMount() {
const { navigation } = this.props;
this.focusListener = navigation.addListener("didFocus", () => {
this.changeLang()
});
var today = new Date()
var time = today.getHours()
console.log(today.getMinutes())
var weekDay = today.getDay()
if ((time >= 19) && (time <= 20 ) && (weekDay === 0 ||3 ||6)){
if(today.getMinutes()<=30){
this.setState({ live: true })
}
}
}
async changeLang() {
try {
const value = await AsyncStorage.getItem('toggle')
this.setState({ toggle: JSON.parse(value) })
} catch (e) {
// error reading value
}
}
render() {
const state = this.state;
console.log('live', this.state.live)
this.changeLang()
return (
<Container style={{ backgroundColor: '#fff' }}>
<Content>
<Left></Left>
<Body></Body>
<Right>{(this.state.live === true) ? <Icon name='football'/>: <Icon name='refresh'/>}</Right>
</View>
</Card>
</View>
</Content>
</Container>
);
}
}
export default withNavigation(Third)
Here this.state.live keeps on giving consoles and doesn't stop. What can be done here to resolve this?
Your problem is with this in render function,
this.changeLang()
Every time you call this.setState your component will re-render, and when component re-render your render function will get called.
So the sequence causing infinite loop is,
Component mounts => in render function you call this.changeLang() => in changeLog function you are calling this.setState => render function get called which again executes this.changeLang() => in changeLog function you are calling this.setState ... so on.
In this way you end up with infinite loop.
Just remove this.changeLang() from render function as you are already calling the same in componentWillMount.
Calling setState here makes your component a contender for producing infinite loops. remove this.changeLang() make it work

Undefined props only in componentDidMount

In my code below you can see my component. How it is written will cause the app to crash with the error:
undefined is not an object (evaluation this.props.data.ID)
So in my componentDidMount that id variable is not receiving the props data.
However if i comment out that code in the componentDidMount the app will load fine and the props.data.ID will print out in View. Is there a reason why i can't access the props.data.ID in my componentDidMount?
Heres my code
// timeline.js
class TimelineScreen extends Component {
constructor(props) {
super(props);
this.state = {
posts: []
}
}
componentDidMount() {
const { id } = this.props.data.ID;
axios.post('/api/hometimeline', { id })
.then(res => {
this.setState({
posts: res.data
});
});
}
render() {
const { data } = this.props;
return (
<View style={s.container}>
{
data
?
<Text>{data.ID}</Text>
:
null
}
</View>
);
}
}
function mapStateToProps(state) {
const { data } = state.user;
return {
data
}
}
const connectedTimelineScreen = connect(mapStateToProps)(TimelineScreen);
export default connectedTimelineScreen;
The input of mapStateToProps is not react state, it is redux store. You shouldn't use this.setState in componentDidMount. Use redux actions and reducers to change redux store. Whenever redux store changes, it will invoke mapStateToProps and update your props
componentDidMount() {
console.log(this.props.data); // for test
const id = this.props.data.ID;
//OR
const {id} = this.props.data;
...
}

React Native: Conditionally Render Components

I've searched this question and found a solution that said to conditionally render based on the state as follows:
render() {
const content = this.state.isReady ? <Home/> : <Splash/>;
return (
{content}
);
}
However, I keep getting an Invariant Violation: Objects are not valid a React child (found object with keys {content}.
your make typo, you returned Object, instead use between JSX elements:
const Ready = () => <div>Ready</div>
const NotReady = () => <div>NotReady</div>
class App extends Component {
constructor() {
super();
this.state = {
isReady: false
};
}
render() {
const content=this.state.isReady ? <Ready /> : <NotReady />
return (
<div>
{content}
</div>
);
}
}
Use simple if else instead of ternary expression because sometimes ternary operators "return" whatever's inside and can't execute blocks of code.
if (this.state.isReady) {
return <Home />
} else {
return <Splash />
}

Get warning after updating component in Navigator

I have a container in my React Native app and and I use it like preload to show scene Loading... before I get data from server. So I dispatch an action to fetch user data and after that I update my state I try to push new component to Navigator but I've got an error:
Warning: setState(...): Cannot update during an existing state transition (such as within `render` or another component's constructor). Render methods should be a pure function of props and state; constructor side-effects are an anti-pattern, but can be moved to `componentWillMount`.
And I don't understand what is the best way to fix my problem.
So my container:
import myComponent from '../components'
class App extends Component {
componentDidMount() {
this.props.dispatch(fetchUser());
}
_navigate(component, type = 'Normal') {
this.props.navigator.push({
component,
type
})
}
render() {
if (!this.props.isFetching) {
this._navigate(myComponent);
}
return (
<View style={styles.container}>
<Text style={styles.welcome}>
Loading...
</Text>
</View>
);
}
}
App.propTypes = {
dispatch: React.PropTypes.func,
isFetching: React.PropTypes.bool,
user: React.PropTypes.string
};
export default connect((state) => ({
isFetching: state.data.isFetching,
data: state.data.user
}))(App);
My reducer:
const data = (state = initialState, action) => {
switch (action.type) {
case types.USER_FETCH_SUCCEEDED:
return {
...state,
isFetching: false,
user: action.user
};
default:
return state;
}
};
Don't trigger anything that can setState inside the body of your render method. If you need to listen to incoming props, use componentWillReceiveProps
Remove this from render():
if (!this.props.isFetching) {
this._navigate(myComponent);
}
and add componentWillReceiveProps(nextProps)
componentWillReceiveProps(nextProps) {
if (!nextProps.isFetching) {
this._navigate(myComponent);
}
}