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

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
}

Related

Redux TypeError: Invalid attempt to spread non-iterable instance. In order to be iterable, non-array objects must have a [Symbol.iterator]() method

I am a beginner integrating redux with my code but i am facing this error when i select a tag from auto tags it gives the error non-iterable instance. The deletion reducer works fine after testing by giving redux empty state a [{name:'ABC'}]
so please guide me in figuring out the issue with my addtags redux function.
First screen:
class Page2 extends React.Component {
componentDidMount(){
this.props.fetchTags();
}
handleSubmit = () => {
addItem(this.props.tags.currentTags);
ToastAndroid.show('Symptoms saved successfully', ToastAndroid.SHORT)
};
handleDelete = index => {
this.props.deleteTags(index)
}
handleAddition = suggestion => {
this.props.addTags(suggestion)
console.log(this.props.tags.currentTags)
}
render() {
return (
<View style={styles.container}>
<View style={styles.autocompleteContainer}>
<AutoTags // Text adding component with auto completion feature and bubble feature
suggestions={this.props.tags.storedTags}
tagsSelected={this.props.tags.currentTags}
handleAddition={this.handleAddition}
handleDelete={this.handleDelete}
placeholder="Add a Symptom.." />
</View>
<TouchableHighlight // wrapper for making views respond properly to touches
style={styles.button}
underlayColor="blue"
onPress={() => {this.props.navigation.navigate('Diagnosis'); this.handleSubmit();}}>
<Text style={styles.buttonText}>Search</Text>
</TouchableHighlight>
</View>
);
}
}
const mapStateToProps = (state) => {
return {
tags: state.tags,
}
}
const mapDispatchToProps = {
addTags,
deleteTags,
fetchTags
}
export default connect(mapStateToProps, mapDispatchToProps)(Page2);
Reducer:
import {ADD_TAGS,DELETE_TAGS , FETCH_TAGS,QUERY_RESULT } from '../actions/tags/tagsActionTypes'
import firebase from '../../config';
const db= firebase.firestore();// connect with firestore of firebase
let itemsRef = db.collection('datasetSymptom');
const initialState = {
currentTags: [],
storedTags:[],
result:[]
}
const tagsReducer = (state = initialState, action) => {
switch (action.type) {
case ADD_TAGS:
return {
...state,
currentTags: [...state.currentTags, action.payload]
};
break
case DELETE_TAGS:
const newArray = [...state.currentTags] //Copying state array
newArray.splice(action.payload,1);
//using splice to insert at an index
return {
...state,
currentTags: newArray //reassigning todos array to new array
}
break
case FETCH_TAGS:
const storedTags=[];
itemsRef.get().then(querySnapshot => {// fetching dataset Symptom collection all documents using snapshot
querySnapshot.docs.forEach(doc => {storedTags.push({'name':doc.id});});//on each and storing it in 'name' key : symptom name form as aked by autotags component
});
return {
storedTags: storedTags
}
break
default:
return state;
}
};
export default tagsReducer
function:
export const addTags = (tag_id) => {
return {
type: ADD_TAGS,
payload: tag_id
}
};
I had a similar error with my code since I too am using an array like you have for your currentTags. I was able to resolve my issue by adding conditional check in my reducer.
The issue is that you are trying to use spread operator for currentTags but in initialState it is empty. I think the below may be your solution and may solve your problem based on what worked for me. For your ADD_TAGS code, replace it with below:
case ADD_TAGS:
return currentTags ?
{
...state,
currentTags: [...state.currentTags, action.payload]
};
:
{
...state,
currentTags: [state.currentTags, action.payload]
};
break

Redux mapStateToProps call only once

