Создание уникального значения флажка в многоразовом компоненте. Реагировать нативный

ОТРЕДАКТИРОВАНО. Я изменил структуру потока, чтобы сделать его менее многословным. Надеюсь, это более удобоваримо

У меня есть набор экранов (желтый, оранжевый, зеленый и т. д.), и на каждом из них есть 5 карточек, на которых будет отображаться массив миниатюр видео, заголовков и т. д. Каждая карточка может содержать любое количество миниатюр. Пользователь добавляет видео/миниатюры в эти карточки (коллекции) с помощью модального окна, содержащего данные, и каждый элемент имеет флажок. После установки флажков и закрытия модала они добавляются на карточку.

Это работает. За исключением того, что когда я открываю модальное окно с другой карты, флажки из предыдущего выбора уже выбраны. Мне нужно, чтобы каждое модальное окно открывалось с флажками, выбранными на карточке, которая его открыла.

ПРИМЕЧАНИЕ. Компонент флажка взят из React Native Paper — см. здесь. У него нет свойства value. Он использует status, который я уже реализую.

Кроме того, каждая карточка находится в отдельных файлах, чтобы обеспечить разделение задач и избежать случайного смешивания значений свойств. Тем не менее, флажок настаивает на запоминании последнего выбора.

Вот логический код желтого экрана

// This function gets triggered by the checkbox selection and 
// populates the state `selectedwaza` 
// it also handles the checked and unchecked status
const selectItem = (item, index) => {
  const newData = [...dropdowndata];
  const newItem = newData[index];

  newItem.checked = !item.checked;
  setChecked(!checked)
  setDropdownData(newData);    

  if (newItem.checked) {
    if (!selectedwaza.includes(newItem)) {
      setSelectedWaza(prev => [...prev, newItem]); 
    }
  } else {
    setSelectedWaza(prev => prev.filter(i => i.title !== newItem.title));
  }
}

И ниже, на мой взгляд:

// I mad the items to pick from and assign a checkbox to each item
{dropdown_collection.map((item, index) => {
    return (
      <View key = {index}>
        <Text style = {{color: activeColors.textcolor}} >
            {item.title}
        </Text> 
        <Checkbox.Item
            mode='android'
            color = {activeColors.primary}
            status = {item.checked ? 'checked' : 'unchecked'}
            onPress = {() => selectItem(item, index)}
        />
      </View>
    )
  }
})}

РЕДАКТИРОВАТЬ 2. Я вижу, что хотя флажки остаются отмеченными, их значение не переносится. Я все еще могу открыть следующий набор карточек и выбрать то, что хочу, и он добавит только те, которые были выбраны недавно, и проигнорирует то, что уже запомнилось из предыдущего использования. Однако это непрактично, потому что, если я хочу добавить тот, который уже показан выбранным, мне придется сначала отменить его выбор и выбрать его еще раз. Я считаю, что простой подход «снять флажки при закрытии модального окна» будет хорошим хаком. Но это тоже не работает. Я публикую рассматриваемые функции на случай, если кто-то еще посмотрит на это и захочет мне помочь.

const selectItem = (item, index) => {

  const newData = [...dropdownCollection];
  const newItem = newData[index];

  newItem.checked = !item.checked;
  setChecked(!checked)  

  if (newItem.checked) {
    if (!selectedwaza.includes(newItem)) {
      setSelectedWaza(prev => [...prev, newItem]); 
    }
  } else {
    setSelectedWaza(prev => prev.filter(i => i.title !== newItem.title));
  }
}

// Function to clear all checkboxes
const handleCloseModal = () => {
  setModalVisible(false);
  const clearedData = dropdown_collection.map(item => ({
    ...item,
    checked: false
  }));
  // Update your state with the cleared data
  setDropdownCollection(clearedData);
};

Для тех из вас, кому нужно больше кода, см. всю страницу ниже. Другие, кому нужен только соответствующий код, игнорируют ниже

