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

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

Related

Keep getting undefined when i add item with redux

need some serious help here. I am currently using a class component to add my items to cart but it looks like i keep getting undefined, can someone point me in the right direction please?
Homepage :
const mapStateToProps = state => ({
cartItems: state.cart.cartItems
});
const mapDispatchToProps = dispatch => {
return {
addToCartHandler: item => {
dispatch(addToCart(item))
}
}
}
renderRow =({item}) =>{
console.log(this.state.cartItems)
return(
<TouchableHighlight >
<View style ={styles.card}>
<Image source={{uri:item.image}} style={styles.image} />
<Icon name="add" size ={20} style= {styles.addToCartBtn} onPress ={() => this.props.addToCartHandler(item)} />
</TouchableHighlight>
)
}
reducers
import { ADD_TO_CART } from '../constants'
const initialState = {
cartItems: [],
totalPrice :0
}
//add item
export const cart = (state = initialState, action) => {
switch (action.type) {
case ADD_TO_CART:
console.log("reducer",action)
var {item} =action.payload;
var newState =Object.assign({},{...state});
for (var i = 0; i < state.cartItems.length; i++) {
newState.cartItems = newState.cartItems.concat(item);
console.log(newState)
return newState
}
default:
return state
}
}
action
import {ADD_TO_CART} from '../constants'
import {REMOVE_FROM_CART} from '../constants'
export const addToCart =(item) =>{
// console.log(item)
return{
type: ADD_TO_CART,
payload : item,
};
}
Dont think anything is wrong with my action or rootreducers. I am not sure why i keep getting undefined whenever i add to cart when i can console.log the item
You are not creating the new state correctly, I think the following will work:
case ADD_TO_CART:
console.log('reducer', action);
var item = action.payload;
return {
...state,
cartItems:state.cartItems.concat(item)
}
Here is some useful information on how to create a new state.

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
}

connect() does not re-render component

a component dispatches an action which modifies the Redux store and the other component should get the changed state to props and rerender.
The thing is, the component gets the props, and they are correct and modified, but the component is never rerendered.
Could someone help, been stuck too much..
Component who uses store:
on mount it does a http request,
and should rerender when the state is changed.
class CalendarView extends Component {
componentDidMount() {
axios.get('http://localhost:3000/api/bookings/get')
.then(foundBookings => {
this.props.getBookings(foundBookings);
})
.catch(e => console.log(e))
}
render() {
return (
<Agenda
items={this.props.items}
selected={this.props.today}
maxDate={this.props.lastDay}
onDayPress={this.props.setDay}
renderItem={this.renderItem}
renderEmptyDate={this.renderEmptyDate}
rowHasChanged={this.rowHasChanged}
/>
);
}
renderItem = (item) => {
return (
<View style={[styles.item, { height: item.height }]}>
<Text>Name: {item.name} {item.surname}</Text>
<Text>Time: {item.time}</Text>
</View>
);
}
renderEmptyDate = () => {
return (
<View style={styles.emptyDate}><Text>This is empty date!</Text></View>
);
}
rowHasChanged = (r1, r2) => {
console.log('hit')
return true;
}
}
const mapStateToProps = (state, ownProps) => {
return {
today: state.app.today,
lastDay: state.app.lastDay,
items: state.app.items
}
}
const mapDispatchToProps = (dispatch) => {
return {
setDay: date => dispatch(appActions.setSelectionDate(date.dateString)),
getBookings: data => dispatch(appActions.getBookings(data)),
}
}
export default connect(mapStateToProps, mapDispatchToProps)(CalendarView);
Action Dispatching:
dispatches an action which modifies the state
onSubmit = (name, surname, selectionDate, selectionTime) => {
axios.post('http://localhost:3000/api/bookings/create', {
bookerName: name,
bookerSurname: surname,
bookerTime: selectionTime,
date: selectionDate
}).then(savedBookings => {
this.props.createBooking(savedBookings);
this.props.navigator.pop({
animationType: 'slide-down',
});
}).catch(e => console.log(e))
}
const mapStateToProps = state => {
//...
}
const mapDispatchToProps = (dispatch) => {
return {
createBooking: data => dispatch(appActions.createBooking(data))
}
}
export default connect(mapStateToProps, mapDispatchToProps)(NewBookingScreen);
Reducer:
case types.CREATE_BOOKING: {
const { date , bookings } = action.savedBookings.data;
let dateArr = state.items;
// formatting a booking how needed
Object.keys(dateArr).forEach(key => {
if (key == date) {
dateArr[key] = [];
bookings.map(oneBooking => {
dateArr[key].push({
name: oneBooking.bookerName,
surname: oneBooking.bookerSurname,
time: oneBooking.bookerTime,
height: Math.max(50, Math.floor(Math.random() * 150))
});
})
}
});
return {
...state,
items: dateArr
};
}
full repo if needed: https://github.com/adtm/tom-airbnb/tree/feature/redux
Thank You in advance!
Your reducer is mutating the state, so connect thinks nothing has changed. In addition, your call to map() is wrong, because you're not using the result value.
Don't call push() on an array unless it's a copy. Also, please don't use any randomness in a reducer.
For more info, see Redux FAQ: React Redux ,Immutable Update Patterns, and Roll the Dice: Random Numbers in Redux .