It's been 2 days that I'm stuck with an issue implementing redux in my react native application. I checked many topics but I can't fix my problem.
The problem is that mapStateToProps is called only once but it's not called anymore after executing action. My reducer is called but the state is not updating.
I test my work on android with android studio and emulator.
Here is my code :
App.js:
<Provider store={createStore(reducers)}>
<View>
<MainScreen />
</View>
</Provider>
HeaderReducer:
export default (state = INITIAL_STATE, action) => {
switch (action.type) {
case SET_HEADER:
return action.payload;
default:
return state;
}
};
reducers/index.js
import { combineReducers } from 'redux';
import HeaderReducer from './HeaderReducer';
export default combineReducers({
headerText: HeaderReducer,
});
actions/index.js
export const SET_HEADER = 'set_header';
export const setHeaderText = (title) => {
return {
type: SET_HEADER,
payload: title,
};
};
MainScreen.js
import * as actions from '../actions';
...
<Text> {this.props.headerText} </Text>
<Button
onPress={() => {
this.props.setHeaderText(`screen: ${SELECTED_PAGE.profile}`);
this.forceUpdate();
}}
>
...
const mapStateToProps = (state) => {
const { headerText } = state;
return { headerText };
};
export default connect(mapStateToProps, actions)(MainScreen);
I know that I don't need to forceUpdate in the onPress callback but it's just to check the state after call the function.
So when the application start the reducers are called and my mapStateToProps is working. But when I click on my button, my reducers are called, the good action is executed in the switch but my mapStateToProps is not called anymore. When I click a second time if I console.log the state in my reducer I can see that the state is not updated but stay the same than the INITIAL_STATE.
I don't mutate the state in my reducers, I connected everything in my MainScreen.js and I wrapped my app in Provider with store.
I follow a tutorial on Udemy to implement reduc in my app and even I check the code of the tuto on github, I can't find out where the issue is from (https://github.com/StephenGrider/ReactNativeReduxCasts/tree/master/tech_stack)
I think I miss something really obvious but I can't find out what's wrong since 2 days :/
If someone can help, I would appreciate :)
Thanks.
EDIT :
I didn't figure out the problem, so I just restart from scratch, I did exactly the same (THE SAAME!!) and it's working now... Thanks for your help :)
The problem is in your HeaderReducer, reducer is expecting to return a state object, but you're returning a string instead.
export default (state = INITIAL_STATE, action) => {
switch (action.type) {
case SET_HEADER:
return action.payload;
default:
return state;
}
During the case of SET_HEADER, what you really want is to overwrite a field instead of overwrite the whole state object, so change to below instead
const INITIAL_STATE = { customizeText: '' };
export default (state = INITIAL_STATE, action) => {
switch (action.type) {
case SET_HEADER:
return { ...state, customizeText: action.payload }; // <<=== HERE
default:
return state;
}
}
Then in your MainScreen.js, you have to update your mapStateToProps function
<Text> {this.props.someText} </Text>
...
const mapStateToProps = ({ headerText }) => {
return { someText: headerText.customizeText };
};
...
I've updated the field name to avoid confusion
First make an actual action as this.props.setHeaderText() and pass title if you wish to and make sure your INITIAL_STATE is an object or [].
const mapStateToProps = (state) => {
return{
headerText: state.headerText
}
};
export default connect(mapStateToProps, actions)(MainScreen);

Why Is My Redux Reducer Ignoring Await on Async Functions?

In my React-Native app, I had some database code that worked fine. However, I decided that I needed to shoehorn in redux to maintain certain state, especially app settings.
Once I got the redux concepts through my thick skull and implemented it, that same database code started returning promises instead of honoring the "await" statements that were previously in use.
Here is the relevant reducer and database code:
// relevant imports
export default function divisionReducer(state = {programId: 1}, action) {
switch (action.type) {
case GET_DIVISIONS_BY_PROGRAM:
// add result to state
return _.cloneDeep({...state, divisions: divisions });
default:
return state;
}
}
getAllDivisions = async (programId) => {
let db = await openDefault();
const sql = "SELECT * FROM DIVISION WHERE DIVISION_PROGRAM_ID = ?";
let sqlResult = await query(db, sql, [programId]);
await close(db);
// function to convert db snake case to camelcase
result = keysToCamelCase(sqlResult.result);
return result;
}
My question: why is this code not honoring the "await" keywords?
Edit: More Code Added by Request
Below is the divisionAction code:
import { GET_DIVISIONS_BY_PROGRAM } from "./actionTypes";
export const getAllDivisions = (programId) => {
return {
type: GET_DIVISIONS_BY_PROGRAM,
payload: programId
}
}
Below is the DivisionManagementScreen, which calls the getAllDivisions code:
port React, {Component} from "react";
import {View, FlatList, Alert} from "react-native";
import {withNavigation} from "react-navigation";
import {connect} from "react-redux";
import masterStyles, {listPage, bigButtonStyles} from "./../../styles/master";
import {getAllDivisions} from "./../../redux/actions/divisionActions";
import DivisionManagementRow from "./DivisionManagementRow";
class DivisionManagmementScreen extends Component {
constructor(props) {
super(props);
}
async componentDidMount() {
this.props.getAllDivisions(this.props.programId);
console.log("Props after getAllDivisions: " + JSON.stringify(this.props));
}
async componentWillUnmount() {
console.log("Entered componentWillUnount()");
}
_renderItem = ({item}) => (
<DivisionManagementRow divisionId={item.DIVISION_ID} divisionName={item.DIVISION_NAME}
onAddTeam={() => {this._addTeam(item.DIVISION_ID)}}
onEdit={() => {this._editDivision(item.DIVISION_ID)}}
onDelete={() => {this._btnDeleteDivision(item.DIVISION_ID)}}/>
);
render() {
console.log("In render(), props: " + JSON.stringify(this.props));
return (
<View style={masterStyles.component}>
<View style={listPage.listArea}>
<FlatList
data={this.props.divisions}
renderItem={this._renderItem}
keyExtractor={(item) => item.DIVISION_ID.toString() } />
</View>
<View style={listPage.bottomButtonArea}>
<PortableButton defaultLabel="Add Division"
disabled={false}
onPress={() => {this._addDivision()}}
onLongPress={() => {}}
style={bigButtonStyles} />
</View>
</View>
);
}
}
function mapStateToProps(state) {
return {
programId: state.divisionReducer.programId,
divisions: state.divisionReducer.divisions
};
}
export default withNavigation(connect(mapStateToProps, {getAllDivisions})(DivisionManagmementScreen));
Is this enough code to diagnose?
I can't see where you're actually calling your getAllDivisions async function. I can only see you trying to call the getAllDivisions action creator - action creators just emit actions syncronously, by default they can't call functions with side effects.
If you want to trigger side effects, like your DB async function you need to look into a library like redux-thunk. Or more advanced would be redux-saga. If you're new to this stuff, I advise starting with redux-thunk.
Also I think the way you're using the connect() function is wrong. The second argument mapDispatchToProps needs to actually dispatch your actions to the store. So it should look like this:
function mapStateToProps(state) {
return {
programId: state.divisionReducer.programId,
divisions: state.divisionReducer.divisions
};
}
function mapDispatchToProps(dispatch) {
return {
getAllDivisions () {
dispatch(getAllDivisions())
}
};
}
export default withNavigation(
connect(
mapStateToProps, mapDispatchToProps
)(DivisionManagmementScreen)
)
So, instead of writing my database-enabled action creator correctly (starting with " return (dispatch) => { /* blah blah blah */ } ), I was still having it return an object, and having the reducer call the method with the database code.
I finally got the concepts through my thick skull, and got the code working over a weekend.

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

I need some help with my app and Redux! (Currently, i hate it aha)
So, i have a notification page component which fetch some datas and i need to put the data length into my redux store to put badge on my icon in my tabbar!
My Main Reducer :
import { combineReducers } from "redux";
import NotificationReducer from "./NotificationReducer";
export default function getRootReducer(navReducer) {
return combineReducers({
nav: navReducer,
notificationReducer: NotificationReducer
});
}
My Notification reducer
const initialState = {
NotificationCount: 0
};
export default function notifications(state = initialState, action = {}) {
switch (action.type) {
case 'SET_COUNT' :
console.log('REDUCER NOTIFICATION SET_COUNT',state)
return {
...state,
NotificationCount: action.payload
};
default:
return state;
}
};
My Action :
export function setNotificationCount(count) {
return function (dispatch, getState) {
console.log('Action - setNotificationCount: '+count)
dispatch( {
type: 'SET_COUNT',
payload: count,
});
};
};
My Component :
import React, { Component } from 'react';
import { View, Text, StyleSheet, ScrollView, Dimensions, TouchableOpacity, SectionList, Alert } from 'react-native';
import Icon from 'react-native-vector-icons/Ionicons';
import { Notification } from '#Components';
import { ORANGE } from '#Theme/colors';
import { NotificationService } from '#Services';
import Style from './style';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import * as Actions from '#Redux/Actions';
const width = Dimensions.get('window').width
const height = Dimensions.get('window').height
export class NotificationsClass extends Component {
constructor(props) {
super(props);
this.state = {
dataSource: [],
NotificationCount: undefined
};
}
async componentWillMount() {
this.updateNotifications();
}
componentWillReceiveProps(nextProps){
console.log('receive new props',nextProps);
}
async updateNotifications() {
this.props.setNotificationCount(10); <---
let data = await NotificationService.get();
if (data && data.data.length > 0) {
this.setState({ dataSource: data });
console.log(this.props) <-- NotificationCount is undefined
}
}
render() {
if (this.state.dataSource.length > 0) {
return (
<SectionList
stickySectionHeadersEnabled
refreshing
keyExtractor={(item, index) => item.notificationId}
style={Style.container}
sections={this.state.dataSource}
renderItem={({ item }) => this.renderRow(item)}
renderSectionHeader={({ section }) => this.renderSection(section)}
/>
);
} else {
return this.renderEmpty();
}
}
renderRow(data) {
return (
<TouchableOpacity activeOpacity={0.8} key={data.notificationId}>
<Notification data={data} />
</TouchableOpacity>
);
}
}
const Notifications = connect(
state => ({
NotificationCount: state.NotificationCount
}),
dispatch => bindActionCreators(Actions, dispatch)
)(NotificationsClass);
export { Notifications };
(I've removed some useless code)
Top Level :
const navReducer = (state, action) => {
const newState = AppNavigator.router.getStateForAction(action, state);
return newState || state;
};
#connect(state => ({
nav: state.nav
}))
class AppWithNavigationState extends Component {
render() {
return (
<AppNavigator
navigation={addNavigationHelpers({
dispatch: this.props.dispatch,
state: this.props.nav,
})}
/>
);
}
}
const store = getStore(navReducer);
export default function NCAP() {
return (
<Provider store={store}>
<AppWithNavigationState />
</Provider>
);
}
React : 15.6.1
React-Native : 0.46.4
Redux : 3.7.2
React-Redux : 5.0.5
React-Navigation : 1.0.0-beta.11
Node : 6.9.1
So if you've an idea! It will be great :D !
Thanks !
There's three issues.
First, React's re-rendering is almost always asynchronous. In updateNotifications(), you are calling this.props.setNotificationCount(10), but attempting to view/use the props later in that function. Even with the await in there, there's no guarantee that this.props.NotificationCount will have been updated yet.
Second, based on your reducer structure and mapState function, props.NotificationCount will actually never exist. In your getRootReducer() function, you have:
return combineReducers({
nav: navReducer,
notificationReducer: NotificationReducer
});
That means your root state will be state.nav and state.notificationReducer. But, in your mapState function, you have:
state => ({
NotificationCount: state.NotificationCount
}),
state.NotificationCount will never exist, because you didn't use that key name when you called combineReducers.
Third, your notificationReducer actually has a nested value. It's returning {NotificationCount : 0}.
So, the value you actually want is really at state.notificationReducer.NotificationCount. That means your mapState function should actually be:
state => ({
NotificationCount: state.notificationReducer.NotificationCount
}),
If your notificationReducer isn't actually going to store any other values, I'd suggest simplifying it so that it's just storing the number, not the number inside of an object. I'd also suggest removing the word Reducer from your state slice name. That way, you could reference state.notification instead.
For more info, see the Structuring Reducers - Using combineReducers section of the Redux docs, which goes into more detail on how using combineReducers defines your state shape.

