skip to Main Content

My first idea was to try to delete items from a nested list. I’ve started using SectionList and a component that has an useState that manages the data changes of the SectionList. I’m trying to solve it using SectionList but it’d be great to display all possible alternatives (FlatList, ViewList, etc).

I figured out how to delete a whole section list (and his items) but not one item by one. I’ve read plenty of posts, but I did find nothing related to managing SectionList items. Maybe there’s an answer out there for FlatList nested items.

Here I left an example code ready to use (without styles) based on React Native official docs:

import React, { useEffect } from "react";
import { StyleSheet, Text, View, SafeAreaView, SectionList, StatusBar, Button } from "react-native";
import { useState } from "react";


const dataResource = [
  {
    title: "Main dishes",
    data: ["Pizza", "Burger", "Risotto"],
    n: "delete",
    k: 1
  },
  {
    title: "Sides",
    data: ["French Fries", "Onion Rings", "Fried Shrimps"],
    n: "delete",
    k: 2
  },
];



function App() {
  const [state, setState] = useState(dataResource)


  const Item = ({ dish, obj, i}) => (
    <View >

      <Text >{dish} </Text>
          <Button title={obj.n} onPress={() => {}} />     // add the handler
    </View>
  );

  const SectionComponent = ({ t, k }) => (
    <View>
      <Text >{t} </Text>
      <Button title={'section'} onPress={() => { setState(state.filter(e => e.k != k)) }} />
    </View>
  );

  return (
    <SafeAreaView >
      <SectionList
        sections={state}
        keyExtractor={(item, index) => item + index}
        renderItem={({ item, section, index}) => <Item dish={item} obj={section} i={index}/>}
        renderSectionHeader={({ section: { title, k } }) => <SectionComponent k={k} t={title} />}
      />
    </SafeAreaView>
  );
}



export default App;


2