import React, {useContext, useState, useRef, useEffect} from 'react';
import { StyleSheet, View, Text, TouchableOpacity, Image, Animated, Pressable, Modal, ActivityIndicator  } from 'react-native'
import { colors } from '../../../../assets/theme/config';
import { ThemeContext } from '../../../../context/ThemeContext';
import Icon from 'react-native-vector-icons/MaterialCommunityIcons';
import { useNavigation } from '@react-navigation/native';
import { Checkbox } from 'react-native-paper';
import SearchField from '../../../../components/SearchField';
import ashiwaza from '../../../../assets/data/nagewaza/ashiwaza';
import koshiwaza from '../../../../assets/data/nagewaza/koshiwaza';
import masutemiwaza from '../../../../assets/data/nagewaza/masutemiwaza';
import yokosutemiwaza from '../../../../assets/data/nagewaza/yokosutemiwaza';
import tewaza from '../../../../assets/data/nagewaza/tewaza';

const dropdown_collection = [...tewaza, ...ashiwaza, ...koshiwaza, ...masutemiwaza, ...yokosutemiwaza]

import {db, auth} from '../../../../firebase';
import {onAuthStateChanged} from 'firebase/auth';
import { doc, getDoc, updateDoc, setDoc } from 'firebase/firestore';

const ITEM_SIZE = 180;
const ITEM_HEIGHT = 145;
const QTY = 8

