I'm learning React Redux at the moment and I'm working on a food list in which the user can add and remove items from a Flatlist, up until now I worked on adding the items, which works perfectly, now I'm using the same approach to remove the item from the global state foodList, I use onLongPress to start the function removeFromFoodList in the Diet screen. When I run the code and I proceed to remove the items instead of deleting the single item it deletes all the items in the Flatlist. Thank you for your help.
Diet
class Diet extends Component {
removeItem = () => {
let foodList = this.props.foodList;
this.props.removeFromFoodList(foodList)
}
render() {
return (
<FlatList
data={this.props.foodList}
renderItem={({item}) => (
<View>
<TouchableOpacity
onLongPress={this.removeItem}
>
<Text>{item.foodName}</Text>
<Text>
{item.calories}
</Text>
<MaterialIcons name="arrow-forward-ios" />
</TouchableOpacity>
</View>
)}
keyExtractor={item => item.id}
/>
}
}
function mapStateToProps(store){
return{
foodList: store.userState.foodList
};
}
const mapDispatchToProps = { removeFromFoodList };
export default connect(mapStateToProps, mapDispatchToProps)(Diet);
INDEX
import { ADD_FOOD, REMOVE_FOOD } from "../constants/index";
export const updateFoodList = (foodList) => {
return { type: ADD_FOOD, payload: foodList}
}
export const removeFromFoodList = (foodList) => {
return { type: REMOVE_FOOD, payload: foodList}
}
REDUCERS
import { ADD_FOOD, REMOVE_FOOD } from "../constants";
const initialState = {
foodList: [],
};
export const user = (state = initialState, action) => {
switch (action.type){
case ADD_FOOD:
return{
...state,
foodList: [...action.payload],
}
case REMOVE_FOOD:
return{
...state,
foodList: [...state.foodList.filter((item) => item.id != action.id)],
}
default:
return state
}
};
ARRAY EXAMPLE
Array [
Object {
"calories": "120",
"foodId": 0.8845240802796346,
"foodName": "Rice",
},
]
I'm not sure why it returns as empty, but there are a few problems I see here.
In your reducer:
[...state.foodList.filter((item) => item.id != action.id)]
If the structure of foodlist is as provided:
Object {
"calories": "120",
"foodId": 0.8845240802796346,
"foodName": "Rice",
},
]
Then it has no id key, and even if it does, action.id doesn't exist (only action.type and action.payload exist). Try console logging action and state.foodList under case REMOVE_FOOD: to get more detail.
On the component Diet this.props.foodList contain all data of the flatlist it's ok , you pass it to the flatlist to be rendered , alwais ok , but on each item of Flatlist you have added on each a onLongPress={this.removeItem} the function removeItem execute removeFromFoodList that you put this.props.foodList as a parameters to be removed .. that why all list are removed
to fix this you need to pass a item value to removeItem():
removeItem = (itemToremove) => {
this.props.removeFromFoodList(itemToremove)
}
render() {
return (
<FlatList
data={this.props.foodList}
renderItem={({ item }) => (
<View>
<TouchableOpacity
onLongPress={this.removeItem(item)}
>
<Text>{item.foodName}</Text>
<Text>
{item.calories}
</Text>
<MaterialIcons name="arrow-forward-ios" />
</TouchableOpacity>
</View>
)}
keyExtractor={item => item.id}
/>
)
}
Related
I created a view cart in which I show total price and view cart button, when I add item it makes condition true and display that cart below in every screen, but when I click view cart it's not making it false again, how can I do this? can someone check my code and tell me please. Below is my code
Viewcart.js
<View>
{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");
this.props.show;
}}
>
View Cart
</Text>
</View>
</View>
) : null}
</View>
const mapStateToProps = (state) => {
return {
show: state.clothes.show,
};
};
const mapDispatchToProps = (dispatch) => {
return {
showCart: () => dispatch(showCart()),
};
};
reducer.js
if (action.type === SHOW_CART) {
let addedItem = state.addedItems;
if (addedItem.length === 0) {
return {
...state,
show: state.showCart,
};
} else {
return {
...state,
show: action.showCart,
};
}
}
const initialstate = {
showCart: false
}
action.js
export const showCart = (id) => {
return {
type: SHOW_CART,
showCart: true,
id,
};
};
As per the chat the requirement is to toggle this when exiting the screen so the easiest way to do that is to use the lifecycle methods.
To hide use componentDidMount
componentDidMount(){
this.props.showCartOff();
}
to show use component
componentWillUnmount(){
this.props.showCart();
}
I'm beginner in react native, and I want to use for loop in flatlist to push require data,
render() {
return (
<View style={styles.container}>
<FlatList
data={[
require("./assest/image1.jpg"),
require("./assest/image2.jpg"),
require("./assest/image3.jpg"),
]}
renderItem={({ item }) => {
return <ListItem image={item} />
}}
keyExtractor={
(index) => {return index}
}
/>
</View>
)
}
}
Like when pushing array from state using for loop
state ={
a: [12 , 13 , 14 ,15 , 19 ,21 ]
b: "1"
d = () => {
let c =[];
for (var i =0; i<= this.state.a.length - 1 ; i++) {
c.push( <child text = {this.state.a[i] />);
}
return c;
};
Is there anyway to use looping in flatlist or we can't using any looping in list or flatlist in
React Native.
You can define a state (array) and then loop through your required data and push them into the array state. Then, pass that state into the data prop of the FLatlist.
You can do something like this...
import React, { Component } from 'react';
import { View, FlatList } from 'react-native';
import ListItem from '...'; //import the ListItem here
export default class App extends Component {
constructor(props) {
super(props);
this.state = {
data: [],
}
}
componentDidMount = () => {
let data = [];
const images = {
image1: require("./assest/image1.jpg"),
image2: require("./assest/image2.jpg"),
image3: require("./assest/image3.jpg"),
}
images.forEach(image => {
data.push(image);
this.setState({ data })
})
}
render() {
return (
<View style={styles.container}>
<FlatList
data={this.state.data}
renderItem={({ item }) => {return <ListItem image={item} />}}
keyExtractor={(index) => {return index}}
/>
</View>
);
}
}
Here, when the component mounts, it loops through the 'images' object and push them into an array called 'data' and store it to the state. Then, that state is passed to the Flatlist as a prop.
Please go through this and ask me if you have any further questions regarding this.
I render a list of items. On click (on each item), I am showing a details view of that item by making an api request by Id. The problem is that when I click on lets say Item1 and Item 2, the object I receive for Item2 'overrides' Item1'- meaning I always show same details data for all items, which makes sense because I re-render with the newly requested data (item2 for example). Ive been thinking and trying to find my mistake for so long now, any help would be deeply appreciated !!!!!!!!!!
my action:
export const fetchItem = (id) => {
return dispatch => {
dispatch({type: FETCH_ITEM})
return ItemAPI.getItem(id)
.then(item => {
dispatch({type: FETCH_ITEM_SUCCESS, payload: item})
})
}
}
reducer:
export default (state = initialState, action) => {
case FETCH_ITEM_SUCCESS:
return {
...state,
item: action.payload
}
}
my container:
class AppContainer extends React.Component {
componentDidMount() {
this.props.fetchItems()
}
getItem = (id) => {
this.props.fetchItem(id)
}
renderItem = ({item}) => (
<WatchedItem
itemSummary={item}
itemDetails={this.props.item}
getItem={this.getItem}
/>
)
render() {
return (
<View>
<FlatList
data={this.props.items}
renderItem={this.renderItem}
/>
</View>
)
}
}
mapStateToProps = state => {
return {
items: _.map(_.values(state.watchList.items), "item"),
item: state.watchList.item
}
}
export default connect(mapStateToProps,
{fetchItems, fetchItem})(AppContainer)
Item:
export default class WatchedItem extends React.Component {
state = {
showDetailView: false
}
getItem = () => {
this.props.getCoin(this.props.itemSummary.id)
this.setState({
showDetailView: !this.state.showDetailView
})
}
render() {
const {itemDetails, itemSummary: {name, symbol}} = this.props;
return (
<View>
<View>
<Text> {name} </Text>
// HERE IS THE PROBLEM. WHEN I CLICK ON MULTIPLE ITEMS, THE DETAILS ARE SHOWN FOR ALL BUT ONLY WITH LATEST FETCHED ITEM
<Button onPress={this.getItem}>Show Details</Button>
</View>
<View>
{this.state.showDetailView ? <WatchedItemDetailView item={itemDetails}/> : null}
</View>
</View>
)
}
}
the data looks about like this:
items: [{id:1, name: "item1"}, {id:2, name: "item2"}, ...]
item: {id:1, name: "item1", symbol: "xxx", somethingelse: "else",...}
Ps. I cannot already save all the detailsinfo of Item in "items", because that data constantly changes, so I do need to request it seperate to get current data.
Your problem is in how you're storing the items, i.e you're only saving one under the item key. Instead you could save all of them items as an object, e.g.
case FETCH_ITEM_SUCCESS:
return {
...state,
items: {
...state.items,
[action.payload.id]: action.payload
}
}
}
(it wasn't clear from your question what the state.watchList data looked like, propose changing it to just an id).
mapStateToProps = state => {
return {
items: Object.values(state.items),
item: state.items[state.watchList.id]
}
}
I'm trying to create dynamic checkbox with the name fetching from json, this issue looks same as I need, but without the code explaining, I can't archieve my goal,
I have a json example like this :
this.state = {
data : [
{
"name": "ALL",
},
{
"name": "Android",
},
{
"name": "iOS",
},
{
"name": "React Native",
}
]}
and with this code below:
<CheckBox
center
title={this.state.data[1].name}
onPress={() => {this.setState({checked: !this.state.checked})}}
checked={this.state.checked}
/>
the checkbox running well but it's just showing 2nd value of json
My Goal is to displaying all of json value into flatlist and makes checkbox running well,
For now I just can displaying those json into FlatList, but the checkbox is not works
import React, { Component } from 'react';
import {
Text, View, StyleSheet, Alert, FlatList
} from 'react-native';
import Dimensions from 'Dimensions';
import { CheckBox } from 'react-native-elements'
const DeviceWidth = Dimensions.get('window').width;
const DeviceHeight = Dimensions.get('window').height;
class MedicalClearlance extends React.Component {
constructor(props){
super(props);
this.state = {
checked:[],
data : [
{
"name": "ALL",
},
{
"name": "Android",
},
{
"name": "iOS",
},
{
"name": "React Native",
}
]}
}
render() {
return (
<FlatList
data={ this.state.data }
renderItem={({item, index}) =>
<CheckBox
center
title={item.name}
onPress={() => {this.setState({checked: !this.state.checked}), console.log(this.state.checked +' '+ index)}}
checked={this.state.checked}/>
}
/>
);
}
}
anyone can help me how to archieve my goal?
The answer that Ahsan Ali provided will work. However it is missing a very vital line of code.
Within the <FlatList/> component, be sure to add this extraData
={this.state}. This will allow the FlatList component to re-render whenever the state is changed.
The render method will then look like this:
handleChange = (index) => {
let checked = [...this.state.checked];
checked[index] = !checked[index];
this.setState({ checked });
}
render() {
let { data, checked } = this.state;
return (
<FlatList
data={data}
extraData={this.state}
renderItem={({ item, index }) =>
<CheckBox
center
title={item.name}
onPress={() => this.handleChange(index)}
checked={checked[index]} />
}
/>
);
}
By passing extraData={this.state} to FlatList we make sure FlatList
itself will re-render when the state.selected changes. Without setting
this prop, FlatList would not know it needs to re-render any items
because it is also a PureComponent and the prop comparison will not
show any changes.
More information can be found at React-Native Flat-List documentation here.
If you're using the code from Ahsun Ali's post, there may be another error you come across.
A warning error displays that the componentWillMount() method is
deprecated. In which case be sure to use the componentDidMount()
instead.
Hope this helps people!
You need to fill up the checked array in order to manipulate it after.
constructor() {
super();
this.state = {
data: [
{
"name": "ALL",
},
{
"name": "Android",
},
{
"name": "iOS",
},
{
"name": "React Native",
}
],
checked: []
}
}
componentWillMount() {
let { data, checked } = this.state;
let intialCheck = data.map(x => false);
this.setState({ checked: intialCheck })
}
and pass the index of the selected checkbox to update it
handleChange = (index) => {
let checked = [...this.state.checked];
checked[index] = !checked[index];
this.setState({ checked });
}
render() {
let { data, checked } = this.state;
return (
<FlatList
data={data}
renderItem={({ item, index }) =>
<CheckBox
center
title={item.name}
onPress={() => this.handleChange(index)}
checked={checked[index]} />
}
/>
);
}
I hope it helps!
you could try this for multiple selection, ref link-> https://facebook.github.io/react-native/docs/flatlist
class MyListItem extends React.PureComponent
{
_onPress = () => {
this.props.onPressItem(this.props.id);
};
render() {
const textColor = this.props.selected ? 'red' : 'black';
return (
<TouchableOpacity onPress={this._onPress}>
<View>
<Text style={{color: textColor}}>{this.props.title}</Text>
</View>
</TouchableOpacity>
);
}
}
class MultiSelectList extends React.PureComponent {
state = {selected: (new Map(): Map<string, boolean>)};
_keyExtractor = (item, index) => item.id;
_onPressItem = (id: string) => {
// updater functions are preferred for transactional updates
this.setState((state) => {
// copy the map rather than modifying state.
const selected = new Map(state.selected);
selected.set(id, !selected.get(id)); // toggle
return {selected};
});
};
_renderItem = ({item}) => (
<MyListItem
id={item.id}
onPressItem={this._onPressItem}
selected={!!this.state.selected.get(item.id)}
title={item.title}
/>
);
render() {
return (
<FlatList
data={this.props.data}
extraData={this.state}
keyExtractor={this._keyExtractor}
renderItem={this._renderItem}
/>
);
}
}
for(const ele of this.state.selected.keys())
console.log(ele);
//**after that you could get all the selected keys or values from something like this**
I have two components who use the same reducer and share the same state.
The first one is a form, the second one is a separate component to
update a specific field of that form using
react-native-autocomplete-select.
In the second component, everything works fine. But when I get back
to the first component (the form), the prop that I'm updating in the
second component is now undefined. Only when I leave the component
and come back to it or reload my app does the component display the
correct value.
I'm new to redux and I thought I had figured it out but apparently, I'm still missing something.
I'll try to share as much code as possible in order to make it easy for anyone to help me out but let me know if you want me to share additional code.
I would really like to understand what's going on.
First Component
class EditElem extends Component {
componentWillMount() {
this.props.xFetch();
}
onButtonPress() {
const { name, description, elem_id } = this.props;
this.props.xSave({ name, description, elem_id });
}
render() {
return (
<ScrollView>
<View>
<Text>Information</Text>
<CardSection>
<Input
label="Name"
placeholder="..."
value={this.props.name}
onChangeText={value => this.props.xUpdate({ prop: 'name', value })}
/>
<Text style={styles.labelStyle}>Description</Text>
<Input
placeholder="Write here..."
value={this.props.description}
onChangeText={value => this.props.xUpdate({ prop: 'description', value })}
multiline = {true}
numberOfLines = {4}
/>
<TouchableWithoutFeedback onPress={ () => Actions.selectElem() }>
<View style={styles.wrapperStyle}>
<View style={styles.containerStyle}>
<Text style={styles.labelStyle}>Elem</Text>
<Text adjustsFontSizeToFit style={styles.inputStyle}>{checkElem(this.props.elem_id ? this.props.elem_id.toString() : "0")}</Text>
</View>
</View>
</TouchableWithoutFeedback>
</CardSection>
<Button title="Save Changes" onPress={this.onButtonPress.bind(this)} />
</View>
</ScrollView>
);
}
}
const mapStateToProps = (state) => {
const { name, description, elem_id } = state.x.x;
return { name, description, elem_id };
};
export default connect(mapStateToProps, { xUpdate, xFetch, xSave })(EditElem);
Second Component
class SelectElem extends Component {
componentWillMount() {
this.props.xFetch();
}
saveElem(suggestion) {
let elem_id = suggestion.id;
let text = suggestion.text
this.props.xUpdate({ prop: 'elem', text })
this.props.xUpdate({ prop: 'elem_id', elem_id })
this.props.xSave({ elem_id });
}
render() {
const suggestions = data
const onSelect = (suggestion) => {
this.saveElem(suggestion);
}
return(
<View style={{flex: 1}}>
<AutoComplete
placeholder={checkElem(this.props.elem_id ? this.props.elem_id.toString() : "0")}
onSelect={onSelect}
suggestions={suggestions}
suggestionObjectTextProperty='text'
value={this.props.elem}
onChangeText={value => this.props.xUpdate({ prop: 'elem', value })}
minimumSimilarityScore={0.4}
/>
</View>
)
}
}
const mapStateToProps = (state) => {
const { elem_id, description, name, elem } = state.x.x;
return { elem_id, description, name, elem };
};
export default connect(mapStateToProps, { xUpdate, xFetch, xSave })(SelectElem);
store
const store = createStore(reducers, {}, compose(applyMiddleware(ReduxThunk)));
reducer
export default function(state = INITIAL_STATE, action) {
switch (action.type) {
case FETCH_X:
return { ...state, x: { ...state.x, name: action.payload.name, description: action.payload.description, elem_id: action.payload.elem_id } };
case UPDATE_X:
return { ...state, x: { ...state.x, [action.payload.prop]: action.payload.value }};
case SAVE_X:
return state;
default:
return state;
}
}
Actions
export const xUpdate = ({ prop, value }) => {
return {
type: UPDATE_X,
payload: { prop, value }
};
};
export const xSave = ({ name, description, elem_id }) => {
return (dispatch) => {
axios({
method: 'post',
url: 'https:xxxxxxxxxxxxxxxxxxxx',
data: {_____________________ }
}
}).then(response => {
dispatch({ type: SAVE_X });
}).catch(error => console.log(error))
};
};
Can you check if UPDATE_X, SAVE_X ... are defined? Do you have the right import statement at the top of the file?
Ok so my problem came from my reducer actualy in my SAVE_X:
I had:
case SAVE_X:
return { state };
Instead of this:
case SAVE_X:
return { ...state, elem: { ...state.elem, elem_id: action.payload.elem_id } };