React Native implement JSON data on ListView

I'm having a problem with implementing API data in ListView. I fetched JSON using Axios.
export function fetchRateService() {
return function(dispatch) {
axios.get(RATE_URL)
.then(response => {
dispatch({
type: FETCH_RATE_SERVICE,
payload: response.data
});
})
.catch((error) => {
console.log(error);
})
}
}
Reducer. I added rates data into array
import {
FETCH_RATE_SERVICE
} from '../actions/types';
const INITIAL_STATE = {
base: '',
date: '',
rates: []
};
export default (state = INITIAL_STATE, action) => {
switch (action.type) {
case FETCH_RATE_SERVICE:
return {
...state,
base: action.payload.base,
date: action.payload.date,
rates: [ ...state.rates, action.payload.rates ]
};
default:
return state;
}
};
This is the component
class ConturyList extends Component {
componentWillMount() {
this.props.fetchRateService();
this.createDataSource(this.props);
}
createDataSource({rates}) {
const ds = new ListView.DataSource({
rowHasChanged: (r1, r2) => r1 !== r2
});
this.dataSource = ds.cloneWithRows(rates);
}
renderRow(rate) {
return <ListItem rate={rate} />
};
render() {
console.log(this.props);
const { CardSectionStyle, textStyle, containerStyle } = styles;
const { visible, closeModal } = this.props;
return (
<Modal
visible={visible}
transparent={false}
animationType="slide"
onRequestClose={() => {this.props.closeModal()}}
>
<ListView
enableEmptySections
dataSource={this.dataSource}
renderRow={this.renderRow}
/>
</Modal>
);
}
}
const mapStateToProps = state => {
return {
rates: state.rateService.rates,
date: state.rateService.date,
base: state.rateService.base
};
}
export default connect(mapStateToProps, { fetchRateService } )(ConturyList);
The problem is I can see the props data using console.log(this.props);
enter image description here
I'm spending more than 3 days to figure out why this is not working. I tried using map() adding on
renderRow(rate) {
return rate.map((data) => {
return <ListItem rate={data} />
};
but it did not work. All the conutry code is in one object, Do I need to split the data by commas?.
Appreciate you helps. Thank you
UPDATE
So I'm trying to implement FlatList instead using ListView. The problem is on the JSON data. enter image description here. I want to implement key which are CountryCurrencyCode(AUD, JPN, etc..) to FlatList. Since rates is an object within an object, I added rates object into an array(reducer). But this.props.rates[0] can't be implemented on data property of FlatList. What kind of method can I try? I can't think of anything. I could print out key using map() when rates is object and then I can't implement it on the FlatList.
I would recommend switching over to the new FlatList component over ListView. FlatList just accepts an array of data to hydrate.
Initiate this.state.datasource as an empty array
constructor(props) {
super(props);
this.state = {
dataSource: [],
}
}
Fetch your data and hydrate this.state.dataSource from your Redux reducer/action
ComponentDidMount(){
this.props.fetchRateService();
var myData = this.props.rates[0];
this.setState({
dataSource:myData
)}
}
Now that your this.state.dataSource is set, we can populate FlatList
<FlatList
data={this.state.dataSource}
renderItem={({item})=>this.renderRow(item)}
/>
Flat List will throw a warning about a key extractor
Add this line below to the FlatList component. You will need to change 'item.key' to fit your own unique child. You can just keep it out for now for development.
keyExtractor={item => item.key}
You should see your data now! Keep in mind, you don't have to set the this.state.dataSource. Its just how I do it. You can plug 'this.props.rates' array directly into FlatList instead. Check out the FlatList docs for all the different things you can do with it. Hope this helps!

How to re render sub component on prop change with redux?

I have a react native app using redux and immutable js. When i dispatch an action from my main screen, it goes through my actions, to my reducer and then back to my container, however, the view doesn't update and componentWillReceieveProps is never called. Furthermore, the main screen is a list whose items are sub components Item. Here's the relevant code for the issue, if you want to see more let me know.
Render the row with the data:
renderRow(rowData) {
return (
<Item item={ rowData } likePostEvent={this.props.likePostEvent} user={ this.props.user } removable={ this.props.connected } />
)
}
The part of Item.js which dispatches an action, and shows the result:
<View style={{flex: 1, justifyContent:'center', alignItems: 'center'}}>
<TouchableOpacity onPress={ this.changeStatus.bind(this, "up") }>
<Image source={require('../img/up-arrow.png')} style={s.upDownArrow} />
</TouchableOpacity>
<Text style={[s.cardText,{fontSize:16,padding:2}]}>
{ this.props.item.starCount }
</Text>
<TouchableOpacity onPress={ this.changeStatus.bind(this, "down") }>
<Image source={require('../img/up-arrow.png')} style={[s.upDownArrow,{transform: [{rotate: '180deg'}]}]} />
</TouchableOpacity>
</View>
The action dispatched goes to firebase, which has an onChange handler that dispatches another action.
The reducer:
const initialState = Map({
onlineList: [],
offlineList: [],
filteredItems: [],
connectionChecked: false,
user: ''
})
...
...
case ITEM_CHANGED:
list = state.get('onlineList')
if(state.get('onlineList').filter((e) => e.id == action.item.id).length > 0){
let index = state.get('onlineList').findIndex(item => item.id === action.item.id);
list[index] = action.item
list = list.sort((a, b) => b.time_posted - a.time_posted)
}
return state.set('onlineList', list)
.set('offlineList', list)
The container:
function mapStateToProps(state) {
return {
onlineItems: state.items.get('onlineList'),
offlineItems: state.items.get('offlineList'),
filteredItems: state.items.get('filteredItems'),
connectionChecked: state.items.get('connectionChecked'),
connected: state.items.get('connected'),
user: state.login.user
}
}
Where I connect the onChange:
export function getInitialState(closure_list) {
itemsRef.on('child_removed', (snapshot) => {
closure_list.removeItem(snapshot.val().id)
})
itemsRef.on('child_added', (snapshot) => {
closure_list.addItem(snapshot.val())
})
itemsRef.on('child_changed', (snapshot) => {
closure_list.itemChanged(snapshot.val())
})
connectedRef.on('value', snap => {
if (snap.val() === true) {
closure_list.goOnline()
} else {
closure_list.goOffline()
}
})
return {
type: GET_INITIAL_STATE,
connected: true
}
}
Calling get initial state:
this.props.getInitialState({
addItem: this.props.addItem,
removeItem: this.props.removeItem,
goOnline: this.props.goOnline,
goOffline: this.props.goOffline,
itemChanged: this.props.itemChanged
})
Any suggestions are welcome, thanks so much!
The source of your issue could be with the call to Firebase. If it is an asynchronous call, it's return callback might not be returning something that can be consumed by your action.
Do you know if it is returning a Promise? If that is the case, middleware exists that handle such calls and stops the calling of an action until a correct response is received. One such middleware is Redux-Promise.
import React from 'react';
import ReactDOM from 'react-dom';
import { createStore,combineReducers } from 'redux' //Redux.createStore
import { Provider,connect } from 'react-redux';
//Функція яка змінює store
const hello = (state= {message:'none'}, action) => {
switch (action.type) {
case 'HELLO':
return Object.assign({}, state, {message:"hello world"});
break
case 'buy':
return Object.assign({}, state, {message:"buy"});
break;
case 'DELETE':
return Object.assign({}, state, {message:"none"});
break;
default :
return state;
}
};
const price = (state= {value:0}, action) => {
switch (action.type) {
case 'HELLO':
return Object.assign({}, state, {value: state.value + 1 });
break;
default :
return Object.assign({}, state, {value:0});
}
};
const myApp = combineReducers({
hello,price
});
//створюємо store
let store = createStore(myApp);
let unsubscribe = store.subscribe(() => console.log(store.getState()))
//VIEW
class App extends React.Component {
constructor(props) {
super(props);
}
render() {
return (
<div>
<p>value: {this.props.price}</p>
<a href="#" onClick={this.props.onClick}>click</a><b>{this.props.message}</b>
</div>
)
}
}
//mapStateToProps() для чтения состояния и mapDispatchToProps() для передачи события
const mapStateToProps = (state, ownProps) => {
return {
message: state.hello.message,
price: state.price.value
}
};
const mapDispatchToProps = (dispatch, ownProps) => {
return {
onClick: () => {
var items= ['HELLO','buy','DELETE','error']
var item = items[Math.floor(Math.random()*items.length)];
dispatch({ type: item })
}
}
}
const ConnectedApp = connect(
mapStateToProps,
mapDispatchToProps
)(App);
ReactDOM.render(
<Provider store={store}>
<ConnectedApp />
</Provider>,
document.getElementById('app')
);