const GokyoNowaza = () => {
  const [index, setIndex] = useState(0);
  const [waza, setWaza] = useState([])
  const [checked, setChecked] = useState(false)
  const [selectedwaza, setSelectedWaza] = useState([])
  const [dropdownCollection, setDropdownCollection] = useState(dropdown_collection);
  const [modalVisible, setModalVisible] = useState(false);
  const [value, setValue] = useState("");

  const isLoading = false
  const ref = useRef(null);
  const {theme} = useContext(ThemeContext)
  let activeColors = colors[theme.mode]
  const navigation = useNavigation();

  // build the collection from selecting the checkboxes
  const selectItem = (item, index) => {

    const newData = [...dropdownCollection];
    const newItem = newData[index];

    newItem.checked = !item.checked;
    setChecked(!checked)  

    if (newItem.checked) {
      if (!selectedwaza.includes(newItem)) {
        setSelectedWaza(prev => [...prev, newItem]); 
      }
    } else {
      setSelectedWaza(prev => prev.filter(i => i.title !== newItem.title));
    }
  }

  // Function to clear all checkboxes
  const handleCloseModal = () => {
    setModalVisible(false);
    const clearedData = dropdown_collection.map(item => ({
      ...item,
      checked: false
    }));
    // Update your state with the cleared data
    setDropdownCollection(clearedData);
  };

  //set collection to data
  const handleTestCollection = async () => {
    const userId = auth.currentUser.uid;
    const docRef = doc(db, 'useraccounts', userId);
  
    try {
      const docSnap = await getDoc(docRef);
  
      // Prepare the data to be set or updated
      const gokyuData = docSnap.exists() && docSnap.data().gokyu 
        ? docSnap.data().gokyu 
        : {};

      gokyuData.gokyoNoWaza = Array.isArray(selectedwaza) 
        ? [...selectedwaza] 
        : gokyuData.gokyoNoWaza || [];

      // Set or update the document
      await setDoc(docRef, { gokyu: gokyuData }, { merge: true });
      console.info("Document successfully updated!");
    } catch (error) {
      console.error("Error updating document: ", error);
    }
  }

  
  //display the colleciton on load
  useEffect(() => {
    const getUser = () => {        
      onAuthStateChanged(auth, (user) => {
        if (user) {      
          const userId = auth.currentUser.uid;
          user !== null || user !== undefined
          const docRef = doc(db, 'useraccounts', userId);        

          getDoc(docRef).then((docSnap) => {
            const data = docSnap.data().gokyu?.gokyoNoWaza || [];
            if (docSnap.exists()){
              setSelectedWaza(data);        
            } else {
              console.info("nothing here"); 
            }      
          });
        } 
      }); 
    }

    getUser();
  }, []);


  return (
    <View style = {[{backgroundColor: activeColors.bgalt}, styles.card]}>
      <View style = {styles.sectionLabelAndButton}>
        <View>
          <View>
            <Text style = {[{color: activeColors.textcolor, fontSize:18, fontWeight:'bold'}]}>
              Gokyo No Waza  
            </Text>
          </View>
            
          <View>
            <Text style = {[{color: activeColors.textcolor, fontSize:18, fontWeight:'normal'}]}>
              Select {QTY} throws 
            </Text>            
          </View> 
        </View>

        <View style = {styles.buttons} >
          <TouchableOpacity 
            onPress = {() => handleTestCollection()}  
            style = {[{backgroundColor:activeColors.primary}, styles.addiconbutton]}>
            <Icon name = "content-save" size = {24} color = {activeColors.white} />
          </TouchableOpacity>
          <TouchableOpacity 
            onPress = {() => setModalVisible(true)}  
            style = {[{backgroundColor:activeColors.primary}, styles.addiconbutton]}>
            <Icon name = "plus" size = {24} color = {activeColors.white} />
          </TouchableOpacity>
        </View>        
      </View>  

      <Animated.FlatList
        ref = {ref}
        data = {selectedwaza}
        horizontal
        initialNumToRender = {10}
        initialScrollIndex = {index}
        keyExtractor = {(item, index) => index.toString()}
        showsHorizontalScrollIndicator = {false}
        snapToInterval = {ITEM_SIZE}
        snapToOffsets = {[...Array(waza.length).keys()].map(i => i * ITEM_SIZE)}
        decelerationRate = {0}
        bounces = {false}
        scrollEventThrottle = {16}
        onEndReachedThreshold = {0.5}
        onEndReached = {() => {
          setWaza([...waza]);
        }}
        contentContainerStyle = {{
          alignItems: 'flex-start',
        }}
        renderItem = {
          ({item, index: fIndex}) => {
            return (
              <View key = {fIndex} style = {{width:ITEM_SIZE * .6, marginVertical:20, marginRight: 20}}> 
                <View style = {[{color: activeColors.textcolor, backgroundColor: activeColors.bgalt,}, styles.counter]}>
                  <Text style = {{color: activeColors.textcolor}}>{fIndex + 1}</Text> 
                </View>    
                <View style = {[styles.overlay]}>
                  <TouchableOpacity 
                    onPress = {() => navigation.navigate('TechniqueDetails', {data: item})}  
                    style = {[{backgroundColor:activeColors.primary}, styles.playbutton]}>
                    <Icon name = "play" size = {24} color = {activeColors.white} />
                  </TouchableOpacity>                  
                </View>
                <Animated.View>         
                  <Image source = {{ uri: `https://judopedia.wiki/assets/images/${item.thumbnail}` }} 
                  style = {styles.thumbnail}/>
                  <Text 
                    numberOfLines = {2}
                    style = {{
                      width: ITEM_SIZE * .6,
                      color:activeColors.textcolor, 
                      fontWeight: 'bold'
                    }}>  
                    {item.title} 
                  </Text>   
                </Animated.View>   
              </View>   
            )
          }
        }
      />
      <Modal animationType = "slide" visible = {modalVisible} transparent = {true}>
        <View 
          style = {[{backgroundColor: activeColors.overlay}, styles.container]}>
          {isLoading && <ActivityIndicator size = {70} color = {colors.tertiary} />}

          {!isLoading && (
            <View style = {[styles.modalView, { backgroundColor: activeColors.bgalt }]}>
              <View style = {{width: "100%", alignItems: "center", justifyContent:"space-between", flexDirection:"row"}}>
                <Text style = {[{color:activeColors.primary }, styles.modalheading]}>
                  Techniques
                </Text>
                <Pressable onPress = {handleCloseModal}>
                  <Icon name = "close" size = {24} color = {activeColors.textcolor} />
                </Pressable>
              </View>
              
              <View style = {styles.decisionRow}>
                <SearchField value = {value} onChangeText = {setValue} style = {{backgroundColor: activeColors.bg}} />
                <View style = {[{backgroundColor: activeColors.bgalt}, styles.searchcontainer]}>
                  {dropdown_collection.map((item, index) => {
                    if (value === "" ) {
                      return null
                    } else if (
                        item.title.toLowerCase().includes(value.toLowerCase()) || item.english.toLowerCase().includes(value.toLowerCase())
                      ) 
                    {
                      return (
                        <View key = {index} style = {styles.searchitems}>
                          <View style = {styles.metadata}>
                            <Image source = {{ uri: `https://judopedia.wiki/assets/images/${item.thumbnail}` }} style = {styles.searchresultsthumbnail}/>
                            <View>
                              <Text style = {{color: activeColors.textcolor}} >{item.title}</Text>
                              <Text style = {{color: activeColors.textcolor, fontSize:12}} >{item.english}</Text>
                            </View>  
                          </View>
                          <View style = {styles.buttongroup}>
                            <Checkbox.Item
                              mode='android'
                              color = {activeColors.primary}
                              status = {item.checked ? 'checked' : 'unchecked'}
                              onPress = {() => selectItem(item, index)}
                            />
                          </View>   
                        </View>
                      )
                    }
                  })}
                </View>
              </View>
            </View>
          )}
        </View>
      </Modal>
    </View>
  )
}

