React Native, redux function not going to original state after updating - javascript

I created a function to view cart details below every screen when a user add a item to cart and when user remove function cart details will hide again, but when I remove item from cart, cart details not hidding, can someone tell me what's wrong, below is my code
reducer
if (action.type === SHOW_CART) {
let addedItem = state.addedItems;
if (addedItem === 0) {
console.log(addedItem);
return {
...state,
show: state.showCart,
};
}
}
const initialstate = {
showChart: false,
addedItems: [],
}
It's my redux code where I'm performing that function, addItems is my cart which is blank array
action
export const showCart = (id) => {
return {
type: SHOW_CART,
showCart: true,
id,
};
};
Here is my action
ViewCart
{this.props.show ? (
<View style={styles.total}>
<Text style={styles.totaltext}>Total:</Text>
<Text style={styles.priceTotal}>{this.props.total}</Text>
<View style={styles.onPress}>
<Text
style={styles.pressText}
onPress={() => RootNavigation.navigate("Cart")}
>
View Cart
</Text>
</View>
</View>
) : null}
Here is my view cart detail where I showing cart details when user add item to cart
can someone please help

You should compare the length, you are currently comparing the array directly so it would also go to the false path, so change that first.
if (action.type === SHOW_CART) {
let addedItem = state.addedItems;
if (addedItem.length === 0) {
console.log(addedItem);
return {
...state,
show: state.showCart,
};
} else {
return {
...state,
show: action.showCart,
};
}
}

In view cart,
{this.props.show && this.props.items.length >0 ? (
<View style={styles.total}>
<Text style={styles.totaltext}>Total:</Text>
<Text style={styles.priceTotal}>{this.props.total}</Text>
<View style={styles.onPress}>
<Text
style={styles.pressText}
onPress={() => RootNavigation.navigate("Cart")}
>
View Cart
</Text>
</View>
</View>
) : null}
const mapStateToProps = (state) => {
return {
total: state.clothes.total,
show: state.clothes.show,
items: state.clothes.addedItems,
};
};

Related

Accessing child state from parent

