manman
manman

Reputation: 5103

React Native update FlatList data in pure component

I'm kinda new to React Native and have been having this issue with FlatList that I want to update the data source it uses and get it to update itself. My case is having a list of items that I want to show with FlatList and then each item is selectable. Most of the examples I've seen are using component and suggestion has been adding state to extraData to solve this issue. However, I'm using pure component and trying to see if there's any solution in this case. Following a sample project I've created. In this example, the press happens, data is updated, but FlatList doesn't reflect the data changes.

import { findIndex } from 'lodash'
import React, { useState } from 'react'
import {
  SafeAreaView,
  StyleSheet,
  View,
  Text,
  StatusBar,
  FlatList,
  Pressable,
} from 'react-native'

import {
  Colors,
} from 'react-native/Libraries/NewAppScreen'

var array = require('lodash/array')

const App = () => {
  const [data, setData] = useState([
    {
      name: "Foo",
      id: '1',
      isSelected: false
    },
    {
      name: "Boo",
      id: '2',
      isSelected: false
    },
    {
      name: "Koo",
      id: '3',
      isSelected: false
    },
    {
      name: "Poo",
      id: '4',
      isSelected: false
    },
    {
      name: "Too",
      id: '5',
      isSelected: false
    },
    {
      name: "Qoo",
      id: '6',
      isSelected: false
    },
  ])

  const tappedItem = (item) => {
    const itemIndex = data.findIndex((i) => i.id == item.id)
    const newItem = data[itemIndex]
    newItem.isSelected = !newItem.isSelected
    data[itemIndex] = newItem
    setData(data)
  }

  const renderItem = (item) => {
    return (
      <Pressable onPress={() => tappedItem(item.item)}>
        <View style={[styles.sectionContainer, { backgroundColor: item.item.isSelected ? 'pink' : 'lightgray' }]}>
          <Text style={styles.sectionTitle}>{item.item.name}</Text>
        </View>
      </Pressable>
    )
  }

  return (
    <>
      <StatusBar barStyle="dark-content" />
      <SafeAreaView>
        <View style={styles.body}>
          <FlatList
            data={data}
            keyExtractor={item => item.id}
            renderItem={renderItem}
            extraData={data}
          />
        </View>
      </SafeAreaView>
    </>
  )
}

const styles = StyleSheet.create({
  body: {
    backgroundColor: Colors.white,
  },
  sectionContainer: {
    marginTop: 32,
    paddingHorizontal: 24,
  },
  sectionTitle: {
    fontSize: 24,
    fontWeight: '600',
    color: Colors.black,
  },
})

export default App

Upvotes: 1

Views: 1179

Answers (1)

Ketan Ramteke
Ketan Ramteke

Reputation: 10645

Final Output:

enter image description here

Here is how you can implement the selection and deselection of FlatList items:

import React, { useState } from 'react';
import {
  SafeAreaView,
  StyleSheet,
  View,
  Text,
  StatusBar,
  FlatList,
  Pressable,
} from 'react-native';


const App = () => {
  const [data, setData] = useState([
    {
      name: 'Foo',
      id: '1',
      isSelected: false,
    },
    {
      name: 'Boo',
      id: '2',
      isSelected: false,
    },
    {
      name: 'Koo',
      id: '3',
      isSelected: false,
    },
    {
      name: 'Poo',
      id: '4',
      isSelected: false,
    },
    {
      name: 'Too',
      id: '5',
      isSelected: false,
    },
    {
      name: 'Qoo',
      id: '6',
      isSelected: false,
    },
  ]);

  const tappedItem = (item) => {
    console.log(item);
    const modifiedList= data.map((element) => {
      if (element.id === item.id) {
        return { ...element, isSelected: !element.isSelected };
      }
      return element;
    });
    setData(modifiedList);
  };

  const renderItem = (item) => {
    return (
      <Pressable onPress={() => tappedItem(item.item)}>
        <View
          style={[
            styles.sectionContainer,
            { backgroundColor: item.item.isSelected ? 'pink' : 'lightgray' },
          ]}>
          <Text style={styles.sectionTitle}>{item.item.name}</Text>
        </View>
      </Pressable>
    );
  };

  return (
    <>
      <StatusBar barStyle="dark-content" />
      <SafeAreaView>
        <View style={styles.body}>
          <FlatList
            data={data}
            keyExtractor={(item) => item.id}
            renderItem={renderItem}
            extraData={data}
          />
        </View>
      </SafeAreaView>
    </>
  );
};

const styles = StyleSheet.create({
  body: {
    backgroundColor: 'white',
  },
  sectionContainer: {
    marginTop: 32,
    paddingHorizontal: 24,
  },
  sectionTitle: {
    fontSize: 24,
    fontWeight: '600',
    color: 'black',
  },
});

export default App;

Working of tappedItem():

/* 
Lets assume we pressed first item in the FlatList, the item that we will be passing 
is: {
      name: 'Foo',
      id: '1',
      isSelected: false,
    }

After that is done most important and also probably the easiest part, 
the modification of the existing state, i.e. toggling the selected property. 

*/
const tappedItem = (item) => {
    console.log(item);
/*
In the following map operation, we iterate through the existing state, i.e. data.
and when we find the matching id, 1 in the case of 
=> {
      name: 'Foo',
      id: '1',
      isSelected: false,
    }

we return the mutated object where we change only the `isSelected` property

You can see this happening here: 
=> if (element.id === item.id) {
        return { ...element, isSelected: !element.isSelected };
   }
in above if statement, we spread the `element` object, toggle the `isSelected` property and return it. 

If the id does not matches then we can simply return the original `element` object without any changes. 

So after that, we will get the modifiedList which will have the `isSelected` toggled as we intend to. 

Then we simply set the data state with that of the newly modified list. 

=> setData(modifiedList);

*/
    const modifiedList= data.map((element) => {
      if (element.id === item.id) {
        return { ...element, isSelected: !element.isSelected };
      }
      return element;
    });
    setData(modifiedList);
  };

/*
this variation of your existing tappedItem function will work fine too, 

just be careful not to mutate the states directly as you did in the original case

=>  data[itemIndex] = newItem [❌]
=> let temp = [...data] 
   temp[itemIndex] = newItem; [✔]
*/


 const tappedItem = (item) => {
    let temp = [...data] //<= create a copy of current state
    const itemIndex = data.findIndex((i) => i.id == item.id);
    const newItem = data[itemIndex];
    newItem.isSelected = !newItem.isSelected;
    temp[itemIndex] = newItem; //<= modify that copy
    setData(temp); //<= use it to set the state.
  };

Full Working demo: Expo Snack

Upvotes: 2

Related Questions