Issue accessing store and calling actionCreators

I'm in the process of learning React Native with Redux and can't wrap my head around how to access the store/actionCreators from within my components. I feel like I've tried ten or so variants of code, watched most of Dan Abramov's Egghead videos (multiple times), and still don't understand what I'm doing wrong. I think the best explanation I've seen so far is the accepted answer here: Redux, Do I have to import store in all my containers if I want to have access to the data?
The error I get is: undefined is not an object (evaluating 'state.clinic.drName'). Here are the relevant bits of code:
I'm passing the store via the Provider (so I think):
index.ios.js
import clinicReducer from './reducers/clinic';
import { createStore, combineReducers } from 'redux';
import { Provider } from 'react-redux';
const rootReducer = combineReducers({
clinic : clinicReducer
});
let store = createStore(combineReducers({clinicReducer}));
//testing - log every action out
let unsubscribe = store.subscribe( () =>
console.log(store.getState())
);
class ReactShoeApp extends React.Component {
render() {
return (
<Provider store={store}>
<ReactNative.NavigatorIOS
style={styles.container}
initialRoute={{
title: 'React Shoe App',
component: Index
}}/>
</Provider>
);
}
}
Here is my actionCreator:
export const CLINIC_DR_NAME_UPDATE = 'CLINIC_DR_NAME_UPDATE_UPDATE';
export function updateClinicDrName(newValue) {
return {type: CLINIC_DR_NAME_UPDATE, value: newValue};
}
Here is my reducer:
import {
CLINIC_DR_NAME_UPDATE
} from '../actions/';
let cloneObject = function(obj) {
return JSON.parse(JSON.stringify(obj));
};
const initialState = {
drName : null
};
export default function clinicReducer(state = initialState, action) {
switch (action.type) {
case CLINIC_DR_NAME_UPDATE:
return {
...state,
drName: action.value
};
default:
return state || newState;
}
}
and here is my component:
import { bindActionCreators } from 'redux';
import { connect } from 'react-redux';
import * as Actions from '../actions';
const mapStateToProps = function(state){
return {
drName: state.clinic.drName,
}
};
const mapDispatchToProps = function (dispatch) {
return bindActionCreators({
updateClinicDrName: Actions.updateClinicDrName,
}, dispatch)
};
//class definition
class Clinic extends React.Component {
constructor(props) {
super(props);
}
render() {
return (
<View style={styles.rowContainer}>
<View style={styles.row}>
<TextGroup label={'Dr. Name'} placeholder={'placeholder'} value={this.props.drName} onChangeText={(text) => this.handlers.onNameChange({text})}></TextGroup>
<TextGroup label={'City'} placeholder={'placeholder'}></TextGroup>
</View>
</View>
);
}
}
export default connect(mapStateToProps, mapDispatchToProps)(Clinic);
All I'm trying to do is update the store (clinic.drName) when the user types in the Dr Name Text Group. Any and all help is appreciated.
The issue is you're mismatching the way you call combineReducers() with the name of the slice you're expecting. This is a common mistake. You're defining an object with the key clinicReducer, but expecting the data to be at a key named clinic.
See Defining State Shape for an explanation of the problem.
Also, your clinicReducer() function looks odd, and has several issues:
Don't access variables outside the function like you are with newState.
Don't do deep-cloning like that (per Redux FAQ: Performance). Instead, use "shallow cloning" to update the state if necessary (see examples at Immutable Update Patterns).
It looks like the reducer is double-nesting the data it's trying to access as well.
I think you want something more like:
// clinicReducer.js
const initialState = {
drName : null
};
export default function clinicReducer(state = initialState, action) {
switch(action.type) {
case CLINIC_DR_NAME_UPDATE: {
return {
...state,
drName : actino.value
};
}
default: return state
}
}
// index.js
import clinicReducer from "./reducers/clinic";
const rootReducer = combineReducers({
clinic : clinicReducer
});
What errors are you getting? I see two issues with what you have.
newState.clinic.drName = action.value; should be throwing a typeError. So for this to work you need to change newState to:
const newState = {
clinic: {}
}
What is this.handlers.onNameChange({text})? I think you need to change this to this.props.updateClinicDrName(text).