Background
I'm building an app which has at some point a FlatList which renders products. The code for the list looks like this:
<FlatList
data={data}
renderItem={({ item }) => (
<View style={styles.container}>
<View style={styles.left}>
<Text style={styles.title}>{item.name}</Text>
<Text style={styles.description}>{item.description}</Text>
<Text style={styles.price}>${item.price}</Text>
<Counter />
</View>
<Image style={styles.right} source={{uri: item.image}}/>
</View>
)}
/>
The data for this list is brought over from a Google Cloud Firestore document. Within this list you can see a component called Counter, its job is to allow the user to add and delete products from their cart. This is its code:
export default function Counter () {
const [count, setCount] = useState(0);
const handleAddition=()=>{
setCount(count + 1)
}
const handleDeletion=()=>{
{count === 0 ? setCount(count) : setCount(count - 1)}
}
return (
<View style={styles.adder}>
<TouchableOpacity onPress={() => {handleDeletion()}}>
<Text style={styles.less}>-</Text>
</TouchableOpacity>
<Text style={styles.counter}>{count}</Text>
<TouchableOpacity onPress={() => {handleAddition()}}>
<Text style={styles.more}>+</Text>
</TouchableOpacity>
</View>
)
}
Problem
As you can see from the fact that I'm rendering the counter within a FlatList, I need to keep the state stored in the child rather than in the parent, as having the count in the parent would mean that if the user selects one product, every item is added at the same time.
I need to have the a button show up when the user selects a product that allows them to navigate to their purchase summary and also I need that button to display the total cost of their selection and amount of products chosen. As you might imagine, I've no idea how to access the child's state in the parent component.
So to sum it all up:
I have a child with a state update that I need to access from its parent, but I do not know how to do it.
Question¨
Is there any way to listen to event changes in a child's state or passing it up as a prop or something like that?
Thanks a lot in advance!
Extra information
This is image shows the UI of the screen. When pressing the "+" button it updates the count +1 and it should also display a button showing the info I mentioned before.
In renderItem you can pass method callback in here
<Counter onPressFunctionItem={(isPlus) => { // handle from parent here }} />
export default function Counter ({ onPressFunctionItem }) {
const [count, setCount] = useState(0);
const handleAddition=()=>{
setCount(count + 1)
if (onPressFunctionItem) {
onPressFunctionItem(true)
}
}
const handleDeletion=()=>{
{count === 0 ? setCount(count) : setCount(count - 1)}
if (onPressFunctionItem) {
onPressFunctionItem(false)
}
}
return (...)
}
Final Output:
You don't really need to pass the child component's state to the parent to achieve the same result, you can do that very easily the conventional way.
Here is the source code of above example:
export default function App() {
const [products, setProducts] = useState(data);
/*
with this function we increase the quantity of
product of selected id
*/
const addItem = (item) => {
console.log("addItem");
let temp = products.map((product) => {
if (item.id === product.id) {
return {
...product,
quantity: product.quantity + 1,
};
}
return product;
});
setProducts(temp);
};
/*
with this function we decrease the quantity of
product of selected id, also put in the condition so as
to prevent that quantity does not goes below zero
*/
const removeItem = (item) => {
console.log("removeItem");
let temp = products.map((product) => {
if (item.id === product.id) {
return {
...product,
quantity: product.quantity > 0 ? product.quantity - 1 : 0,
};
}
return product;
});
setProducts(temp);
};
/*
this varible holds the list of selected products.
if required, you can use it as a seperate state and use it the
way you want
*/
let selected = products.filter((product) => product.quantity > 0);
/**
* below are two small utility functions,
* they calculate the total itmes and total price of all
* selected items
*/
const totalItems = () => {
return selected.reduce((acc, curr) => acc + curr.quantity, 0);
};
const totalPrice = () => {
let total = 0;
for (let elem of selected) {
total += elem.quantity * elem.price;
}
return total;
};
useEffect(() => {
console.log(products);
}, [products]);
return (
<View style={styles.container}>
<FlatList
data={products}
renderItem={({ item }) => {
return (
<Card style={styles.card}>
<View style={styles.textBox}>
<Text>{item.name}</Text>
<Text>$ {item.price.toString()}</Text>
<View style={{ flexDirection: "row" }}></View>
<View style={styles.buttonBox}>
<Button
onPress={() => removeItem(item)}
title="-"
color="#841584"
/>
<Text>{item.quantity.toString()}</Text>
<Button
onPress={() => addItem(item)}
title="+"
color="#841584"
/>
</View>
</View>
<Image
style={styles.image}
source={{
uri: item.image,
}}
/>
</Card>
);
}}
/>
<View style={{ height: 60 }}></View>
{selected.length && (
<TouchableOpacity style={styles.showCart}>
<View>
<Text style={styles.paragraph}>
{totalItems().toString()} total price ${totalPrice().toString()}
</Text>
</View>
</TouchableOpacity>
)}
</View>
);
}
You can find the working app demo here: Expo Snack

React Native, state changing for all items instead of clicked item

I created a button in flat list, when user click an specific item, it's button should change state and increment button should appear, but button changing state for all the items. I pass id too but it's not working, can someone please help me... below is my code
Items.js
<FlatList
data={this.props.items}
extraData={this.props}
keyExtractor={(items) => items.id.toString()}
numColumns={2}
renderItem={({ item }) => (
<CardBuyItem>
<Image style={styles.image} source={item.image} />
<View style={styles.detailContainer}>
<Text style={styles.title}>{item.title}</Text>
<Text style={styles.subTitle} numberOfLines={1}>
{item.subTitle}
</Text>
<Text style={styles.price}>Rs {item.price}</Text>
</View>
{this.props.button && this.props.added.length > 0 ? (
<View style={styles.add}>
<Text style={styles.quantity}>{item.quantity}</Text>
<MaterialCommunityIcons
style={styles.iconUp}
size={20}
name="plus-circle-outline"
onPress={() => this.props.addQuantity(item.id)}
/>
<MaterialCommunityIcons
style={styles.iconDown}
size={20}
name="minus-circle-outline"
onPress={() => this.props.subtractQuantity(item.id)}
/>
</View>
) : (
<View style={styles.buy}>
<Text
style={styles.buyonce}
onPress={() => {
this.props.addToCart(item.id);
this.props.showCart();
this.props.showButton(item.id);
}}
>
Buy Once
</Text>
</View>
)}
</CardBuyItem>
)}
/>
const mapStateToProps = (state) => {
return {
items: state.clothes.jeans,
button: state.clothes.showButton,
added: state.clothes.addedItems,
};
};
const mapDispatchToProps = (dispatch) => {
return {
addToCart: (id) => dispatch(addToCart(id)),
addQuantity: (id) => dispatch(addQuantity(id)),
subtractQuantity: (id) => dispatch(subtractQuantity(id)),
showCart: () => dispatch(showCart()),
showButton: (id) => dispatch(showButton(id)),
};
};
That's my item list with mapStateToProsp and mapDispatchToProps here button should change it's state
reducer.js
if (action.type === SHOW_BUTTON) {
let addedItem = state.jeans.find((item) => item.id === action.id);
return {
...state,
addedItem: addedItem,
showButton: action.showButton,
};
}
const initialstate = { showButton: false}
it's my reducer function with initial state of that button
action.js
export const showButton = (id) => {
return {
type: SHOW_BUTTON,
showButton: true,
id,
};
};
it's my action where I'm describing action for my reducer
You are having a common state variable for this which causes it to show all buttons.
You can do a simple solution like this.
In your flatlist you can have a logic to display the button
{this.props.added.find(x=>x.id==item.id) !=null ? (
Or if you have to use the reducer, you will have to have a property in the array and update it which would be complex to maintain.

React Native , unable to add item in cart by using react -redux

I created a cart screen and list of items using react native and redux, but when I click buy item is not adding in cart and it's also not showing any error
Below is my code where I store list of items
Jeans.js
class Jeans extends Component {
render() {
return (
<View style={styles.container}>
<FlatList
data={this.props.items}
key={(items) => items.id.toString()}
numColumns={2}
renderItem={({ item }) => (
<CardBuyItem>
<Image style={styles.image} source={item.image} />
<View style={styles.detailContainer}>
<Text style={styles.title}>{item.title}</Text>
<Text style={styles.subTitle} numberOfLines={1}>
{item.subTitle}
</Text>
<Text style={styles.price}>Rs {item.price}</Text>
</View>
<TouchableHighlight onPress={() => this.props.addToCart(item.id)}>
<View style={styles.buy}>
<Text>Buy Once</Text>
</View>
</TouchableHighlight>
</CardBuyItem>
)}
/>
</View>
);
}
}
const mapStateToProps = (state) => {
return {
items: state.clothes.jeans,
};
};
const mapDispatchToProps = (dispatch) => {
return {
addToCart: (id) => dispatch(addToCart(id)),
};
};
Below is my code of cart screen where items should added when user click by
cart.js
class Cart extends Component {
render() {
let addedItems =
this.props.items && this.props.items.length ? (
<FlatList
data={this.props.items}
key={(items) => items.id.toString()}
numColumns={2}
renderItem={({ item }) => (
<View>
<Image style={styles.image} source={item.image} />
<View style={styles.detailContainer}>
<Text style={styles.title}>{item.title}</Text>
<Text style={styles.subTitle} numberOfLines={1}>
Quantity: {item.quantity}
</Text>
<Text style={styles.price}>Rs {item.price}</Text>
</View>
<TouchableOpacity>
<View style={styles.buy}>
<Text>Remove</Text>
</View>
</TouchableOpacity>
</View>
)}
/>
) : (
<View style={styles.emptyContainer}>
<Text style={styles.empty}>There is Nothing in your Cart</Text>
</View>
);
return (
<View style={styles.container}>
<View style={styles.order}>
<Text style={styles.orderText}>You Order:</Text>
</View>
<View>{addedItems}</View>
</View>
);
}
}
const mapStateToProps = (state) => {
return {
items: state.addedItems,
};
};
And below is my code reducer and action
reducer.js
export default function ClothesReducer(state = initialstate, action) {
if (action.type === ADD_TO_CART) {
let addedItem = state.jeans.find((item) => item.id === action.id);
let existed_item = state.addedItems.find((item) => action.id === item.id);
if (existed_item) {
addedItem.quantity += 1;
return {
...state,
total: state.total + addedItem.price,
};
} else {
addedItem.quantity = 1;
let newTotal = state.total + addedItem.price;
return {
...state,
addedItems: [...state.addedItems, addedItem],
total: newTotal,
};
}
} else {
return state;
}
}
action.js
import { ADD_TO_CART } from "./ClothesActionType";
export const addToCart = (id) => {
return {
type: ADD_TO_CART,
id,
};
};
I'm trying to figure out what's wrong but can't find any error. Can someone help me to fix this?
In cart.js you should replace this
const mapStateToProps = (state) => {
return {
items: state.addedItems,
};
};
With
const mapStateToProps = (state) => {
return {
items: state.clothes.addedItems,
};
};
You are mutating state, here is some info on how to not do that.
I think your reducer should look something like this:
export default function ClothesReducer(
state = initialstate,
action
) {
if (action.type === ADD_TO_CART) {
let addedItem = state.jeans.find(
(item) => item.id === action.id
);
let existed_item = state.addedItems.find(
(item) => action.id === item.id
);
const addedItems = existed_item
? state.addedItems.map((item) =>
item === existed_item
? { ...item, quantity: item.quantity + 1 }
: item
)
: [
...state.addedItems,
{ ...addedItem, quantity: 1 },
];
return {
...state,
addedItems,
total: state.total + addedItem.price,
};
} else {
return state;
}
}

I can't remove items from FlatList one by one by clicking via redux in react native

I've created a simple shopping app which has three screens including: Home, Book Store and Cart screens
I am using redux for updating all states, Everything works smoothly except one thing. I want the users when click on items in Cart they get removed one by one but the problem is that when i click on one item all of them get removed simultaneously
How can i fix this?
Reducer code:
import {
ADD,
REMOVE
} from '../actions/types';
const initialState = {
items: [],
counter: 0
}
export const cardReducer = (state = initialState, action) => {
switch (action.type) {
case ADD:
return {
...state, items: state.items.concat({
name: action.payload.name,
index: Math.random(),
price: action.payload.price,
id: action.payload.id
}), counter: state.counter + 1 }
break;
case REMOVE:
return {
...state,
items: state.items.filter((item) => {
item.index !== action.payload
}), counter: state.counter - 1 }
break;
default:
return state;
}}
Action code:
import {
ADD,
REMOVE
} from './types';
export const addSomething = (item) => {
return {
type: ADD,
payload: item
}}
export const removeSomething = (index) => {
return {
type: REMOVE,
payload: index
} }
And this is the Cart screen codes:
import { useDispatch, useSelector } from 'react-redux';
import { addSomething, removeSomething } from '../actions';
const Cards = (props) => {
const { navigation } = props;
const dispatch = useDispatch();
const items = useSelector(state => state.cardR.items)
return (
<View style={{ alignItems: 'center', flex: 1 }}>
<View style={{ width: '80%' }}>
<FlatList
data={items}
keyExtractor={(item, index) => index}
renderItem={({ item, index }) => (
<TouchableOpacity
style={styles.button}
activeOpacity={0.7}
onPress={(index) => dispatch(removeSomething(item.index))}
>
<Text style={{ color: 'white' }}>{item.name}</Text>
<Text style={{ color: 'white' }}>{item.price}</Text>
</TouchableOpacity>
)} />
</View>
</View>
)}
The problem seems in the index property, likewise #giotskhada has mentioned in his response. The possible solution could be to check the removable item with its id instead of the index, which is already there to uniquely identify the each item.
Try this instead -
Reducer Code:
case REMOVE:
return {
...state,
items: state.items.filter((item) => {
return item.id !== action.payload // Id instead of index and return statement
}),
counter: state.counter - 1
}
break;
Cart Screen Code:
<TouchableOpacity
style={styles.button}
activeOpacity={0.7}
onPress={(index) => dispatch(removeSomething(item.id))}> // id instead of index
<Text style={{ color: 'white' }}>{item.name}</Text>
<Text style={{ color: 'white' }}>{item.price}</Text>
</TouchableOpacity>
I believe the problem here is that index property is the same for all items.
Having randomly generated indices is a bad idea on its own, because you might end up with duplicate indices. Moreover, Math.random() is a pseudo-random floating-point number generator and shouldn't be trusted.
If you want to have a different index for each item, you have to do that another way. For example, you can have a idCounter state, which only increases by one when a new item is added. Then you can set new item's index to state.idCounter and be sure that it will be different from all the others.

ListView is not re-rendering after dataSource has been updated

I am trying to implement a todo app in react-native with features like addTodo, removeTodo, markCompleted todos. After adding todos, when I press on markComplete text, the listView is not re-rendering, if I reload the app it displays expected results. I am using Firebase database to fetch my todos from.
Basically, I am updating a property in my listView datasource when I click on markComplete. Everything is working fine expect the re-rendering of listView whenever I press markComplete or Completed buttons on UI. I have tried a few solutions suggested in related question, I couldnt get it working.
To be more specific: please look at code below comment // When a todo is changed. I am updating my datasource in those lines of code when I changes something in items array.
Below is my code and snapshot of app UI.
import React, { Component } from 'react';
import {
AppRegistry,
StyleSheet,
ListView,
Text,
View,
TouchableHighlight,
TextInput
} from 'react-native';
var Firebase = require('firebase');
class todoApp extends Component{
constructor(props) {
super(props);
var myFirebaseRef = new Firebase('[![enter image description here][1]][1]database URL');
this.itemsRef = myFirebaseRef.child('items');
this.state = {
newTodo: '',
completed: false,
todoSource: new ListView.DataSource({rowHasChanged: (row1, row2) => row1 !== row2})
};
this.handleKey = null;
this.items = [];
} // End of Constructor
componentDidMount() {
// When a todo is added
this.itemsRef.on('child_added', (dataSnapshot) => {
this.items.push({
id: dataSnapshot.key(),
text: dataSnapshot.child("todo").val(),
completedTodo: dataSnapshot.child("completedTodo").val()
});
this.setState({
todoSource: this.state.todoSource.cloneWithRows(this.items)
});
});
// When a todo is removed
this.itemsRef.on('child_removed', (dataSnapshot) => {
this.items = this.items.filter((x) => x.id !== dataSnapshot.key());
this.setState({
todoSource: this.state.todoSource.cloneWithRows(this.items)
});
});
// When a todo is changed
this.itemsRef.on('child_changed', (dataSnapshot) => {
this.items.forEach(function (value) {
if(value["id"] == this.handleKey){
this.items["value"]["completedTodo"]= dataSnapshot.child("completedTodo").val()
}
});
this.setState({
todoSource: this.state.todoSource.cloneWithRows(this.items)
});
});
}
addTodo() {
if (this.state.newTodo !== '') {
this.itemsRef.push({
todo: this.state.newTodo,
completedTodo: this.state.completed,
});
this.setState({
newTodo : ''
});
}
console.log(this.items);
}
removeTodo(rowData) {
this.itemsRef.child(rowData.id).remove();
}
handleCompleted(rowData){
this.handleKey = rowData.id;
if(rowData.completedTodo){
this.itemsRef.child(rowData.id).update({
completedTodo: false
})
}
if(rowData.completedTodo == false){
this.itemsRef.child(rowData.id).update({
completedTodo: true
})
}
}
renderRow(rowData) {
return (
<View>
<View style={styles.row}>
<TouchableHighlight
underlayColor='#dddddd'
onPress={() => this.removeTodo(rowData)}>
<Text style={styles.todoText}>{rowData.text}</Text>
</TouchableHighlight>
<TouchableHighlight underlayColor='#dddddd' onPress={() => this.handleCompleted(rowData)}>
{rowData.completedTodo? <Text style={styles.todoText}>Completed</Text>:<Text style={styles.todoText}>MarkCompleted</Text>}
</TouchableHighlight>
</View>
<View style={styles.separator} />
</View>
);
}
render() {
return (
<View style={styles.appContainer}>
<View style={styles.titleView}>
<Text style={styles.titleText}>
My Todos
</Text>
</View>
<View style={styles.inputcontainer}>
<TextInput style={styles.input} onChangeText={(text) => this.setState({newTodo: text})} value={this.state.newTodo}/>
<TouchableHighlight
style={styles.button}
onPress={() => this.addTodo()}
underlayColor='#dddddd'>
<Text style={styles.btnText}>Add!</Text>
</TouchableHighlight>
</View>
<ListView
dataSource={this.state.todoSource}
renderRow={this.renderRow.bind(this)} />
</View>
);
}
} // Main Class End
Make sure to create new objects instead of updating the properties of existing objects.
If you want to update listView, create new objects instead of updating
the properties of existing objects.
The below code resolved a similar issue on Github.
let newArray = oldArray.slice();
newArray[indexToUpdate] = {
...oldArray[indexToUpdate],
field: newValue,
};
let newDataSource = oldDataSource.cloneWithRows(newArray);
For more detailed explanation, This answer might help you.

Categories

Resources