Undefined props only in componentDidMount - react-native

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;
...
}

Related

I am using redux in react native application to fetch and display data but its not updating on data change from backend

I am using Redux in my React-Native application.
I am fetching the data from api call and on success rendoring it on ListItem.
I am able to fetch and display data but data is not auto updating unless and until I revisit the page.
Even values are not storing into the app
I am calling method from actions in constructor and componentDidMount method.
Can you Please check the code and tell me where am I going wrong.
Here is action.js
import {
FETCHING_PRODUCT_REQUEST,
FETCHING_PRODUCT_SUCCESS,
FETCHING_PRODUCT_FAILURE
} from './types';
export const fetchingProductRequest = () => ({
type : FETCHING_PRODUCT_REQUEST
});
export const fetchingProductSuccess = (json) => ({
type : FETCHING_PRODUCT_SUCCESS,
payload : json
});
export const fetchingProductFailure = (error) => ({
type : FETCHING_PRODUCT_FAILURE,
payload : error
});
export const fetchProduct = () => {
return async dispatch => {
dispatch(fetchingProductRequest());
try {
let response = await fetch("http://phplaravel-325095-1114213.cloudwaysapps.com/api/shop/shop");
let json = await response.json();
dispatch(fetchingProductSuccess(json));
} catch(error) {
dispatch(fetchingProductFailure(error));
}
}
}
My reducer.js
import {
FETCHING_PRODUCT_REQUEST,
FETCHING_PRODUCT_SUCCESS,
FETCHING_PRODUCT_FAILURE
} from './../actions/types';
const initialState = {
loading : false,
errorMessage : '',
shops: []
}
const products = ( state = initialState, action ) => {
switch(action.type) {
case FETCHING_PRODUCT_REQUEST :
return { ...state, loading: true} ;
case FETCHING_PRODUCT_SUCCESS :
return { ...this.state, loading: false, shops: action.payload };
case FETCHING_PRODUCT_FAILURE :
return { ...state, loading: false, errorMessage: action.payload};
}
};
export default products;
product.js
import * as React from 'react';
import { FlatList , ActivityIndicator} from 'react-native';
import { ListItem } from 'react-native-elements';
import { fetchProduct } from './../../actions/products';
import { connect } from 'react-redux';
import propTypes from 'prop-types';
class Product extends React.Component {
constructor(props) {
super(props);
this.props.fetchProduct();
this.state = {
loading : true,
shops : '',
isFetching: false,
active : true,
}
}
fetchProducts() {
const shopid = 13;
fetch(`http://phplaravel-325095-1114213.cloudwaysapps.com/api/shop/shop`)
.then(response => response.json())
.then((responseJson)=> {
this.setState({
loading: false,
shops: responseJson
})
alert(JSON.stringify(this.state.shops));
})
.catch(error=>console.log(error)) //to catch the errors if any
}
componentDidMount(){
// this.fetchProducts();
this.props.fetchProduct().then(this.setState({loading : false}));
}
renderItem = ({ item }) => (
<ListItem
title={item.name}
subtitle={item.name}
leftAvatar={{
source: item.avatar && { uri: item.avatar },
title: item.name[0]
}}
bottomDivider
chevron
/>
)
render () {
if(!this.state.loading)
{
if(this.props.shopsInfo.loading)
{
return (
<ActivityIndicator/>
)
}
else
{
return (
<FlatList
vertical
showsVerticalScrollIndicator={false}
data={this.props.shopsInfo.shops}
renderItem={this.renderItem}
/>
)
}
}
else
{
return (
<ActivityIndicator/>
)
}
}
}
Product.propTypes = {
fetchProduct: propTypes.func.isRequired
};
const mapStateToProps = (state) => {
return { shopsInfo: state };
}
function mapDispatchToProps (dispatch) {
return {
fetchProduct: () => dispatch(fetchProduct())
}
}
export default connect(mapStateToProps, mapDispatchToProps)(Product);
1. Not updating on data change from backend.
You have to call an api on regular interval to get updated data. Redux implementation doesn't mean it will fetch data from server whenever there is any change.
2. Even values are not storing into the app
If you are expecting redux will store data even if you will close/kill an application than it will not. You have persist data in-order to use it or store it in cache. Take a look at redux-persist
The problem is your passing wrong props in mapStateToProps function.
In reducer your updating the response value in shop props.
In order to get the updated value you need to pass shops property to get the value.
const mapStateToProps = (state) => {
const { shops: state };
return {shops};
}