export default GokyoNowaza

В ваш образец включено много кода, который не имеет отношения к описанной проблеме. Можете ли вы отредактировать его, чтобы он стал минимально воспроизводимым примером с только абсолютно необходимым кодом для воспроизведения проблемы?

jsejcksn 12.04.2024 20:14

@jsejcksn воспроизводимо? Нет, если у вас не запущена выставка в эмуляторе и все необходимые зависимости. Я выбрал другой маршрут, который нельзя использовать повторно, но он менее многословен. я обновлю

LOTUSMS 12.04.2024 22:15

@jsejcksn Думаю, теперь стало намного чище, если хочешь взглянуть. Дайте мне знать, если вам нужно, чтобы я что-нибудь добавил к этому.

LOTUSMS 13.04.2024 15:01

Кроме того, распространение создает неглубокую копию, так являются ли элементы массива объектами? Вы перебираете обновленный список или статический список, созданный вне модального компонента?

morganney 13.04.2024 16:49

@morganney Раньше у меня там был весь экранный код, за вычетом таких тривиальных вещей, как другие функции и стили, и были жалобы, что кода слишком много. Я обновлю «весь» код файла ниже в качестве дополнительной ссылки.

LOTUSMS 13.04.2024 16:52

Я обновил все это. Но возможно, вы правы. SetDropdownData создает коллекцию из группы собранных флажков. Это вполне может быть частью проблемы

LOTUSMS 13.04.2024 16:56

Я был бы не против предоставить вам временный доступ к github в рамках совместной работы. Но это зависит от тебя

LOTUSMS 13.04.2024 16:57

Суть вашей проблемы выглядит следующим образом: const foo = { name: 'foo' }; const bar = { name: 'bar' }; const a = [foo, bar]; const b = [...a]; b[0].checked = true; Теперь, если вы это сделаете a[0].checked, это тоже будет true.

morganney 13.04.2024 17:04

Вы можете попробовать провести рефакторинг своего кода или заменить развороты на StructuredClone. Я бы предложил первое.

morganney 13.04.2024 17:09

Я не хочу навязываться. Я понимаю. Ваше объяснение объясняет, почему они оба верны. Есть ли у вас ответ, или показанного кода недостаточно, чтобы найти решение?

LOTUSMS 13.04.2024 17:09

В selectItem создайте глубокий клон данных раскрывающегося списка, а затем переберите этот список вместо статического, созданного вне компонента.

morganney 13.04.2024 17:34

Если ваш список большой, рассмотрите более простую/плоскую структуру данных, которая ссылается только на уникальные идентификаторы для ваших элементов раскрывающегося списка.

morganney 13.04.2024 17:41

Возможно, я не понимаю методологии или того, как это правильно сделать, но это не сработало. Кстати, вместо этого мне пришлось использовать lodash, поскольку StructuredClone предназначен для веб-реализации. Это нативный код

