hopefully we are all in good health wherever we are.
I have a problem when I want to create a Quiz with Drag and Drop type in React Native. I'm using the react-native-drax library. The data I use comes from firebase, if it is described as follows :
[{
quest: "Book ?",
options: ["Yes","No"],
correct_option: "Yes",
type:'yesorno',
image:'urlofimages'
},
{
quest: "Fill the following words \n F_ _t Train M_ _ ern",
options: ["as","od","am"],
correct_option: ["as","od"],
type:'drag',
image:''
}]
(all quiz data is accommodated in the variable allData).
then I made the following components, to create a drag and drop area. I made it dynamic. Dropzone corresponds to the number of correct_option and TouchDrag corresponds to the number of options (taken from data).
import { DraxProvider, DraxView, DraxList } from 'react-native-drax';
....
.....
.......
const Exam = () => {
const [showAnwOne, setAnwOne] = useState([]);
const [showAnwTwo, setAnwTwo] = useState([]);
...
.....
.......
const Draxzone= ({item,index}) =>{
return(
<View style={{flexDirection:'row', justifyContent:'space-around', width:'100%', marginBottom:20}}>
<DraxView
style={styles.receiver}
key={item}
receivingStyle={styles.receiving}
onReceiveDragDrop={(event) => {
let selected_item = event.dragged.payload;
setAnwOne(selected_item)
}}
>
<Text>{showAnwOne}</Text>
</DraxView>
</View>
)
}
const TouchDrag = ({item,index})=>{
return(
<DraxView
style={styles.draggable}
draggingStyle={styles.dragging}
dragReleasedStyle={styles.dragging}
hoverDraggingStyle={styles.hoverDragging}
dragPayload={index}
longPressDelay={0}
key={item}
>
<Text style={{color:'#FFF'}}>{item}</Text>
</DraxView>
)
}
after that I display the component as follows:
(quiz is displayed based on its type)
{allData[currentIndex]?.type === 'drag' ?
<DraxProvider>
<View style={{width:'100%'}}>
<View style={{flexDirection:'row', justifyContent:'space-around', width:'100%', marginBottom:20}}>
{allData[currentIndex]?.correct_option.map((item, index) => Draxzone({ item, index }))}
</View>
</View>
<View style={{flexDirection:'row', justifyContent:'space-around', width:'100%', marginBottom:20}}>
<DraxList
data={allData[currentIndex]?.options}
renderItemContent={TouchDrag}
keyExtractor={(item, index) => item.toString()}
numColumns={3}
ItemSeparatorComponent={FlatListItemSeparator}
scrollEnabled={true}
/>
</View>
</DraxProvider>
: <Text>Sorry This Qustion not have type</Text> }
the result is executed as follows:
I tried to drag and drop components, the results are like in steps 2 and 3
while the result I want is as follows: Text on Dropzone follows text on TouchDrag
after that, I'm also confused to validate the correct answer in the order in correct_option. not like a quiz that only uses options from A to E for example. Honestly this is new to me, and I've been looking for quiz reference resources that use drag and drop in React Native, but I haven't found any results.
if there is something easier to understand, I ask for guidance. I hope that some of my friends here can help with this problem, thank you for your attention and time.
Not sure if this will fix your issue, but here are some suggestions:
Try to use unique IDs instead of indexes.
DraxView has a payload property in which you could pass in the ID and use it accordingly.
Good luck!
Related
I'm stuck at a problem - I'm building a receipes app with Firebase Realtime. I've a working prototype, but I'm stuck with an issue where useEffect won't trigger a reload after editing the array [presentIngredients].
This how my presentIngredients is defined (note that presentIngredient is used to store the current user input before the user adds the ingredient. After that, the presentIngredient get's added to the presentIngredients!):
const [ presentIngredients, setPresentIngredients ] = useState([]);
const [ presentIngredient, setPresentIngredient ] = useState('');
My useEffect hook looks like that:
useEffect(() => {
console.log('useEffect called!')
return onValue(ref(db, databasePath), querySnapshot => {
let data = querySnapshot.val() || {};
let receipeItems = {...data};
setReceipes(receipeItems);
setPresentIngredients(presentIngredients);
})
}, [])
Here's my code to render the UI for adding/removing existing ingredients:
{ /* adding a to-do list for the ingredients */ }
<View style={styles.ingredientsWrapper}>
{presentIngredients.length > 0 ? (
presentIngredients.map((key, value) => (
<View style={styles.addIngredient} key={value}>
<Text>{key} + {value}</Text>
<TouchableOpacity onPress={() => removeIngredient(key)}>
<Feather name="x" size={24} color="black" />
</TouchableOpacity>
</View>
))
) : (
<Text style={styles.text}>No ingredients, add your first one.</Text>
)}
<View style={styles.addIngredientWrapper}>
<TextInput
placeholder='Add ingredients...'
value={presentIngredient}
style={styles.text}
onChangeText={text => {setPresentIngredient(text)}}
onSubmitEditing={addIngredient} />
<TouchableOpacity onPress={() => addIngredient()}>
<Feather name="plus" size={20} color="black" />
</TouchableOpacity>
</View>
</View>
And this is my function to delete the selected entry from my presentIngredients array or add one:
// update the ingredients array after each input
function addIngredient() {
Keyboard.dismiss();
setPresentIngredients(presentIngredients => [...presentIngredients, presentIngredient]);
setPresentIngredient('');
}
// remove items by their key
function removeIngredient(id) {
Keyboard.dismiss();
// splice (remove) the 1st element after the id
presentIngredients.splice(id, 1);
setPresentIngredients(presentIngredients);
}
The useEffect hook isn't triggered when adding an ingredient, however the change is instantly rendered on the screen. If I delete an item, the change isn't noticeable until I reload the screen - what am I doing wrong?
Note that all this is happening before data is send to Firebase.
Issues
There are a few overt issues I see with the code:
The code uses the array index as the React key, so if you mutate the array, i.e. add, remove, reorder, etc... the index values won't be the same as they were on a previous render cycle per array element. In other words, the React key is the same regardless what value is now at any given index and React likely bails on rerendering.
The removeIngredient callback handler is mutating the existing state instead of creating a new array reference.
Solution
Using the array index as a React key is bad if you are actively mutating the array. You want to use React keys that are intrinsically related to the data so it's "sticky" and remains with the data, not the position in the array being mapped. GUIDs and other object properties that provide sufficient uniqueness within the data set are great candidates.
presentIngredients.map((el, index) => (
<View style={styles.addIngredient} key={el.id}>
<Text>{el.key} + {index}</Text>
<TouchableOpacity onPress={() => removeIngredient(el.id)}>
<Feather name="x" size={24} color="black" />
</TouchableOpacity>
</View>
));
Use Array.prototype.filter to remove an element from an array and return a new array reference.
function removeIngredient(id) {
Keyboard.dismiss();
setPresentIngredients(presentIngredients => presentIngredients.filter(
el => el.id !== id
));
}
Note above that I'm assuming the presentIngredients data element objects have a GUID named id.
Listening for state updates
The useEffect hook isn't triggered when adding an ingredient, however
the change is instantly rendered on the screen.
The single useEffect only exists to run once when the component mounts to fetch the data and populate the local state. If you want to then issue side-effects when the state updates later you'll need a second useEffect hook with a dependency on the state to issue the side-effect.
Example:
useEffect(() => {
console.log("presentIngredients updated", { presentIngredients });
// handle side-effect like updating the fire store
}, [presentIngredients]);
you need to create another useEffect, because the first useEffect need to be called only once (because you are setting a watcher).
useEffect(() => {
// this effect will be executed when presentIngredients change
}, [presentIngredients]);
if you don't remember how the array of dependencies work
you can check the explanation here
https://reactjs.org/docs/hooks-effect.html#tip-optimizing-performance-by-skipping-effects
Been so curious about my console.log output.
I have a button, when clicked it should basically add 1 to the use-State(1) I have created but it seems that I am doing something wrong; Please I need help with know if I am managing my State correctly {I am using React HOOKs}
const [procedureCounter, setProcedureCounter] = useState([1]);
const addProcedureHandler = () => {
setProcedureCounter((procedureCounter) => [
...ProcedureCounter,
{
id: procedureCounter.length,
//value: procedureCounter + 1 // value is undefined for some reason so I removed it but still works
},
]);
console.log(ProcedureCounter);
{ procedureCounter.map((item, value) => (
<View style={{ marginVertical: 15 }} key={item.id + 1}>
<ProcedureSteps step={value + 1} /> //This is a TexteInput Form
</View>
))}
<TouchableOpacity onPress={addProcedureHandler}>
ADD Button
</TouchableOpacity>
Once 'ADD Button' is pressed it does not start from 2 as I have 1 set as initialState.
Below is my Terminal Output after clicking or Pressing 'Add Button' 3 Times
Thank you for taking the time to look into this. I really hoped I explained this as best as I can. Thank you again!
First, have a consistent data type in your state so that is will easy mapping over values to create components.
const [procedureCounter, setProcedureCounter] = useState([{
id: 1,
value: 1
}]);
The reason you are getting undefined is you are not accessing the array properly. Check the below snippet to see how to use it. I have also updated the map iteration for component creation.
const [procedureCounter, setProcedureCounter] = useState([{
id: 1,
value: 1
}]);
const addProcedureHandler = () => {
setProcedureCounter((procedureCounter) => [
...procedureCounter,
{
id: procedureCounter[procedureCounter.length -1].id + 1,
value: procedureCounter[procedureCounter.length -1].value + 1
},
]);
console.log(procedureCounter);
{ procedureCounter.map((item) => (
<View style={{ marginVertical: 15 }} key={item.id}>
<ProcedureSteps step={item.value} /> //This is a TexteInput Form
</View>
))}
<TouchableOpacity onPress={addProcedureHandler}>
ADD Button
</TouchableOpacity>
https://res.cloudinary.com/catify/image/upload/v1588704903/hcnqjp7okfykkb3az2v3.jpg
Hello im trying to create a proyect of a guessing game, i have multiple components of letters as show in the image, some letters are needed for the answer and some are not, i need a button that when i click it it removes or hides the components that are not needed for the answer, how can i do this with react or react native?
Im saving the letters in a array and then rendering them using Map with a custom component that is styled to look like the photo, im doing it in react native but i think it should be the same in react, any help is welcome, thanks.
return (
<Animated.View style={{flex: 1}}>
{Letters.forEach(element => {
<LetterCard letter={element} />;
})}
<Button
title="eliminar"
onPress={() => {
eliminar;
}}
/>
</Animated.View>
);
You probably need a list in state or somewhere that holds which letters are needed and which aren't, as well as a boolean to determine if you are showing all letters or just your needed letters.
Your button which toggles to show/hide the unneeded letters would simply toggle the neededOnly state.
this.state={
neededLetters = [], //array of needed letters
neededOnly = false,
}
{neededOnly ?
neededLetters.forEach(element => {
<LetterCard letter={element} />;
}) :
Letters.forEach(element => {
<LetterCard letter={element} />;
})}
<Button
title="eliminate"
onPress={() => {
this.setState(prevState => ({
neededOnly: !prevState.neededOnly
}));
/>
What I did here so far is, just created three random views and with the help of the state, I just hide/show the view. However, according to requirements that I have, the logic is the following:
If you click to any close icon (x) it should disappear and in place of this, the view which is standing in the bottom should replace it. Like in Gmail, if you archive message it will be archived and will disappear. The view in the bottom will fill that place.
Can anyone share, small example in order to figure out concept. I understand here that task should be done with array and view should be displayed with a help of map=>. I am bit in confusion since this task requirement concept is brand new to me.
I created for you a small example in which we display cards and when you click on an item, that item is removed.
The code and the working example can be found in the followig link
const App = () => {
const items = ['Item1', 'Item2', 'Item3']
const [visible, setVisible] = useState({
item0: true,
item1: true,
item2: true
});
const itemClicked = (item) => {
setVisible({
...visible,
[item]: false
})
}
return (
<View style={styles.container}>
{
items.map((item, i) => {
const itemRendered = `item${i}`;
return (
<View>
{
visible[itemRendered] &&
<TouchableOpacity onPress={() => itemClicked(itemRendered)} style={styles[itemRendered]}>
<Text>{item}</Text>
</TouchableOpacity>
}
</View>
)
})
}
</View>
);
}
I used the map to render 3 clickable items. Those will be displayed only if they should be displayed based on the value of visible[itemRendered]. The component keeps the state of which items should be visible or not. In case you click on an item, the state is updated so as the clicked item wont be displayed.
I am trying to pass the data from one screen to another and I have data in
this.props.data which has something like this ["12121","434536"], I am trying to do the following
trying to add that data to list via this.ds.cloneWithRows([values]),
but I am getting output like below:
["12121","434536"] in one row, its not adding the elements one after another.
let values= [];
values =this.props.data;
alert(values)
this.ds = new ListView.DataSource({rowHasChanged: (r1, r2) => r1 !== r2});
this.state = {
dataSource: this.ds.cloneWithRows([values]),
<ListView
style={{width: '100%'}}
renderHeader={() => <Text style={styles.text}>adding Started</Text>}
dataSource={this.state.dataSource}
renderRow={(rowData) => <View style={{borderWidth:1, borderColor: '#232C46',borderRadius: 4}}>
<Text style={[styles.text, {backgroundColor: '#192034'}]}>{rowData}</Text>
</View>
}
/>
Agreed, with the first answer, it's a syntax error ("values" is already in the correct form, no need to put it in an array).
Also, just FYI, ListView is a deprecated component and you may want to convert this into a FlatList for fewer bugs, better performance / memory usage, and I think a more intuitive API.
https://facebook.github.io/react-native/docs/flatlist
Your data is already an array. You don't need the extra []
Change it like below;
this.ds.cloneWithRows(values)