How can i refresh data with setInterval when Actions.pop()?

I'm trying to create live dashboard mobile app with react-native. I setInterval to fetch data every 5 sec. When i go to other actions i clearIntervar(cause if i don't clear it continues other pages) and it's ok but when i try to Action.pop() i cant setInterval again.
I tried to setInterval in componentWillUnmount() and Action.refresh(with same props) but every time; i get the same error.
Warning: Can't perform a React state update on an unmounted component.
This is a no-op, but it indicates a memory leak in your application.
To fix, cancel all subscriptions and asynchronous tasks in the
componentWillUnmount method.
This is the sample like my code:
export default class MyComponent extends Component {
constructor(props) {
super(props);
this.state = {
isRefresh: false
}
}
componentDidMount() {
this.getData()
}
async getData() {
//just a sample
const data = await fetch(url).then((response) => response.json());
this.setState({data: data});
if (this.state.isRefresh) {
const intervalId = setInterval(() => {
this.getData();
}, 5000);
this.setState({
intervalId: intervalId,
isRefresh: true
})
}
}
render() {
return (
<View>
<Text>{this.state.data}</Text>
<Button onPress={() => {
clearInterval(this.state.intervalId);
Action.otherPage();
}
} title={'Test Button'}/>
</View>
)
}
}
I have to setInterval and fetch data in the other pages too. So i need to clear when i go to other pages and need to setInterval when i come back with Actions.pop()
Don't store intervalId in state, instead you should make use of instance variable for your interval,
constructor(props) {
super(props);
this.state = {
isRefresh: false
}
this.intervalId = null; //instance variable
}
Then assign your interval to instance variable,
this.intervalId = setInterval(() => { this.getData();}, 5000);
Then use componentWillUnmount to clear interval,
componentWillUnmount(){
clearInterval(this.intervalId);
}
Please use this
componentDidMount() {
const { navigation } = this.props;
this.focusListener = navigation.addListener('didFocus', () => {
// The screen is focused
// Call any action you want when a user on this screen
});
}
componentWillUnmount() {
// Remove the event listener
this.focusListener.remove();
}

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

I can only parse my mapStateToProps object to an extent inside of my react component

I am pretty new to redux and am having trouble parsing JSON data, when I mapStateToProps inside my react component. For instance, if I console.log(this.props.chartData[0]) in my react component, the console will display the array I am trying to access, however, when I try to access a specific element in the array by console logging (this.props.ChartData[0].title), I get an error:
[enter image description here][1]
class ChartContainer extends Component {
componentWillMount(){
this.props.chartChanged();
}
render(){
console.log(this.props.chartData[0]);
return(
<Text style={styles.textStyle}>
test
</Text>
);
}
}
const mapStateToProps = state => {
return {
chartData: state.chart
}
};
export default connect (mapStateToProps, {chartChanged}) (ChartContainer);
Interestingly, I have no problem accessing(this.props.ChartData[0].title) inside my reducer.
import {CHART_CHANGED} from '../actions/types';
const INITIAL_STATE = { chartData: [] };
export default (state = INITIAL_STATE, action) => {
console.log(action);
switch (action.type) {
case CHART_CHANGED:
console.log("action");
console.log(action.payload[0].title);
return{...state, chartData: action.payload};
default:
return state;
}
};
Here is the api call in my action file:
export const chartChanged = (chartData) => {
return (dispatch) => {
axios.get('https://rallycoding.herokuapp.com/api/music_albums')
.then((chartData) =>{
dispatch({type: CHART_CHANGED, payload: chartData.data});
});
};
};
If someone can explain why this is happening, I would be super grateful.
So the problem is that you shouldn't assign any value during fetching, what you need to do is use lodash and try doing something like this
import _ from 'lodash'
const title = _.get(this.props.ChartData, 'chartData', [])
if(!isFetching){
//do something
}