Answers


  1. I think how you display the data is trivial. The component you use will just change how you access the data, not how you update it. What you need is helper functions for editing the data. With those in place you can do things like add/remove section items, and editing section items themselves:

    import React, { useState } from 'react';
    import {
      Text,
      View,
      StyleSheet,
      SafeAreaView,
      SectionList,
      Button,
      TextInput,
    } from 'react-native';
    
    const dataResource = [
      {
        title: 'Main dishes',
        data: ['Pizza', 'Burger', 'Risotto'],
        id: 1,
      },
      {
        title: 'Sides',
        data: ['French Fries', 'Onion Rings', 'Fried Shrimps'],
        id: 2,
      },
    ];
    export default function App() {
      const [state, setState] = useState(dataResource);
      const [sectionTitle,setSectionTitle] = useState('Drinks')
      
      const editItem = (itemId, newValue) => {
        let newState = [...state];
        let itemIndex = newState.findIndex((item) => item.id == itemId);
        if (itemIndex < 0) return;
        newState[itemIndex] = {
          ...newState[itemIndex],
          ...newValue,
        };
        setState(newState);
      };
      const addSectionItem = (title)=>{
        let newState = [...state]
        newState.push({
          title,
          data:[],
          id:newState.length+1
        })
        setState(newState)
      }
    
      const removeFood = (itemId, food) => {
        let currentItem = state.find((item) => item.id == itemId);
        console.log(currentItem);
        currentItem.data = currentItem.data.filter((item) => item != food);
        console.log(currentItem.data);
        editItem(itemId, currentItem);
      };
      const addFood = (itemId, food) => {
        let currentItem = state.find((item) => item.id == itemId);
        console.log(currentItem.data);
        currentItem.data.push(food);
        console.log(currentItem.data);
        editItem(itemId, currentItem);
      };
    
      const Item = ({ item, section, index }) => {
        return (
          <View style={styles.row}>
            <Text>{item} </Text>
            <Button
              title={'Delete'}
              onPress={() => {
                removeFood(section.id, item);
              }}
            />
          </View>
        );
      };
    
      const SectionHeader = ({ title, id }) => {
        return (
          <View style={styles.header}>
            <Text style={{ fontSize: 18 }}>{title} </Text>
            <Button
              title={'X'}
              onPress={() => {
                setState(state.filter((e) => e.id != id));
              }}
            />
          </View>
        );
      };
      const SectionFooter = ({ id }) => {
        const [text, setText] = useState('');
        return (
          <View style={[styles.row, styles.inputWrapper]}>
            <Text>Add Entry</Text>
            <TextInput
              value={text}
              onChangeText={setText}
              style={{ borderBottomWidth: 1 }}
            />
            <Button title="Add" onPress={() => addFood(id, text)} />
          </View>
        );
      };
      return (
        <SafeAreaView>
          <Button title="Reset list" onPress={() => setState(dataResource)} />
          <View style={[styles.row, styles.inputWrapper]}>
            <Text>Add New Section</Text>
            <TextInput
              value={sectionTitle}
              onChangeText={setSectionTitle}
              style={{ borderBottomWidth: 1 }}
            />
            <Button title="Add" onPress={() => addSectionItem(sectionTitle)} />
          </View>
          <View style={styles.sectionListWrapper}>
            <SectionList
              sections={state}
              keyExtractor={(item, index) => item + index}
              renderItem={Item}
              renderSectionHeader={({ section }) => <SectionHeader {...section} />}
              renderSectionFooter={({ section }) => <SectionFooter {...section} />}
            />
          </View>
        </SafeAreaView>
      );
    }
    
    const styles = StyleSheet.create({
      row: {
        flexDirection: 'row',
        width: '100%',
        justifyContent: 'space-between',
        padding: 5,
        alignItems: 'center',
      },
      header: {
        flexDirection: 'row',
        paddingVertical: 5,
        width: '100%',
        borderBottomWidth: 1,
      },
      inputWrapper: {
        paddingVertical: 15,
        marginBottom: 10,
      },
      sectionListWrapper: {
        padding: 5,
      },
    });
    

    Here’s a demo

    Login or Signup to reply.
  2. I’ve improved my answer to give a clearer example of how to manipulate SectionList. An example of how to add/delete/modify sections and its data is provided.

    import React from 'react';
    
    export default function useStateHelpers(state, setState) {
      const editSection = (sectionId, newValue) => {
        // parse and stringify is used to clone properly
        let newState = JSON.parse(JSON.stringify(state));
        let itemIndex = newState.findIndex((item) => item.id == sectionId);
        if (itemIndex < 0) return;
        const section = {
          ...newState[itemIndex],
          ...newValue,
        };
        newState[itemIndex] = section;
        setState(newState);
      };
      const editSectionDataItem = (sectionId, itemId, newValue) => {
        const newState = JSON.parse(JSON.stringify(state));
        const sectionIndex = newState.findIndex(
          (section) => section.id == sectionId
        );
        const section = newState[sectionIndex];
        const itemIndex = section.data.findIndex((item) => item.id == itemId);
        let item = section.data[itemIndex];
        section.data[itemIndex] = {
          ...item,
          ...newValue,
        };
    
        editSection(sectionId, section);
      };
      const editSectionTitle = (sectionId, title) =>
        editSection(sectionId, { title });
    
      const setSelectSectionItem = (sectionId, itemId, isSelected) => {
        editSectionDataItem(sectionId, itemId, { isSelected });
      };
    
      const removeSectionDataItem = (sectionId, food) => {
        let newState = JSON.parse(JSON.stringify(state));
        let sectionIndex = newState.findIndex((section) => section.id == sectionId);
        let section = newState[sectionIndex];
        section.data = section.data.filter((item) => item.title != food);
        editSection(sectionId, section);
      };
    
      const addSectionDataItem = (sectionId, title, price) => {
        let newState = JSON.parse(JSON.stringify(state));
        let sectionIndex = newState.findIndex((section) => section.id == sectionId);
        let section = newState[sectionIndex];
        section.data.push({
          title,
          price,
          id: `section-${sectionId}-${section.data.length + 1}`,
        });
        editSection(sectionId, section);
      };
    
      const addSection = (title, data = [], id) => {
        let newState = JSON.parse(JSON.stringify(state));
        newState.push({
          title,
          data,
          id: newState.length + 1,
        });
        setState(newState);
      };
      
      const helpers = {
        editSection,
        editSectionTitle,
        removeSectionDataItem,
        addSectionDataItem,
        addSection,
        setSelectSectionItem,
        editSectionDataItem,
      };
      return helpers;
    }
    
    
    import React, { useState } from 'react';
    import {
      StyleSheet,
      SafeAreaView,
      View,
      Button,
      SectionList,
    } from 'react-native';
    
    import data from './data';
    import StateContext from './StateContext';
    import useStateHelpers from './hooks/useStateHelpers';
    
    import Header from './components/SectionHeader';
    import Footer from './components/SectionFooter';
    import Item from './components/SectionItem';
    import TextInput from './components/Input';
    
    export default function App() {
      const [state, setState] = useState(data);
      const [sectionTitle, setSectionTitle] = useState('');
      const helpers = useStateHelpers(state, setState);
    
      return (
        <SafeAreaView style={{ flex: 1 }}>
          <StateContext.Provider value={{ state, setState, ...helpers }}>
            <View style={styles.container}>
              <Button title="Reset list" onPress={()=>setState(data)} />
              <TextInput
                label={'Add New Section'}
                value={sectionTitle}
                onChangeText={setSectionTitle}
                onRightIconPress={() => {
                  helpers.addSection(sectionTitle);
                  setSectionTitle('');
                }}
              />
              <View style={styles.sectionListWrapper}>
                <SectionList
                  style={{ flex: 1 }}
                  sections={state}
                  renderItem={(props) => <Item {...props} />}
                  renderSectionHeader={(props) => <Header {...props} />}
                  renderSectionFooter={(props) => <Footer {...props} />}
                />
              </View>
            </View>
          </StateContext.Provider>
        </SafeAreaView>
      );
    }
    
    const styles = StyleSheet.create({
      container: {
        flex: 1,
        padding: 8,
      },
      sectionListWrapper: {
        padding: 5,
        flex: 1,
      },
    });
    
    import React, { useContext } from 'react';
    import { View, TouchableOpacity, StyleSheet, Text, Button } from 'react-native';
    import StateContext from '../StateContext';
    
    export default function Item({ item, section, index }) {
      const { 
        removeSectionDataItem,
        setSelectSectionItem
        } = useContext(StateContext);
      const hasPrice = item.price.toString().trim().length  > 0;
      return (
        <TouchableOpacity
          style={[styles.container, item.isSelected && styles.selectedContainer]}
          onPress={() =>
            setSelectSectionItem(section.id, item.id, !item.isSelected)
          }
        >
          <View style={styles.row}>
            <Text>
              {item.title + (hasPrice ? ' | ' : '')}
              {hasPrice && <Text style={{ fontWeight: '300' }}>{item.price}</Text>}
            </Text>
            <Button
              title={'Delete'}
              onPress={() => {
                removeSectionDataItem(section.id, item.title);
              }}
            />
          </View>
        </TouchableOpacity>
      );
    }
    
    const styles = StyleSheet.create({
      container: {
        marginVertical: 2,
      },
      selectedContainer: {
        backgroundColor: 'rgba(0,0,0,0.2)',
      },
      row: {
        flexDirection: 'row',
        width: '100%',
        justifyContent: 'space-between',
        padding: 5,
        alignItems: 'center',
      },
    });
    
    
    /*SectionFooter.js*/
    import React, { useState, useContext, useEffect } from 'react';
    import { View, StyleSheet, Text } from 'react-native';
    import TextInput from './Input';
    import StateContext from '../StateContext';
    
    export default function SectionFooter({ section }) {
      const [title, setTitle] = useState('');
      const [price, setPrice] = useState('');
      const [titleWasEdited, setTitleWasEdited] = useState(false);
      const { addSectionDataItem, editSectionDataItem } = useContext(StateContext);
      const selectedItem = section.data.find((item) => item.isSelected);
      // listen for item selection
      useEffect(() => {
        // pass item values to text input
        setTitle(selectedItem?.title || '');
        setPrice(selectedItem?.price || '');
        // reset title/price input
        setTitleWasEdited(false);
      }, [selectedItem]);
      return (
        <View style={styles.container}>
          <Text>
            {selectedItem 
              ? 'Editing ' + selectedItem.title 
              : 'Add New Entry'
            }
          </Text>
          {/*one input handles both title and price*/}
          <TextInput
            label={titleWasEdited ? 'Price' : 'Title'}
            value={titleWasEdited ? price : title}
            onChangeText={titleWasEdited ? setPrice : setTitle}
            rightIconName={titleWasEdited ? 'plus' : 'arrow-right'}
            // rightIcon is disabled when theres no text
            // but price is allowed to be empty
            allowEmptySubmissions={titleWasEdited}
            style={{ width: '80%' }}
            onRightIconPress={() => {
              // after title is edited allow submission
              if (titleWasEdited) {
                // if item was edited update it
                if (selectedItem) {
                  editSectionDataItem(section.id, selectedItem.id, {
                    title,
                    price,
                    isSelected: false,
                  });
                }
                // or add new item
                else {
                  addSectionDataItem(section.id, title, price);
                }
                setTitle('');
                setPrice('');
              }
              // toggle between title & price
              setTitleWasEdited((prev) => !prev);
            }}
          />
        </View>
      );
    }
    const styles = StyleSheet.create({
      container: {
        marginVertical: 20,
        alignItems: 'center',
      },
    });
    
    import React, { useContext } from 'react';
    import { View, StyleSheet } from 'react-native';
    import { TextInput } from 'react-native-paper';
    import StateContext from '../StateContext';
    
    export default function SectionHeader({ section: { id, title } }) {
      const { editTitle, setState, state } = useContext(StateContext);
      return (
        <View style={{ borderBottomWidth: 1 }}>
          <View style={styles.header}>
            <TextInput
              style={styles.titleInput}
              value={title}
              onChangeText={(text) => {
                editTitle(id, text);
              }}
              right={
                <TextInput.Icon
                  name="close"
                  onPress={() => {
                    setState(state.filter((sec) => sec.id != id));
                  }}
                />
              }
              underlineColor="transparent"
              activeUnderlineColor="transparent"
              outlineColor="transparent"
              activeOutlineColor="transparent"
              selectionColor="transparent"
              dense
            />
          </View>
        </View>
      );
    }
    const styles = StyleSheet.create({
      header: {
        flexDirection: 'row',
        width: '100%',
        justifyContent: 'space-around',
        alignItems: 'center',
      },
      titleInput: {
        fontSize: 18,
        backgroundColor: 'transparent',
        borderWidth: 0,
        borderColor: 'transparent',
        height: 36,
      },
    });
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search