LOTUSMS 14.04.2024 17:10
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Улучшение производительности загрузки с помощью Google Tag Manager и атрибута Defer
Улучшение производительности загрузки с помощью Google Tag Manager и атрибута Defer
В настоящее время производительность загрузки веб-сайта имеет решающее значение не только для удобства пользователей, но и для ранжирования в...
Безумие обратных вызовов в javascript [JS]
Безумие обратных вызовов в javascript [JS]
Здравствуйте! Юный падаван 🚀. Присоединяйся ко мне, чтобы разобраться в одной из самых запутанных концепций, когда вы начинаете изучать мир...
Система управления парковками с использованием HTML, CSS и JavaScript
Система управления парковками с использованием HTML, CSS и JavaScript
Веб-сайт по управлению парковками был создан с использованием HTML, CSS и JavaScript. Это простой сайт, ничего вычурного. Основная цель -...
JavaScript Вопросы с множественным выбором и ответы
JavaScript Вопросы с множественным выбором и ответы
Если вы ищете платформу, которая предоставляет вам бесплатный тест JavaScript MCQ (Multiple Choice Questions With Answers) для оценки ваших знаний,...
0
13
189
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

В предоставленном вами коде именно в этой части:

dropdown_collection.map((item, index) => {
  if (value === '') {
    return null;
  } else if (
    item.title.toLowerCase().includes(value.toLowerCase()) ||
    item.english.toLowerCase().includes(value.toLowerCase())
  ) {
    return (
      <View key = {index} style = {styles.searchitems}>
        <View style = {styles.metadata}>
          <Image
            source = {{ uri: `https://judopedia.wiki/assets/images/${item.thumbnail}` }}
            style = {styles.searchresultsthumbnail}
          />
          <View>
            <Text style = {{ color: activeColors.textcolor }}>{item.title}</Text>
            <Text style = {{ color: activeColors.textcolor, fontSize: 12 }}>{item.english}</Text>
          </View>
        </View>
        <View style = {styles.buttongroup}>
          <Checkbox.Item
            mode='android'
            color = {activeColors.primary}
            status = {item.checked ? 'checked' : 'unchecked'}
            onPress = {() => selectItem(item, index)}
          />
        </View>
      </View>
    );
  }
});

Вам следует составить карту штата, а не dropdown_collection. Функция handleCloseModal очищает данные состояния dropdownCollection, но вы возвращаете dropdown_collection.

Спасибо. Я бы хотел, чтобы он сохранял те, которые выбраны в соответствии с каждой карточкой, на которую я нажимаю, а не с чистого листа, но это работает лучше, чем не работает вообще. Спасибо

LOTUSMS 16.04.2024 21:08

@LOTUSMS Тогда вам нужен другой подход. Вам нужно обрабатывать состояние вне компонента и передавать его ему. Итак, вы по сути хотите сохранить состояние всех карт. Я думаю, так намного проще. Я думаю, что лучше всего будет просто создать контекст, управляющий состояниями этих карт.

Ceco Svidovski 20.04.2024 12:18

Другие вопросы по теме

Похожие вопросы

Как установить состояние сложного объекта в React?
Как разместить элемент управления «Лифлет» (временная шкала) внизу под элементом управления атрибуцией (например, MerrySky)
Пользовательский раскрывающийся список с (переключателями и флажками) в реакции. возможный?
Ошибка: прочтите ECONNRESET в TCP.onStreamRead (узел: внутренний/stream_base_commons: 217:20)
Понимание связи между EventEmitter в Node.js и addEventListener в браузере
Docxtemplater – использование нескольких фильтров | Формат для конкретной страны и пользовательские десятичные дроби
Плохой INP при нажатии на внешнюю ссылку
Таблицы данных: добавление входных данных для поиска по отдельным столбцам
Yarn устанавливает локальный пакет npm так, чтобы локальный пакет использовал node_modules основного проекта (ошибка ckeditor-duduced-modules)
Проблема невозможности создать прямоугольную цветную область, когда рисуемый полукруг нарисован крайне справа