Connected Component's prop doesn't update in React Native with Redux

I'm creating some kind of Realtime Chat App with React Native + Redux. When I get some new message from websocket, internally it will updates list of message as array with redux store. This is what looks like:
import { CHAT_INIT, CHAT_RECV } from '../actions/Chat';
const defaultState = {
chatList: []
};
export default function(state = defaultState, action = {}) {
switch(action.type) {
case CHAT_INIT:
return Object.assign({}, {
chatList: []
});
case CHAT_RECV:
let chatList = state.chatList;
chatList.push(action.data);
return Object.assign({}, {
chatList: chatList
});
default:
return state;
}
}
There are only two actions: CHAT_INIT and CHAT_RECV which can easily understand.
When app receives new message from socket, it will invoke store.dispatch with 'CHAT_RECV' action. This is the component code of list of messages:
class ChatList extends Component {
static propTypes = {
chatList: React.PropTypes.array
}
static defaultProps = {
chatList: []
}
componentWillMount() {
store.dispatch({
type: ChatActions.CHAT_INIT,
data: ''
});
}
componentWillReceiveProps(nextProps) {
console.log('will receive props'); // 1
}
render() {
console.log('<ChatList />::chatList', this.props.chatList); // 2
return (
<View style={styles.chatList}>
<Text>ChatList</Text>
</View>
);
}
}
export default connect(state => {
let chatList = state.ChatReducer.chatList;
console.log('Got:', chatList); // 3
return {
chatList: state.ChatReducer.chatList
};
})(ChatList);
I connected ChatList component with ChatReducer.chatList so when new message arrives, props of ChatList component will be update.
The problem is props on ChatList component doesn't updating at all! As you can see, I placed lots of console.log to tracking where is the problem. Numbers next of console.log is just added for easy explanation.
You can see that I'm trying to update chatList props of connected component ChatList, and it should be re-render on receive new props(means new message).
So [3] of console.log prints 'Got: [..., ...]' as well, but [1] and [2] are not prints anything! It means ChatList component didn't receive next props properly.
I double checked the code and tried to fix this, but not much works. Is this problem of Redux or React-Redux module? Previously I used both modules for my Electron ChatApp, and it worked without any problem.
Is there a something that I missed? I really don't know what is the matter . Anyone knows about this issue, please gimme a hand, and will be very appreciate it.
P.S. These are other component codes. I think it doesn't important, but I just paste it for someone who wants to know.
Superior component: App.js
export default class App extends Component {
componentDidMount() {
init(); // this invokes CHAT_INIT action.
}
render() {
return (
<Provider store={store}>
<ChatApp />
</Provider>
);
}
}
ChatApp.js which actually renders ChatList component:
class ChatApp extends Component {
render() {
return (
<View style={styles.container}>
<NavBar username={this.props.username} connected={this.props.connected} />
<ChatList connected={this.props.connected} />
<ChatForm connected={this.props.connected} />
</View>
);
}
}
export default connect(state => {
return {
username: state.UserReducer.username,
connected: state.NetworkReducer.connected
};
})(ChatApp);
You're mutating your state here:
case CHAT_RECV:
let chatList = state.chatList;
chatList.push(action.data);
return Object.assign({}, {
chatList: chatList
});
Instead, do:
case CHAT_RECV:
let chatList = state.chatList.concat(action.data);
return Object.assign({}, {
chatList: chatList
});