Save selected radio button values even after closing the app - javascript

Im trying to make a feature for my app where the user can fill a questionnaire with radio buttons and he can save the progress and can return later to finish it.
Take a look at this code:
const Step3 = () => {
const [questions, setQuestions] = useState([]);
const [answers, setAnswers] = useState([]);
const {addQuest} = useContext(AuthContext);
const getQuestions = async () => {
const locale = 'sq'; // TODO: get current locale
const response = await apiStandarts.get(`/questions?locale=${locale}`, {
params: { _limit: MAX_QUESTIONS, active: 1, _sort:'sortId:ASC' },
});
setQuestions(response.data);
};
const isOptionSelected = (option) => {
const answer = answers[option.question]
if (answer) {
return option.id == answer.id
}
return false;
}
const saveData = () =>{
AsyncStorage.setItem('questions', JSON.stringify(answers)).then(() => {
console.log('data saved', answers)
}).catch((error) => {
console.log(error)
})}
const retrieveData = async () => {
try {
const answers = await AsyncStorage.getItem('questions');
const parsed = JSON.parse(answers);
Alert.alert(`${parsed.id}`);
} catch(error) {
Alert.alert(error)
}
}
useEffect(() => {
getQuestions();
}, []);
const OptionList = (groupOption) => {
return (
<FlatList
data={groupOption.options}
keyExtractor={(result) => result.id.toString()}
renderItem={({ item, index}) => {
const clickedRadio = () => {
const selectedOption = {[item.question]:{...item}}
setAnswers({...answers, ...selectedOption})
console.log(answers)
}
const status = isOptionSelected(item) ? true : false
return (
<View key={index}>
<Radio initialValue={status} label={item.description} onChange={() => clickedRadio()}/>
</View>
);
}}
/>
);
};
return (
<View style={styles.container}>
<Text style={{ fontWeight: "bold", fontSize: 16, color: "#6B24AA" }}>
{t("Choose an option/Scroll for more questions")}
</Text>
<FlatList
data={questions}
keyExtractor={(result) => result.id.toString()}
renderItem={({ item, index }) => {
return (
<View style={styles.groupOptions} key={index}>
<Text>{item.description}</Text>
<OptionList options={item?.question_options}></OptionList>
</View>
);
}}
/>
<Button onPress={() => addQuest({answers})}>Save progress</Button>
<Button onPress={() => retrieveData()}>Get progress</Button>
</View>
)
}
I've tried with AsyncStorage but i get my questions from an API and the data re-renders every time the app opens again. Any ideas how to achieve this ? Thanks in advance

Related

how to assign each list item to the same button in react native

