I have an array of universities
And I need to make a search funtion.
My search implementation works perfectly and finds the university that I type.
However when I select the option, it confuses itself and always select by new index.
For example my array is ["A.T. Still University", "Abilene Christian University", "Abraham Baldwin University", "Academy for Five Element University" ... ]
In here, A.T. Still University is at the 0 index originally. and when I make a search with filter, it creates a new array. I type "Abilene" for example and it brings me a new array with results and "Abilene" becomes at the 0 index. However it doesnt select this one and keeps selecting "A.T. Still University" which has the original 0 index.
Can someone help me how to fix this problem ?
I want to be able to select what I type in search area.
My code is below
const SignupStep5 = forwardRef((props, ref) => {
const dispatch = useDispatch();
const userInfo = useSelector(state => state.profile.user);
const universityList = useSelector(state => state.config.universities);
const isFetchingUniversity = useSelector(state =>
isLoadingSelector([fetchUniversities], state),
);
//State
const [
selectedUniversityId,
setSelectedUniversityId,
selectedUniversityIdRef,
] = useStateRef(null);
const [choosenUniversity, setChoosenUniversity, choosenUniversityRef] =
useStateRef('');
const [choosenUniversityId, setChoosenUniversityId, choosenUniversityIdRef] =
useStateRef('');
useEffect(() => {
if (universityList.length) {
if (props.activeIndex == 5 && userInfo && userInfo.university) {
const index = universityList.findIndex(university =>
isEqual(university, userInfo.university),
);
if (index !== -1) {
setSelectedUniversityId(index);
setChoosenUniversity(userInfo.university);
}
}
}
}, [universityList]);
const renderHeader = () => (
<View>
<View style={styles.modalHeaderContainer}>
<Text style={styles.modalHeaderText}>
{strings.aboutMe.selectUniversity}
</Text>
<Touchable
style={styles.modalHeaderTextContainer}
onPress={() => {
if (selectedUniversityIdRef.current != null) {
setChoosenUniversityId(selectedUniversityIdRef.current);
setChoosenUniversity(
universityList[selectedUniversityIdRef.current],
);
closeBottomSheet();
}
}}>
<Text style={styles.modalHeaderSelectText}>
{strings.common.select}
</Text>
</Touchable>
</View>
<View style={styles.modalHeaderLine}></View>
</View>
);
const renderItem = ({item, index}) => (
<Touchable
style={
selectedUniversityIdRef.current === index
? styles.modalChildContainerSelected
: styles.modalChildContainer
}
onPress={() => handleSelection(index)}>
<Text style={styles.modalChildText}>{item}</Text>
</Touchable>
);
const handleSelection = id => {
const selectedId = selectedUniversityIdRef.current;
if (selectedId === id) setSelectedUniversityId(null);
else setSelectedUniversityId(id);
};
const onOverlayPress = () => {
if (choosenUniversityRef.current) {
const index = universityList.findIndex(
university => university === choosenUniversityRef.current,
);
if (index !== -1) {
setSelectedUniversityId(index);
}
} else if (
selectedUniversityIdRef.current != null &&
!choosenUniversityRef.current
) {
setSelectedUniversityId(null);
}
setModalVisible(false);
};
const [searchField, setSearchField] = useState('');
const searchInputFields = () => {
const filteredUniversity = universityList.filter(value => {
return value.toLowerCase().includes(searchField.toLowerCase());
});
return filteredUniversity;
};
return (
<>
<Portal>
{props.activeIndex == 5 && (
<BottomSheet
ref={bottomSheetRef}
snapPoints={snapPoints}
handleComponent={renderHeader}
backdropComponent={renderBackdrop}
onAnimate={handleSheetChanges}>
<TextInput
onChangeText={value => setSearchField(value)}
value={searchField}
style={{padding: 10, alignSelf: 'center'}}
placeholder="Search"
placeholderTextColor={'gray'}
/>
<BottomSheetFlatList
data={searchField ? searchInputFields() : universityList}
keyExtractor={(item, index) => index.toString()}
renderItem={renderItem}
contentContainerStyle={styles.modalChildStyle}
extraData={selectedUniversityIdRef.current}
/>
</BottomSheet>
)}
</Portal>
</>
);
});
export default SignupStep5;
Related
function Carts() {
let cartEval = [];
const cartData = useSelector((state) => state.cartProducts);
cartEval = cartData.map((item) => evalCart(item));
function evalCart(a) {
if (cartEval.find((b) => b.id == a.id)) {
return { ...b, numOfTimes: numOfTimes + 1 };
} else {
return {
id: a.id,
numOfTimes: 1,
prodTitle: a.title,
price: a.price,
};
}
}
const totalPrice = cartData
.map((item) => item.price)
.reduce((x, y) => x + y, 0);
function cartRender({ item }) {
return (
<View>
<View>
<View>
<Text>{item.numOfTimes}</Text>
<Text>{item.prodTitle}</Text>
<Text>{item.price}</Text>
<Text>Delete Button</Text>
</View>
</View>
</View>
);
}
return (
<View>
<View>
<Text>Total Sum of Items:{totalPrice}</Text>
</View>
<FlatList data={cartEval} renderItem={cartRender} />
</View>
);
}
const styles = StyleSheet.create({});
export default Carts;
This is what I want to achieve:
I need the function evalCart(a) to check if the item.id exist in the cartEval. If the id exists, increase item.nuofItems by 1. If it does not, create a new item with the id in the cartEval array.
But every time the function is called it returns false, hence the first part of the if statement never executes. only the else part. What am I doing wrong?
I was finally able to solve the puzzle. This is my solution below
function Carts() {
let cartEval = [];
const cartData = useSelector((state) => state.cartProducts);
cartData.map((item) => evalCart(item));
function evalCart(a) {
if (cartEval.find((b) => b.id == a.id)) {
const dummy = cartEval.filter((d) => d.id == a.id)[0];
cartEval.splice(cartEval.indexOf(dummy), 1, {
...dummy,
numOfTimes: dummy.numOfTimes + 1,
});
} else {
cartEval.push({
id: a.id,
numOfTimes: 1,
prodTitle: a.title,
price: a.price,
});
}
}
const totalPrice = cartData
.map((item) => item.price)
.reduce((x, y) => x + y, 0);
function cartRender({ item }) {
return (
<View>
<View>
<View>
<Text>{item.numOfTimes}</Text>
<Text>{item.prodTitle}</Text>
<Text>{item.price}</Text>
<Text>Delete Button</Text>
</View>
</View>
</View>
);
}
return (
<View>
<View>
<Text>Total Sum of Items:{totalPrice}</Text>
</View>
<FlatList data={cartEval} renderItem={cartRender} />
</View>
);
}
const styles = StyleSheet.create({});
export default Carts;
Thanks for your contributions
I have an accordion component in my React Native app which is from galio framework . I have populated it with api data. The accordion closes if you click in the title but I want it to close when I select a radio button. Here is my code:
const Step3 = () => {
const [questions, setQuestions] = useState([]);
const [answers, setAnswers] = useState([]);
const [icon, setIcons] = useState([]);
const [iconColor, setIconsColor] = useState([])
const [refreshing, setRefreshing] = useState(true);
const getQuestions = async () => {
const locale = i18next.language; // TODO: get current locale
const response = await apiStandarts.get(`/questions?locale=${locale}`, {
params: { active: 1, _sortId: [1,2] , _sort: "sortId:ASC"},
});
setRefreshing(false)
setQuestions(response.data);
};
const isOptionSelected = (option) => {
const answer = answers[option.question];
if (answer) {
return option.id == answer.id;
}
return false;
};
const questionIcon = async () => {
const response = await apiStandarts.get(`/commitments-icons`);
setIcons(response.data)
}
const questionIconColor = async () => {
const response = await apiStandarts.get(`/commitments`);
setIconsColor(response.data)
}
const objectMap = (obj, fn) =>
Object.fromEntries(
Object.entries(obj).map(([k, v], i) => [k, fn(v, k, i)])
);
const newAnswers = objectMap(answers, (item) => {
return [item.id, item.description];
});
// useEffect(() => {
// questionIcon();
// }, []);
useEffect(() => {
questionIcon();
getQuestions();
}, []);
const OptionList = (groupOption) => {
return (
groupOption.options.map((item, index) => {
const clickedRadio = () => {
const selectedOption = { [item.question]: { ...item } };
setAnswers({ ...answers, ...selectedOption });
};
let status = isOptionSelected(item) ? true : false;
return (
<Radio
initialValue={status}
label={item.description}
onChange={() => clickedRadio()}
color="rgba(0,0,0,.54)"
radioInnerStyle={{backgroundColor: "#3671a6"}}
labelStyle={ styles.label}
containerStyle={{ width: 300, padding: 5 }}
/>
);
})
);
};
return (
<View style={styles.container}>
<Text style={{ fontWeight: "bold", fontSize: 12, color: "#6B24" }}>
{t("Choose an option/Scroll for more questions")}
</Text>
<FlatList
data={questions}
keyExtractor={(result) => result.id.toString()}
contentContainerStyle={{ padding: 5, paddingBottom: 5 }}
refreshControl={<RefreshControl refreshing={refreshing} onRefresh={getQuestions} />}
renderItem={({ item, index }) => {
const arr = [item.commitments[0].commitment_icon];
const questIcon = arr.filter(i => Boolean(i)).map(id => icon.find(o => o.id === id)?.image?.url);
const imgUrl = APIURL + questIcon;
function iconBgColor(){
let bgColor
switch (item.commitments[0].commitment_icon) {
case 1:
bgColor="#78bad3"
break;
case 3:
bgColor = "#027a95"
break;
case 6:
bgColor = "#027a95"
break;
case 4:
bgColor = '#1fc191'
break;
case 5:
bgColor = '#78bad3'
break;
case 2:
bgColor = "#e4da4d"
break;
case 7:
bgColor = "#1fc191"
break;
default:
bgColor= "#fff"
break;
}
return bgColor;
}
const backgroundColor = iconBgColor(item.commitments[0].commitment_icon)
const data = [
{
title: (<>
<View style={[styles.iconWrapper,{backgroundColor: backgroundColor}]}>
{
imgUrl.indexOf('.svg') > 0 ? <SvgUri uri={APIURL + questIcon} height={20} width={20} style={styles.iconColor}/> : null
}
</View>
<Text style={styles.text}>{item.sortId}.</Text>
<Text style={styles.text} key={item.description}>{item.description}</Text>
</>),
content:<View><OptionList key={item?.question_options.id} options={item?.question_options}></OptionList></View>
},
];
return (
<View style={styles.groupOptions} key={index}>
<Accordion style={styles.accordion} headerStyle={styles.headerStyle} contentStyle={styles.contentStyle} dataArray={data} opened={index} />
</View>
);
}}
/>
</View>
);
};
Any ideas how to achieve what I want? Any answer would be appreciated, thanks.
you have a next code:
<Accordion
style={styles.accordion}
headerStyle={styles.headerStyle}
contentStyle={styles.contentStyle}
dataArray={data}
opened={index} // here you should have "isAccordionOpen"
/>
and handle a variable in state your Step3 component.
In this function you should to change this variable "isAccordionOpen"
const clickedRadio = () => {
const selectedOption = { [item.question]: { ...item } };
setAnswers({ ...answers, ...selectedOption });
}
As I have checked in the docs and Galio Library there seems no prop to manage Accordion after the first render cycle.
We can only manage the Accordion initially on the first render.
If you have to manage it then you have to make changes in the Galio library code.
Here I am attaching the Sample code base, Hoping that it might help you.
Sample Code:
import React, { useState } from 'react'
import { View } from 'react-native'
import { Accordion, Block, Checkbox } from 'galio-framework';
const App = () => {
const [openIndex, setOpenIndex] = useState(-1)
const radioClickHandler = (id, status) => {
setOpenIndex(-1)
}
const data = [
{
title: "First Chapter",
content: (
<Checkbox
onChange={radioClickHandler.bind(null, 'first')}
color="primary"
label="Primary Checkbox"
/>
)
},
{
title: "Second Chapter",
content: (
<Checkbox
onChange={radioClickHandler.bind(null, 'second')}
color="primary"
label="Secondary Checkbox"
/>
)
}
]
const onOpen = (prop) => {
setOpenIndex(prop?.title === "First Chapter" ? 0 : 1)
}
return (
<View style={{ flex: 1, justifyContent: 'center' }}>
<Block style={{ height: 200 }}>
<Accordion
dataArray={data}
opened={openIndex}
onAccordionOpen={onOpen}
/>
</Block>
</View>
)
}
export default App
Here are the changes in the library code to manage the collapse Accordion Component.
file Path: 'node_modules/galio-framework-src-Accordion.js'
Add Below Code
const [selected, setSelected] = useState(opened);
useEffect(() => {
setSelected(opened)
}, [opened])
I am trying to use AsyncStorage to fetch my todos from inside the useEffect hook. If there are no todos(Meaning todos === []) Then a Text Component shows saying "Add a todo".
App image in expo
Initially the todos are set to "[]" inside the useState hook. When the addItem() method is called onPress the todos are not loading.
I do not know why this is happening...
export default function App() {
const [todo, setTodo] = useState('');
const [todos, setTodos] = useState([]);
useEffect(() => {
_retrieveData();
}, [todos]);
const addItem = (newTodo) => {
if (newTodo.length === 0) {
Alert.alert(
'Enter a String',
'You have entered a string with 0 characters',
[{ text: 'Okay', style: 'default' }]
);
} else {
console.log(newTodo);
let newTodos = [newTodo, ...todos];
setTodo('');
_storeData(JSON.stringify(newTodos));
}
};
const deleteTodo = (idx) => {
setTodos(todos.filter((todo, id) => id !== idx));
};
const _storeData = async (value) => {
try {
await AsyncStorage.setItem('TASKS', value);
} catch (error) {
// Error saving data
console.log(e);
}
};
const _retrieveData = async () => {
try {
const value = await AsyncStorage.getItem('TASKS');
if (value !== null) {
// We have data!!
setTodos(JSON.parse(value));
console.log(value);
}
} catch (error) {
// Error retrieving data
console.log(error);
}
};
return (
<TouchableWithoutFeedback
onPress={() => {
Keyboard.dismiss();
}}
>
<View style={styles.outerContainer}>
<Text style={styles.header}>TODO</Text>
<View style={styles.container}>
<TextInput
placeholder='new todo'
style={styles.input}
value={todo}
onChangeText={(text) => {
setTodo(text);
}}
></TextInput>
<Button title='Add' onPress={() => addItem(todo)}></Button>
</View>
<ScrollView style={styles.scrollView}>
{todos === [] ? (
<View>
<Text>Add a todo!</Text>
</View>
) : (
todos.map((todo, idx) => (
<View style={styles.todo} key={idx}>
<Text style={styles.todoText}>{todo}</Text>
<View style={styles.delete}>
<Button
color='red'
title='Delete'
onPress={() => deleteTodo(idx)}
></Button>
</View>
</View>
))
)}
</ScrollView>
</View>
</TouchableWithoutFeedback>
);
}
Dont use passed todo value newTodo, as setState is async dont get executed immediately, so you can use current setted todo value instead passed old value,
const addItem = (newTodo) => {
if (todo.length === 0) {
Alert.alert(
'Enter a String',
'You have entered a string with 0 characters',
[{ text: 'Okay', style: 'default' }]
);
} else {
console.log(todo);
let newTodos = [todo, ...todos];
setTodo('');
_storeData(JSON.stringify(newTodos));
setTodos(newTodos);
}
};
I was making a to-do app in react-native. I made a function that deletes the task on press but when I do press, it deletes the first item on the list, not the one I pressed on.
Here is the code I used:
let id = 0;
const [task, setTask] = useState("");
const [taskList, setTaskList] = useState([
{ name: "Add your first task", id: id++ },
]);
const handleTask = (e) => {
e.preventDefault();
setTaskList([...taskList, { name: task, id: id++ }]);
setTask("");
};
function completeTask(index) {
let itemsCopy = [...taskList];
itemsCopy.splice(index, 1);
setTaskList(itemsCopy);
}
{taskList.map((taskList, index) => {
return (
<View style={styles.wrapper}>
<View style={styles.text}>
<TouchableOpacity key={index} onPress={completeTask}>
<Text> {taskList.name} </Text>
</TouchableOpacity>
</View>
</View>
);
})}
You don't pass index when call completeTask and update state wrond way.
onPress={() => completeTask(index)}
function completeTask(index) {
setTaskList((preTasks) => preTasks.filter((item, i) => i !== index));
}
This question already has an answer here:
Sorting react-native FlatList
(1 answer)
Closed 2 years ago.
I am working on a to do list app in react native, when a new item is added it goes directly to the last place and I will like every new object to go to the first place. To achieve this I tried adding a function that is supposed to sort the items but it the code doesnt make any changes. How can I sort these items in my to do list?
app.js
const [todos, setTodos] = useState([]);
const [addMode, setAddMode] = useState(false);
const [isReady, setIsReady] = useState(false);
const addTodoHandler = addTodos => {
if (addTodos.lenght === 0) {
return;
};
setTodos(prevTodos => [...prevTodos, { key: Math.random().toString(), value: addTodos, date: Date.now() }]);
setAddMode(false);
Keyboard.dismiss();
};
const sortTodos = () => { //this is the function that is supposed to sort the items.
const todoSort = [...todos];
const soarted = todoSort.sort((a, b) => {
return a.todoSort - b.todoSort;
})
setTodos(soarted);
};
return (
<View style={styles.screen}>
<Header />
<AddTodo onAddTodo={addTodoHandler} />
<FlatList
keyExtractor={(item, index) => item.key}
data={ todos }
renderItem={({ item }) => <TodoItem key={item.key}
todoKey={item.key}
title={item.value}
editHandler={handleEdit}
pressHandler={pressHandler}/> }
/>
</View>
);
AddTodo.js
const AddTodo = props => {
const [text, setText] = useState('');
const changeHandler = (val) => {
setText(val);
};
const addTodoHandler = () => {
props.onAddTodo(text);
setText('');
};
return (
<View style={styles.inputView}>
<TextInput style={styles.textInput} placeholder='What do you want to do?' onChangeText={changeHandler} value={text}/>
<Buttons title="Add" onPress={addTodoHandler} style={styles.salsachBtn}/>
</View>
);
};
TodoItem.js
const TodoItem = props => {
return (
<View>
<View style={styles.items}>
<View style={styles.itemContainer}>
<Text style={styles.itemText}>{props.title}</Text>
</View>
</View>
</View>
);
};
if you have any questions please let me know in the comments:)
First idea:
Add your 'sortTodos' inside function that handle adding new item.
Add date to items with e.g. Date.now()
Sort a.date - b.date
Second (without sorting): you can try to use unshift
const newTodo = [...prevTodos]
newTodo.unshift({ key: Math.random().toString(), value: addTodos });
setTodos(newTodo)