ОТРЕДАКТИРОВАНО. Я изменил структуру потока, чтобы сделать его менее многословным. Надеюсь, это более удобоваримо
У меня есть набор экранов (желтый, оранжевый, зеленый и т. д.), и на каждом из них есть 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 воспроизводимо? Нет, если у вас не запущена выставка в эмуляторе и все необходимые зависимости. Я выбрал другой маршрут, который нельзя использовать повторно, но он менее многословен. я обновлю
@jsejcksn Думаю, теперь стало намного чище, если хочешь взглянуть. Дайте мне знать, если вам нужно, чтобы я что-нибудь добавил к этому.
Кроме того, распространение создает неглубокую копию, так являются ли элементы массива объектами? Вы перебираете обновленный список или статический список, созданный вне модального компонента?
@morganney Раньше у меня там был весь экранный код, за вычетом таких тривиальных вещей, как другие функции и стили, и были жалобы, что кода слишком много. Я обновлю «весь» код файла ниже в качестве дополнительной ссылки.
Я обновил все это. Но возможно, вы правы. SetDropdownData создает коллекцию из группы собранных флажков. Это вполне может быть частью проблемы
Я был бы не против предоставить вам временный доступ к github в рамках совместной работы. Но это зависит от тебя
Суть вашей проблемы выглядит следующим образом: const foo = { name: 'foo' }; const bar = { name: 'bar' }; const a = [foo, bar]; const b = [...a]; b[0].checked = true;
Теперь, если вы это сделаете a[0].checked
, это тоже будет true
.
Вы можете попробовать провести рефакторинг своего кода или заменить развороты на StructuredClone. Я бы предложил первое.
Я не хочу навязываться. Я понимаю. Ваше объяснение объясняет, почему они оба верны. Есть ли у вас ответ, или показанного кода недостаточно, чтобы найти решение?
В selectItem
создайте глубокий клон данных раскрывающегося списка, а затем переберите этот список вместо статического, созданного вне компонента.
Если ваш список большой, рассмотрите более простую/плоскую структуру данных, которая ссылается только на уникальные идентификаторы для ваших элементов раскрывающегося списка.
Возможно, я не понимаю методологии или того, как это правильно сделать, но это не сработало. Кстати, вместо этого мне пришлось использовать lodash, поскольку StructuredClone предназначен для веб-реализации. Это нативный код
В предоставленном вами коде именно в этой части:
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 Тогда вам нужен другой подход. Вам нужно обрабатывать состояние вне компонента и передавать его ему. Итак, вы по сути хотите сохранить состояние всех карт. Я думаю, так намного проще. Я думаю, что лучше всего будет просто создать контекст, управляющий состояниями этих карт.
В ваш образец включено много кода, который не имеет отношения к описанной проблеме. Можете ли вы отредактировать его, чтобы он стал минимально воспроизводимым примером с только абсолютно необходимым кодом для воспроизведения проблемы?