this is my
homePage
as you can see I have some items and a button that I use to add them to the chartScreen, which is chartScreen , I'm using context api to store items in cartContext.js where each item has its own key to make things easier but I cannot correlate the logic for how to assign each item to the button so whenever I press the button its own item gets added to the chartScreen.
const CartContext = createContext();
export function CartProvider({ children }){
const [items, setItems] = useState(
[
{
title: "Pınar",
text: "Pınar Klasik Dana Sucuk (225g)",
pic: <Image
style={styles.image1}
source={require("./Images/klasik_dana_sucuk_paket.png")} />,
key: 1,
},
{
text: "Pınar Uzun Sosis (225g)",
pic: <Image
style={styles.image1}
source={require("./Images/pinar_uzun_sosis.png")} />,
key: 2,
},
]
const [store, setStore] = useState([]);
const addToCart = (text) => {
setStore((prevState) => [
...prevState,
text,
]);
}
return (
<CartContext.Provider value={{items,setItems, addToCart, store}}>
{children}
</CartContext.Provider>
)
I found a cheeky way to send a specific item from addButton.js
const AddButton = () => {
const { items } = useContext(CartContext);
const { addToCart } = useContext(CartContext);
return (
<TouchableOpacity style={styles.button} onPress={() => addToCart(items[0])}>
<Text>+</Text>
</TouchableOpacity>
)
}
and in the chartScreen I view it just like this
const ChartScreen = () => {
const { store } = useContext(CartContext);
if(store.length != 0) {
return (
<FlatList
data={store}
renderItem={({ item }) => (
<View style={styles.listItem}>
<Text style={styles.itemText}>{item.pic} {item.text}</Text>
</View>
)}
/>
)
}
this is for sure not even the wrong way to achieve the goal so can you help me?
You can import your button and make it common for all the items, and pass the value to it.
Here is your button code
const AddButton = ({item}) => {
const { items } = useContext(CartContext);
const { addToCart } = useContext(CartContext);
return (
<TouchableOpacity style={styles.button} onPress={() =>
addToCart(item?.text)}>
<Text>+</Text>
</TouchableOpacity>
)
}
Here you can make button for all you item
const ChartScreen = () => {
const { store } = useContext(CartContext);
if(store.length != 0) {
return (
<FlatList
data={store}
renderItem={({ item }) => (
<View style={styles.listItem}>
<Text style={styles.itemText}>{item.pic} {item.text}</Text>
<AddButton item={item} />
</View>
)}
/>
)
}

TypeError: undefined is not a function (near '...todos.map...')

I am trying to use ASyncStorage inside of the useState hook to load data from storage if present.
I am rendering the todos inside a map function but before that I am checking whether todos is undefined or [].
The logic should be returning [] to useState if the data is not present but it is giving undefined!
Here is an image of the error
Here is the code:
export default function App() {
const [todo, setTodo] = useState('');
const [todos, setTodos] = useState(async () => {
try {
const value = await AsyncStorage.getItem('#MySuperStore:key');
// We have data!!
return value ? JSON.parse(value) : [];
} catch (error) {
// Error retrieving data
console.log(error);
}
});
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(newTodos).then(_retrieveData());
// setTodos(newTodos);
}
};
const deleteTodo = (idx) => {
setTodos(todos.filter((todo, id) => id !== idx));
};
const _storeData = async (value) => {
try {
await AsyncStorage.setItem('#MySuperStore:key', JSON.stringify(value));
} catch (error) {
// Error saving data
console.log(error);
}
};
const _retrieveData = async () => {
try {
const value = await AsyncStorage.getItem('#MySuperStore:key');
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 === [] || todos === undefined ? (
<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>
);
}
I'm not sure why the default state of todos would use an async function, that doesn't make sense to me. Instead I would do const [todos, setTodos] = useState([]); and leverage useEffect hook something similar to this:
useEffect(() => {
(async () => {
try {
const value = await AsyncStorage.getItem('#MySuperStore:key');
// We have data!!
setTodos(value ? JSON.parse(value) : [])
} catch (error) {
// Error retrieving data
console.log(error);
}
})();
},[]);

multiple switches in react native how to

I'm trying to make multiple switches, the problem is that when I enable / disable 1, all others enable / disable together.
useEffect is being used for me to bring my data from my API.
const [data, setData] = useState([]);
useEffect(() => {
async function fetchData() {
await api
.get('/synchronization/group/true')
.then(response => setData(response.data.data))
.catch(error => setData(error));
}
fetchData();
}, []);
useEffect(() => {
// Just return data
}, [data]);
const [isEnabled, setIsEnabled] = useState({
enabled: false
});
const toggleSwitch = () => setIsEnabled((toggle) => !toggle);
const Item = ({ id, title }) => {
return (
<View>
<TextList>{title}</TextList>
<Switch
key={id}
onValueChange={toggleSwitch}
value={isEnabled}
/>
</View>
);
};
And here my render switches with looping,
It adds component according to the records in the database
const renderItem = ({ item }) => {
const date = new Date(item.synchronization)
const formattedDate = date.toISOString().split('T')[0];
return <Item id={item.id} title={'Sincronização: ' + formattedDate.split('-').reverse().join('/')} />
};
return (
<Container>
<List
data={data}
renderItem={renderItem}
keyExtractor={item => item.id.toString()}
/>
<TouchableButton onPress={() => { navigation.navigate('Loading') }}>
<TextButton>Carregar para offline</TextButton>
</TouchableButton>
</Container>
);
}
My screen:
Example
If I understand correctly, all the code that you provided is in one component.
You need to store the state of switch button separately.
Try it:
const Item = ({id, title, value, onValueChange}) => (
<View>
<TextList>{title}</TextList>
<Switch
onValueChange={onValueChange}
value={value}
/>
</View>
);
const Component = ({navigation}) => {
const [data, setData] = useState([]);
useEffect(() => {
const fetchData = async () => {
await api
.get('/synchronization/group/true')
.then(response => setData(response.data.data))
.catch(error => setData(error));
}
fetchData();
}, []);
useEffect(() => {
// Just return data
}, [data]);
const [state, setState] = useState({
switches: {}
});
const toggleSwitch = useCallback((id) => () => setState((state) => ({
...state,
switches: {
...state.switches,
[id]: !state.switches[id]
}
})), []);
const renderItem = useCallback(({item}) => {
const date = new Date(item.synchronization)
const formattedDate = date.toISOString().split('T')[0];
return <Item id={item.id} title={'Sincronização: ' + formattedDate.split('-').reverse().join('/')} value={!!state.switches[item.id]} onValueChange={toggleSwitch(item.id)}/>
}, [state.switches, toggleSwitch]);
return (
<Container>
<List
data={data}
renderItem={renderItem}
keyExtractor={item => item.id.toString()}
/>
<TouchableButton onPress={() => navigation.navigate('Loading')}>
<TextButton>Carregar para offline</TextButton>
</TouchableButton>
</Container>
);
};
If you don't need to store states of all switches in your parent component, you can declare state in a component that will be rendered in list.
Here you declare state, that will be handled locally in this component.
const SwitchComponent = (item => {
const [switchState, setSwitchState] = useState(item.state)
return (
<Switch value={switchState} onValueChange={ () => setSwitchState(prevState => !prevState)}/>
)
})
then it can be rendered in List
<View style={styles.container}>
{arr.map(item => (<SwitchComponent item={item}/>))}
</View>
Rendering as many switches as you want, with state handled separately.
You can try it here.

display firebase data on a flatlist

I'm trying to display a bunch of data i have on firebase in a flatlist, I don't really know where the problem is right now, i've tried physically filling the array out and that works but it doesn't when i get the data from firebase. I do see that im getting the data on the console log but it's not getting displayed.
function Squad() {
const gk = [];
db.collection('squad').orderBy('position').get().then(snapshot => {
snapshot.forEach(doc => {
const playerObject = doc.data();
gk.push({name: playerObject.name, number: playerObject.number});
console.log(gk);
});
});
const Item = ({ name, number }) => (
<View style={styles.item}>
<Text style={styles.itemText}>{number} - {name}</Text>
</View>
);
const renderItem = ({ item }) => (
<Item name={item.name} number={item.number} />
)
return(
<View>
<View style={styles.bar}>
<Text style={styles.barText}>goalkeeper</Text>
</View>
<FlatList
data={gk}
renderItem={renderItem}
keyExtractor={item => item.id}
/>
</View>
)
}
You can get the info on mount in an async function, then store it in a stateful array using hooks.
function Squad() {
const [gk, setGk] = useState([]);
const getSquad = async () => {
const ref = db.collection('squad').orderBy('position');
const doc = await ref.get();
const playerObject = doc.data();
const newGk = [...gk, {name: playerObject.name, number: playerObject.number}];
setGk(newGk);
}
useEffect(() => {
getSquad();
}, [])
const Item = ({ name, number }) => (
<View style={styles.item}>
<Text style={styles.itemText}>{number} - {name}</Text>
</View>
);
const renderItem = ({ item }) => (
<Item name={item.name} number={item.number} />
)
return(
<View>
<View style={styles.bar}>
<Text style={styles.barText}>goalkeeper</Text>
</View>
<FlatList
data={gk}
renderItem={renderItem}
keyExtractor={item => item.id}
/>
</View>
)
}
Start by creating a useEffect hook to act as a componentWillMount function to call the method when the compnent is ready. I have also included catch block in order to show any errors that might occur.
import React, { useState, useEffect } from "react";
const Squad = () => {
const [gk, setGK] = useState([]);
useEffect(() => {
getSquadData();
});
const getSquadData = () => {
db.collection("squad")
.orderBy("position")
.get()
.then((snapshot) => {
let myData = [];
snapshot.forEach((doc) => {
const playerObject = doc.data();
myData.push({
id: playerObject.id,
name: playerObject.name,
number: playerObject.number,
});
});
setGK(myData);
})
.catch((error) => {
console.log("Error getting data: ", error);
});
};
const Item = ({ name, number }) => (
<View style={styles.item}>
<Text style={styles.itemText}>
{number} - {name}
</Text>
</View>
);
const renderItem = ({ item }) => (
<Item name={item.name} number={item.number} />
);
return (
<View>
<View style={styles.bar}>
<Text style={styles.barText}>goalkeeper</Text>
</View>
<FlatList
data={gk}
renderItem={renderItem}
keyExtractor={(item) => item.id}
/>
</View>
);
};
export default Squad;
So if you are seeing error logs in your console, check your collection in firestore, and also check the rules in your firestore.

React Native: How to update a single item in a Flatlist without changing states of all items

I have Flatlist with an array of items and when I select an item, all other items take the state/color of the selected item grey. How do I ensure that only selected items changes state?
Also the items I select are stored in a firestore database and are deleted when unselected. Can you also help me to store this changed state into firestore db. Thank you and I appreciate your effort.
const [catalogueArray, setCatalogueArray] = useState([])
const [addCompare, setAddCompre] = useState(false)
const [textvalue, setTextValue] = useState(`Add to \n Cart`)
const [textColor, setTextColor] = useState('green')
const storeToDB = async (item) => {
if (!addCompare) {
await db.collection('users').doc(auth.currentUser.uid)
.collection('myProducts').doc(item.storeName + item.genName)
.set({
product_id: item.id,
product_genName: item.genName
})
} else {
await db.collection('users').doc(auth.currentUser.uid)
.collection('myProducts').doc(item.storeName + item.genName).delete()
}
}
const clickedBtn = () => {
setAddCompre(!addCompare ? true : false)
setTextValue(!addCompare ? `Item \n Added` : `Add to \n Cart`)
setTextColor(!addCompare ? 'grey' : 'green')
}
render(
....
<FlatList
keyExtractor={(item) => item.id}
data={catalogueArray}
renderItem={({ item }) => (
......
<TouchableOpacity style={styles.btn} onPress={() => { storeToDB(item); clickedBtn() }}>
<MaterialCommunityIcons name='plus-circle-outline' size={24} color={textColor} />
......
<Text style={...}>{textvalue}</Text>
</TouchableOpacity>
/>
const [isSelected, setIsSelected] = useState([])
const [addCompare, setAddCompare] = useState(false)
const catalogueList = () => {
const catalogue = data.filter(item => {
return item.storeName == navigation.state.params.selectStore
}).map(item => ({ ...item }))
setCatalogueArray(catalogue)
}
const storeToDB = async (item) => {
if (!addCompare) {
await db.collection('users').doc(auth.currentUser.uid).collection('myProducts').doc(item.storeName + item.genName).set({
product_id: item.id,
product_genName: item.genName
})
} else {
await db.collection('users').doc(auth.currentUser.uid).collection('myProducts').doc(item.storeName + item.genName).delete()
}
}
const clickedBtn = async (item) => {
setAddCompare(
addCompare ? false : true
)
if (isSelected.indexOf(item) > -1) {
let array = isSelected.filter(indexObj => {
if (indexObj == item) {
return false
}
return true
})
setIsSelected(array)
} else {
setIsSelected([
...isSelected, item
])
}
}
return (
....
<FlatList
extraData={isSelected}
keyExtractor={(item) => item.id}
data={catalogueArray}
renderItem={({ item }) => (
<View style={styles.contain}>
<TouchableOpacity style={styles.btn} onPress={() => { storeToDB(item); clickedBtn(item) }}>
<MaterialCommunityIcons name='plus-circle-outline' size={24} color={isSelected.indexOf(item) > -1 ? 'grey' : 'green'} />
<View style={{ position: 'absolute', bottom: 3 }}>
<Text style={{ fontSize: 10, textAlign: 'center', color: isSelected.indexOf(item) > -1 ? 'grey' : 'green' }}>{isSelected.indexOf(item) > -1 ? 'item \nAdded' : 'Add to\n Compare '}</Text>
</View>
</TouchableOpacity>
</View>
)}
/>
)
}```

Categories