I am making a todo list application with redux. I am able to add todos perfectly fine with redux however my toggle todos and remove todos are having problems.
The toggle todo action gets called by the redux store (I see it happening in the debugger), however, it does not update the prop to be the opposite of completed and I am not sure why.
I have tried playing around with the syntax and modeling other people's redux todo lists for hours but have not been able to solve this issue.
My toggleTodo and removeTodo actions:
export const toggleTodo = (item) => {
return {
type: TOGGLE_TODO,
id: item.id
};
};
export const removeTodo = (item) => {
return {
type: REMOVE_TODO,
id: item.id
};
};
My TodoReducer: // this is where I suspect the problem is
const initialState = {
todos: []
};
const todos = (state = initialState, action) => {
switch (action.type) {
case TOGGLE_TODO:
if (state.id !== action.id) {
return state;
}
return {
...state, completed: !state.todos.completed
};
case REMOVE_TODO: {
const newState = [...state];
newState.splice(action.id, 1);
return { ...newState };
}
My main flatlist where I call the actions:
render() {
return (
<View style={{ height: HEIGHT }}>
<FlatList
data={this.props.todos}
extraData={this.state}
keyExtractor={(item, index) => index.toString()}
renderItem={({ item }) => {
return (
<TodoItem
todoItem={item}
pressToToggle={() => this.props.toggleTodo(item)}
deleteTodo={() => this.props.removeTodo(item)}
/>
);
}}
/>
</View>
);
}
}
export default connect(mapStateToProps, { addTodo, toggleTodo, removeTodo })(MainTodo);
// I call the actions I am using here and don't use mapDispatchToProps
And my TodoItem component where I pass in the props:
class TodoItem extends Component {
render() {
const todoItem = this.props.todoItem;
return (
<View>
<TouchableOpacity
style={styles.todoItem}
onPress={this.props.pressToToggle}
>
<Text
style={{
color: todoItem.completed ? '#aaaaaa' : '#f5f5f5',
textDecorationLine: todoItem.completed ? 'line-through' : 'none',
fontSize: 16 }}
>
{todoItem.text}
</Text>
<Button
title='Remove'
color='#ff5330'
onPress={this.props.deleteTodo}
/>
</TouchableOpacity>
</View>
);
}
}
When I hit toggle todo instead of the prop changing and the line coming through over the text nothing happens.
And when I try to remove a todo I get this error- "invalid attempt to spread non-iterable instance."
when you pass a function to component, try to pass it's reference, instead of
<TodoItem
todoItem={item}
pressToToggle={() => this.props.toggleTodo(item)}
deleteTodo={() => this.props.removeTodo(item)}
/>
try
<TodoItem
todoItem={item}
pressToToggle={this.props.toggleTodo.bind(this)}
deleteTodo={this.props.removeTodo.bind(this)}
/>
and in your TodoItem component call the function like
class TodoItem extends Component {
render() {
const todoItem = this.props.todoItem;
return (
<View>
<TouchableOpacity
style={styles.todoItem}
onPress={() => this.props.pressToToggle(todoItem)} /* this line */
>
<Text
style={{
color: todoItem.completed ? '#aaaaaa' : '#f5f5f5',
textDecorationLine: todoItem.completed ? 'line-through' : 'none',
fontSize: 16 }}
>
{todoItem.text}
</Text>
<Button
title='Remove'
color='#ff5330'
onPress={this.props.deleteTodo}
/>
</TouchableOpacity>
</View>
);
}
}
Related
I'm using the #react-navigation/stack to use it on our app.
We have an issue that after we navigate to a Detail Component we need to navigate back to the Main Component, the Detail component have a Reference for a input to make a focus on the mount.
So we have this.
const input = React.createRef();
class Barcode extends React.Component {
componentDidMount() {
if(this.input){
setTimeout(() => {
this.input.current.focus();
}, 400);
}
}
moveUpdate() {
const { barcodeReducerMessageVisible, barcodeReducerMessage, barcodeReducerMessageFinished } = this.props;
if(barcodeReducerMessageFinished) {
this.props.navigation.navigate('Search');
}
}
render() {
return ( <ScrollView keyboardShouldPersistTaps={'handled'}>
<Input
label="Código de barras"
placeholder="Código de barras"
errorStyle={{ color: "red" }}
inputStyle={{ marginTop: 40, marginBottom: 40 }}
onChangeText={textBarcode => this.setState({ textBarcode })}
value={textBarcode}
ref={(ref)=>{this.input = ref}}
onBlur={() => this.Save()}
/> </ScrollView> );
}
}
So moveUpdate navigate to 'Search', on search we have something like this:
ChangeBarCode(stockidRewn = null) {
this.props.navigation.navigate('Barcode', { stockidRewn } ,{ stockidRewn: stockidRewn });
}
<ListItem
leftAvatar={{ source: { uri: searchProductReducerRepos[key].vtiger_productid } }}
key={key}
title={searchProductReducerRepos[key].description}
subtitle={description}
bottomDivider
onPress={() => this.ChangeBarCode(searchProductReducerRepos[key].stockid)}
/>
When I call onPress again to go to Barcode I get:
TypeError: undefined is not an object (_this.input.current.focus)
I don't know if the reference is not declared properly.
Any advice?
You should define the ref inside the component
class Barcode extends React.Component {
// declaring ref inside the component
input = React.createRef();
componentDidMount() {
if (this.input) {
setTimeout(() => {
this.input.current.focus();
}, 400);
}
}
moveUpdate() {
const { barcodeReducerMessageVisible, barcodeReducerMessage, barcodeReducerMessageFinished } = this.props;
if (barcodeReducerMessageFinished) {
this.props.navigation.navigate('Search');
}
}
render() {
return (<ScrollView keyboardShouldPersistTaps={'handled'}>
<Input
label="Código de barras"
placeholder="Código de barras"
errorStyle={{ color: "red" }}
inputStyle={{ marginTop: 40, marginBottom: 40 }}
onChangeText={textBarcode => this.setState({ textBarcode })}
value={textBarcode}
ref={(ref) => { this.input = ref }}
onBlur={() => this.Save()}
/> </ScrollView>);
}
}
After action call, the observable gets updated with new values but it doesn't trigger update in my observer class. The reaction occurs when I put an condition check in render which uses observable object. But I use observable object in another place inside the returned DOM as a prop condition. I couldn't understand why this happens.
Here is my observer class
#inject("store")
#observer
export default class SignupWithMobileNo extends Component {
constructor() {
super();
this.sendOTP = this.sendOTP.bind(this);
this.state = {
phoneInput: ""
};
}
static navigationOptions = {
header: null
};
componentDidMount() {
BackHandler.addEventListener("hardwareBackPress", this.handleBackButton);
}
handleBackButton() {
ToastAndroid.show("You cannot go back", ToastAndroid.SHORT);
return true;
}
sendOTP(phone) {
this.props.store.userStore.sendOTP(phone);
}
componentDidUpdate() {
console.log("component did update", this.props);
const navigation = this.props.navigation;
const { sendOTPRequest } = this.props.store.userStore;
if (sendOTPRequest.state === "succeeded") {
navigation.navigate("VerifyOTP");
}
}
render() {
const navigation = this.props.navigation;
const { sendOTPRequest } = this.props.store.userStore;
// reaction occurs when I uncomment the following lines.
// if (sendOTPRequest.state === "succeeded") {
// }
return(
<View style={styles.container}>
<Formik
initialValues={{
phone: ""
}}
onSubmit={values => {
this.sendOTP(values.phone);
}}
validate={values => {
let errors = {};
if (values.phone.length < 1) {
errors.phone = "Invalid phone number";
}
return errors;
}}
>
{({
handleChange,
handleSubmit,
setFieldTouched,
values,
errors,
touched
}) => (
<View style={styles.formBody}>
<Text style={styles.headline}>Get authenticate your account</Text>
<FormInput
onChange={handleChange("phone")}
value={values.phone}
placeholder="Enter your phone number"
keyboardType="phone-pad"
onBlur={() => {
setFieldTouched("phone");
}}
/>
<FormButton
onClickHandler={handleSubmit}
buttonText="Send OTP"
isDisabled={
values.phone.length < 1 ||
sendOTPRequest.state === "requested"
}
/>
{touched.phone && errors.phone ? (
<Text style={styles.body}> {errors.phone} </Text>
) : null}
{sendOTPRequest.state === "failed" ? (
<Text style={styles.body}> {sendOTPRequest.error_code</Text>
) : null}
</View>
)}
</Formik>
</View>
);
}
}
No subscribers to observable data in the observer's render function. Once I added that, the issue solved.
Using Expo SDK 29 for a react native application.
I would like to use a flat list component. This makes up the entirety of a SafeAreaView component. I make this point as there are lots of issues relating to a flat list inside of a scroll view which this is not.
The flat list shows a list of jobs.
I have added a jobLoading boolean to the redux state to manage when the list should show as refreshing and can confirm that this toggles as expected when firing the actions to fetch the data and the success.
When i add the props to the flat list for onRefresh and refreshing the component seems to work by showing the activity indicator in the UI but does not fire the onRefresh function. I have tried implementing the call in numerous ways but nothing happens. The result is that the activity indicator shows itself and never disappears.
As it's Expo SDK 29 the React Native version is 0.55.4
Anyone have any ideas of what to try. I've spent a couple of hours looking at this trying various things but suggestions are welcome.
Thanks in advance.
EDIT: Added the code for reference. Reducer for refreshing sets true when fetchJobs() is dispatched and false when a success or error is recieved. The console log for onRefresh never triggers.
import * as React from 'react'
import * as actions from '../../redux/actions'
import { ActivityIndicator, FlatList, KeyboardAvoidingView, Dimensions, SafeAreaView, StyleSheet, View } from 'react-native'
import { ApplicationState, JobState, Job } from '../../redux'
import { Button, Form, Input, Item, Text, Icon } from 'native-base'
import { JobListItem } from './jobListItem'
import { StateHandlerMap, compose, lifecycle, withPropsOnChange, withStateHandlers } from 'recompose'
import { connect } from 'react-redux'
interface ReduxStateProps {
jobs: JobState
refreshing: boolean
screenOrientation: string
}
interface ReduxDispatchProps {
fetchJobs: (param?: string) => any
}
export interface DataItem {
key: string
data: Job
}
interface ListProps {
jobList: DataItem[]
}
interface SearchStateProps {
timer: number | undefined
searchString: string
}
interface SearchHandlerProps extends StateHandlerMap<SearchStateProps> {
updateSearch: (searchString: string) => any
setTimer: (timer: number | undefined) => any
}
type OuterProps = {}
type InnerProps = OuterProps & ReduxStateProps & ReduxDispatchProps & ListProps & SearchStateProps & SearchHandlerProps
const enhance = compose<InnerProps, OuterProps>(
connect<ReduxStateProps, ReduxDispatchProps, OuterProps, ApplicationState>(
state => ({
jobs: state.job,
refreshing: state.jobLoading,
screenOrientation: state.screenOrientation
}),
dispatch => ({
fetchJobs: (param?: string) => dispatch(actions.jobs.request({ param }))
})
),
withPropsOnChange<ListProps, OuterProps & ReduxStateProps & ReduxDispatchProps>(
['jobs', 'screenOrientation'],
props => ({
jobList: props.jobs && Object.keys(props.jobs).map(job => ({ key: job, data: props.jobs[Number(job)] }))
})
),
withStateHandlers<SearchStateProps, SearchHandlerProps, OuterProps>(
{
timer: undefined,
searchString: ''
},
{
updateSearch: state => (searchString: string) => ({ searchString }),
setTimer: state => (timer: number | undefined) => ({ timer })
}
),
lifecycle<InnerProps, {}>({
componentDidMount() {
this.props.fetchJobs()
}
})
)
export const JobList = enhance(({ fetchJobs, jobList, refreshing, screenOrientation, searchString, setTimer, timer, updateSearch }) => {
const onSearchChange = (search: string) => {
clearTimeout(timer)
updateSearch(search)
const timing = setTimeout(() => {
fetchJobs(search)
}, 500)
setTimer(timing)
}
const onRefresh = () => {
console.log('requesting refresh')
fetchJobs()
}
return (
<SafeAreaView style={{ flex: 1}}>
<KeyboardAvoidingView style={{ flexDirection: 'row', justifyContent: 'space-evenly', paddingTop: 3, paddingRight: 3 }}>
<Form style={{ flex: 1, paddingLeft: 10, paddingRight: 10 }}>
<Item>
<Input
value={searchString}
onChangeText={(text: string) => onSearchChange(text)}
placeholder='Search'
/>
</Item>
</Form>
<Button onPress={() => {fetchJobs(); updateSearch('')}}>
<Icon name='refresh' />
</Button>
</KeyboardAvoidingView>
{refreshing &&
<View style={styles.refreshContainer}>
<Text style={{ paddingBottom: 10 }}>Fetching Data</Text>
<ActivityIndicator />
</View>
}
<FlatList
keyExtractor={item => item.key}
data={jobList}
renderItem={({ item }) =>
<JobListItem
screenOrientation={screenOrientation}
item={item}
/>
}
onRefresh={onRefresh}
refreshing={refreshing}
/>
</SafeAreaView>
)
})
const styles = StyleSheet.create({
refreshContainer: {
height: 60,
flex: 1,
flexDirection: 'column',
justifyContent: 'center',
alignItems: 'center'
}
})
I'm having the exact same issue and I'm using expo SDK 30. But my case is a little bit different. The onRefresh function is called everytime I pull, however if I scroll down my list, and scroll back up fast, the loading indicator shows up, but my onRefresh function is not called.
My refreshing prop is set on my reducer, and my onRefresh function dispatches an action that fetches data and set refreshing true and false.
Here is my code:
class NoticiasScreen extends Component {
static navigationOptions = {
header: <Header
title='Notícias Alego'
leftComponent={<Image source={require('../../../assets/images/play_grande.png')} style={imageStyle} resizeMode='contain'/>}
/>
}
constructor(props) {
super(props);
this.renderItem = this.renderItem.bind(this);
this.keyExtractor = this.keyExtractor.bind(this);
this.renderContent = this.renderContent.bind(this);
this.navigateToNoticias = this.navigateToNoticias.bind(this);
this.carregarMaisNoticias = this.carregarMaisNoticias.bind(this);
this.onRefresh = this.onRefresh.bind(this);
}
componentDidMount() {
this.props.carregarNoticias(this.props.pagina);
}
renderItem({item}) {
return (
<NoticiaListItem noticia={item} abrirNoticia={this.navigateToNoticias} />
);
}
keyExtractor(item) {
return item.id.toString();
}
navigateToNoticias(noticia) {
this.props.navigation.navigate('NoticiasExibir', { id: noticia.id });
}
onRefresh() {
console.log('onRfresh');
this.props.carregarNoticias(1, true);
}
carregarMaisNoticias() {
const { carregarNoticias, pagina } = this.props;
carregarNoticias(pagina + 1);
}
renderContent() {
const { noticias, carregandoNoticias, erroNoticias } = this.props;
if(noticias.length === 0 && carregandoNoticias) {
return (
<View style={styles.containerCenter}>
<ActivityIndicator size="large" color={colors.verde}/>
</View>
);
}
if(erroNoticias) {
return (
<View style={styles.containerCenter}>
<Text style={styles.message}>{erroNoticias}</Text>
<TouchableOpacity hitSlop={hitSlop15}>
<Text>Recarregar</Text>
</TouchableOpacity>
</View>
)
}
return (
[<TextInput
style={styles.textInput}
placeholder='Pesquise'
key='pesquisa'
underlineColorAndroid='transparent'
/>,
<FlatList
data={noticias}
renderItem={this.renderItem}
keyExtractor={this.keyExtractor}
style={styles.list}
key='lista'
onRefresh={this.onRefresh}
refreshing={carregandoNoticias}
onEndReached={this.carregarMaisNoticias}
onEndReachedThreshold={0.1}
/>]
)
}
render() {
return (
<SafeAreaView style={styles.safeArea}>
<View style={styles.container}>
{this.renderContent()}
</View>
</SafeAreaView>
);
}
}
function mapStateToProps(state) {
return {
noticias: state.intranet.noticias,
pagina: state.intranet.pagina,
erroNoticias: state.intranet.erroNoticias,
carregandoNoticias: state.intranet.carregandoNoticias
}
}
function mapDispatchToProps(dispatch) {
return {
carregarNoticias: (pagina, recarregar) => dispatch(ActionCreator.carregarNoticias(pagina, recarregar))
}
}
export default connect(mapStateToProps, mapDispatchToProps)(NoticiasScreen);
No idea what's going on. Any help is appreciated.
EDIT:
I fixed it somehow. I added the onMomentScrollBegin prop to prevent my flatList from rendering twice on Render, and that fixed this issue.
here is what I added:
constructor(props) {
super(props);
...
this.onRefresh = this.onRefresh.bind(this);
this.onMomentumScrollBegin = this.onMomentumScrollBegin.bind(this);
this.onEndReachedCalledDuringMomentum = true; //PUT THIS HERE
}
onRefresh() {
this.props.carregarNoticias(1, true);
}
carregarMaisNoticias() {
if(!this.onEndReachedCalledDuringMomentum){
const { carregarNoticias, pagina } = this.props;
carregarNoticias(pagina + 1);
this.onEndReachedCalledDuringMomentum = true;
}
}
onMomentumScrollBegin() {
this.onEndReachedCalledDuringMomentum = false;
}
render() {
<OptimizedFlatList
data={noticias}
renderItem={this.renderItem}
keyExtractor={this.keyExtractor}
style={styles.list}
key='lista'
onRefresh={this.onRefresh}
refreshing={carregandoNoticias}
onMomentumScrollBegin={this.onMomentumScrollBegin} //PUT THIS HERE
onEndReached={this.carregarMaisNoticias}
onEndReachedThreshold={0.1}
/>
}
I want to call an action from redux inside the stack navigator options. This is my code. When I use the action using this.props.action_name, it says this is not a function. What am I doing wrong here? I used the navigation.params to achieve this, but still it doesn't work.
componentDidMount() {
this.props.navigation.setParams({
referencedSharePost: this.sharePost,
referencedDoShare: this.props.doShare
});
}
sharePost = () => {
this.props.doShare();
console.log("DONE");
};
render() {
return (
<View style={styles.container}>
<ScrollView>
<WritePost profile={this.state.loggedUserProfile} />
<View style={styles.sharePostWrapper}>
<PostProfileBar profile={this.state.postedUserProfile} />
<Image
source={{
uri: "https://pbs.twimg.com/media/DWvRLbBVoAA4CCM.jpg"
}}
resizeMode={"stretch"}
style={styles.image}
/>
</View>
</ScrollView>
</View>
);
}
static navigationOptions = ({ navigation }) => {
const { params = {} } = navigation.state;
return {
headerTitle: "Share To Feed",
headerTitleStyle: {
paddingLeft: "20%",
paddingRight: "20%"
},
headerStyle: {
paddingRight: 10,
paddingLeft: 10
},
headerLeft: (
<Icon
name={"close"}
size={30}
onPress={() => {
navigation.goBack();
}}
/>
),
headerRight: (
<ButtonWithoutBackground
buttonText={styles.buttonText}
onPress={() => params.referencedSharePost()}
>
Post
</ButtonWithoutBackground>
)
};
};
This is how I map my state to props.
import { connect } from "react-redux";
import { bindActionCreators } from "redux";
import SharePostScreen from "./SharePostScreen";
import { doShare } from "../../config/actions";
const mapStateToProps = state => {
return {
sharedPost: state.mainFeed.updatedPost
};
};
const mapDispatchToProps = dispatch => {
return {
doShare: bindActionCreators(doShare, dispatch)
};
};
export default connect(mapStateToProps, mapDispatchToProps)(SharePostScreen);
I want to use this.props.doShare inside onPress() of stackNavigationOptions.
You don't need to separately connect SharePostScreen after exporting it as a default, since it has got no instance to the redux states, during the time component's execution happens.
Therefore you can move your code in the second snippet, to the SharePostScreen and use it there, to have an access to the props.
const mapStateToProps = state => {
return {
sharedPost: state.mainFeed.updatedPost
};
};
const mapDispatchToProps = dispatch => {
return {
doShare: bindActionCreators(doShare, dispatch)
};
};
export default connect(mapStateToProps, mapDispatchToProps)(SharePostScreen);
I put together a simple React-native application to gets data from a remote service, loads it in a FlatList. When a user taps on an item, it should be highlighted and selection should be retained. I am sure such a trivial operation should not be difficult. I am not sure what I am missing.
import React, { Component } from 'react';
import {
StyleSheet,
Text,
View,
FlatList,
ActivityIndicator,
Image,
TouchableOpacity,
} from 'react-native';
export default class BasicFlatList extends Component {
constructor(props) {
super(props);
this.state = {
loading: false,
data: [],
page: 1,
seed: 1,
error: null,
refreshing: false,
selectedItem:'null',
};
}
componentDidMount() {
this.makeRemoteRequest();
}
makeRemoteRequest = () => {
const {page, seed} = this.state;
const url = `https://randomuser.me/api/?seed=${seed}&page=${page}&results=20`;
this.setState({loading: true});
fetch(url)
.then(res => res.json())
.then(res => {
this.setState({
data: page === 1 ? res.results : [...this.state.data, ...res.results],
error: res.error || null,
loading: false,
refreshing: false
});
})
.catch(error => {
this.setState({error, loading: false});
});
};
onPressAction = (rowItem) => {
console.log('ListItem was selected');
console.dir(rowItem);
this.setState({
selectedItem: rowItem.id.value
});
}
renderRow = (item) => {
const isSelectedUser = this.state.selectedItem === item.id.value;
console.log(`Rendered item - ${item.id.value} for ${isSelectedUser}`);
const viewStyle = isSelectedUser ? styles.selectedButton : styles.normalButton;
return(
<TouchableOpacity style={viewStyle} onPress={() => this.onPressAction(item)} underlayColor='#dddddd'>
<View style={styles.listItemContainer}>
<View>
<Image source={{ uri: item.picture.large}} style={styles.photo} />
</View>
<View style={{flexDirection: 'column'}}>
<View style={{flexDirection: 'row', alignItems: 'flex-start',}}>
{isSelectedUser ?
<Text style={styles.selectedText}>{item.name.first} {item.name.last}</Text>
: <Text style={styles.text}>{item.name.first} {item.name.last}</Text>
}
</View>
<View style={{flexDirection: 'row', alignItems: 'flex-start',}}>
<Text style={styles.text}>{item.email}</Text>
</View>
</View>
</View>
</TouchableOpacity>
);
}
render() {
return(
<FlatList style={styles.container}
data={this.state.data}
renderItem={({ item }) => (
this.renderRow(item)
)}
/>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
marginTop: 50,
},
selectedButton: {
backgroundColor: 'lightgray',
},
normalButton: {
backgroundColor: 'white',
},
listItemContainer: {
flex: 1,
padding: 12,
flexDirection: 'row',
alignItems: 'flex-start',
},
text: {
marginLeft: 12,
fontSize: 16,
},
selectedText: {
marginLeft: 12,
fontSize: 20,
},
photo: {
height: 40,
width: 40,
borderRadius: 20,
},
});
When user taps on an item in the list, "onPress" method is invoked with the information on selected item. But the next step of highlight item in Flatlist does not happen. 'UnderlayColor' is of no help either.
Any help/advice will be much appreciated.
You can do something like:
For the renderItem, use something like a TouchableOpacity with an onPress event passing the index or id of the renderedItem;
Function to add the selected item to a state:
handleSelection = (id) => {
var selectedId = this.state.selectedId
if(selectedId === id)
this.setState({selectedItem: null})
else
this.setState({selectedItem: id})
}
handleSelectionMultiple = (id) => {
var selectedIds = [...this.state.selectedIds] // clone state
if(selectedIds.includes(id))
selectedIds = selectedIds.filter(_id => _id !== id)
else
selectedIds.push(id)
this.setState({selectedIds})
}
FlatList:
<FlatList
data={data}
extraData={
this.state.selectedId // for single item
this.state.selectedIds // for multiple items
}
renderItem={(item) =>
<TouchableOpacity
// for single item
onPress={() => this.handleSelection(item.id)}
style={item.id === this.state.selectedId ? styles.selected : null}
// for multiple items
onPress={() => this.handleSelectionMultiple(item.id)}
style={this.state.selectedIds.includes(item.id) ? styles.selected : null}
>
<Text>{item.name}</Text>
</TouchableOpacity>
}
/>
Make a style for the selected item and that's it!
In place of this.state.selectedItem and setting with/checking for a rowItem.id.value, I would recommend using a Map object with key:value pairs as shown in the RN FlatList docs example: https://facebook.github.io/react-native/docs/flatlist.html. Take a look at the js Map docs as well: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map.
The extraData prop recommended by #j.I-V will ensure re-rendering occurs when this.state.selected changes on selection.
Your onPressAction will obviously change a bit from example below depending on if you want to limit the number of selections at any given time or not allow user to toggle selection, etc.
Additionally, though not necessary by any means, I like to use another class or pure component for the renderItem component; ends up looking something like the following:
export default class BasicFlatList extends Component {
state = {
otherStateStuff: ...,
selected: (new Map(): Map<string, boolean>) //iterable object with string:boolean key:value pairs
}
onPressAction = (key: string) => {
this.setState((state) => {
//create new Map object, maintaining state immutability
const selected = new Map(state.selected);
//remove key if selected, add key if not selected
this.state.selected.has(key) ? selected.delete(key) : selected.set(key, !selected.get(key));
return {selected};
});
}
renderRow = (item) => {
return (
<RowItem
{...otherProps}
item={item}
onPressItem={this.onPressAction}
selected={!!this.state.selected.get(item.key)} />
);
}
render() {
return(
<FlatList style={styles.container}
data={this.state.data}
renderItem={({ item }) => (
this.renderRow(item)
)}
extraData={this.state}
/>
);
}
}
class RowItem extends Component {
render(){
//render styles and components conditionally using this.props.selected ? _ : _
return (
<TouchableOpacity onPress={this.props.onPressItem}>
...
</TouchableOpacity>
)
}
}
You should pass an extraData prop to your FlatList so that it will rerender your items based on your selection
Here :
<FlatList style={styles.container}
data={this.state.data}
extraData={this.state.selectedItem}
renderItem={({ item }) => (
this.renderRow(item)
)}
/>
Source : https://facebook.github.io/react-native/docs/flatlist
Make sure that everything your renderItem function depends on is passed as a prop (e.g. extraData) that is not === after updates, otherwise your UI may not update on changes
First
constructor() {
super();
this.state = {
selectedIds:[]
};
}
Second
handleSelectionMultiple = async (id) => {
var selectedIds = [...this.state.selectedIds] // clone state
if(selectedIds.includes(id))
selectedIds = selectedIds.filter(_id => _id !== id)
else
selectedIds.push(id)
await this.setState({selectedIds})
}
Third
<CheckBox
checked={this.state.selectedIds.includes(item.expense_detail_id) ? true : false}
onPress={()=>this.handleSelectionMultiple(item.expense_detail_id)}
/>
Finally i got the solution to my problem from the answer given by Maicon Gilton