Have a problem with keyExtractor - Warning unique ''key'' - javascript

I'm new in react native , just starting to learn.
I have a FlatList that contain data from array like this one:
const friends = [
{ name: 'Friend #1', age: '26', },
{ name: 'Friend #2', age: '31' },
{ name: 'Friend #3', age: '34' },
];
In the method that return the FlatList I'm added keyExtractor:
return (
<FlatList
keyExtractor={friend => friend.name}
data={friends}
renderItem={({ item }) => {
return ([
<Text style={styles.itemView}>{item.name} - Age {item.age}</Text>
]
);
}} />
);
Still have a warning:
Warning: Each child in a list should have a unique "key" prop.%s%s

Your problem is that you nested Text element inside array
Change it like this:
<FlatList
keyExtractor={friend => friend.name}
data={friends}
renderItem={({ item }) => {
return (
<Text style={styles.itemView}>{item.name} - Age {item.age}</Text>
);
}}
/>
Codesandbox

Related

How to not show the first item on a array in a map?

I receive an array like this from backend:
[
{
id: 0,
name: "John",
language: "Enlgish"
},
{
id: 1,
name: "Chris",
language: "Spanish"
},
{
id: 2,
name: "Bastian",
language: "German"
}
]
So I display the languages from this array in a table, and to do that I map through them.
I don't want to show the first language on the first object of this array
Parent.js
const [language, setLanguage] = useState ([])
useEffect(() => {
axios
.get('api').then((res) => {setLanguage(response.data.languages)})
}, [])
Child.js
return(
{language.map((lang, i) => {
return (
<tr key={"item-" + i}>
<td>
<div>
<input
type="text"
value={
lang.language
? lang.language.shift()
: lang.language
}
</div>
</td>
</tr>
))}
)
So what I have tried by far is the shift method which removes the first item of an array, but it didn't work.
This error happened :TypeError: lang.language.shift is not a function
How can I fix this?
Use the index
{language.map((lang, i) => {
(i > 0) && (
return (
......

Next.JS router.query returns undefined for arrays

I'm trying to pass objects of an array in another page using router.query, single objects such as router.query.title work fine, but when it comes to arrays such as router.query.reviews it returns something like this reviews: ['', '', '', '', '', '', '', '']. I've tried using router.isReady but nothing seems to change
Example object in the array
{
title: "Center-Hall",
reviews: [
{
title: "Wislong Chew",
comment: "jsdncjnsd csdncjds csjcnsdj csjdcn cdc djndc",
},
{
title: "Wisdsdlong Chew",
comment: "jsdncjnsd csdncjds csjcnsdj csjdcn cdc djndc",
},
],
},
Parent
{array.map((item) => (
<Grid key={item} item>
<Card
label={item.title}
onClick={() =>
router.push({
pathname: "/page",
query: {
title: item.title,
reviews: item.reviews,
},
})
}
/>
</Grid>
))}
Next Page
function index() {
const router = useRouter();
useEffect(() => {
if (!router.isReady) return;
console.log(router.query);
}, [router.isReady]);
const Reviews = () =>
<ReviewCard
reviewsList={router.query.reviews}
/>
);
return (
<Box>
<Typography>{router.query.title}</Typography>
<Reviews />
</Box>
);
}
export default index;
When you passing an array using router.query to another page parse it into a json string using the JSON.stringify method and on the next page parse the string into an array using the JSON.parse method
Parent
{array.map((item) => (
<Grid key={item} item>
<Card
label={item.title}
onClick={() =>
router.push({
pathname: "/page",
query: {
title: item.title,
reviews: JSON.stringify(item.reviews),
},
})
}
/>
</Grid>
))}
Next Page
function index() {
const router = useRouter();
useEffect(() => {
if (!router.isReady) return;
console.log(router.query);
console.log(JSON.parse(router.query.reviews))
}, [router.isReady]);
const Reviews = () =>
<ReviewCard
reviewsList={JSON.parse(router.query.reviews)}
/>
);
return (
<Box>
<Typography>{router.query.title}</Typography>
<Reviews />
</Box>
);
}
export default index;
It happens when the array item is a JSON object.You can pass array but not array of objects.
router.push({
pathname: "/planterupdate",
query: {
title: "test",
textArray: ["1","2","3","4"],//will work
reviews: [{num:1},{num:2},{num:3},{num:4}],//will not work
numArray: [5,6,7,8],//will work
},
})
Sample log of query
{title: 'test', textArray: Array(4), reviews: Array(4), numArray: Array(4)}
numArray: (4) ['5', '6', '7', '8']
reviews: (4) ['', '', '', '']
textArray: (4) ['1', '2', '3', '4']
title: "test"

How would I be able to pass the product ID according to what product was selected?

I have a dynamic form where users can add multiple products. I wonder how I could save the selected products' id.
In the console.log(fields, "fields");, this is where I can see the saved product. So how can I save the selected product id as well?
Any help would be appreciated. Thank you.
Codesandbox: https://codesandbox.io/s/react-hook-form-wizard-form-from-reddit-with-data-ouy64e?file=/src/fieldArray.js:322-4143
const products = [
{
prodName: "Tumbler",
price: 1.5,
size: "500",
colorMap: { Black: 20, Pink: 10, Green: 5 },
id: "aRLMZkiSU7T0lcsPCSsV"
},
{
prodName: "Shirt",
price: 2.0,
size: "L",
colorMap: { Blue: 10, Black: 10 },
id: "uTHIR6OQFRuqP9Drft0e"
},
{
size: "200",
price: 2.0,
colorMap: { Green: 50, Red: 19, Black: 20 },
prodName: "Notebook",
id: "y9ECyZBKp2OBekmWym4M"
}
];
const options = products.map(
(object) =>
object.prodName +
" - " +
object.size +
`${object.cat === "CM" || object.cat === "ML" ? "- " + object.cat : ""}` +
" "
);
console.log(options, "options");
const FieldArray = ({ control, register, setValue, getValues }) => {
const { fields, append, remove, prepends } = useFieldArray({
control,
name: "order"
});
console.log(fields, "fields");
renderCount++;
return (
<div>
<ul>
{fields.map((item, index) => {
console.log(item);
return (
<li key={item.id}>
<Controller
control={control}
name={`order.${index}.product`}
render={({ field: { onChange, value = "", ...rest } }) => (
<Autocomplete
{...rest}
onInputChange={(e, newValue) => {
onChange(newValue);
console.log(newValue, "new value");
}}
inputValue={value}
options={products}
// isOptionEqualToValue={(option, value) =>
// option?.value === value?.value
// }
getOptionLabel={(option) =>
option.prodName + " " + option.size
}
// getOptionLabel={(option) => option?.label ?? ""}
renderInput={(params) => (
<TextField
{...params}
label="Product"
variant="outlined"
fullWidth
/>
)}
/>
)}
/>
);
})}
</div>
);
};
export default FieldArray;
Update
this is the submit button in step1.js
const onSubmit = (data) => {
// action(data);
console.log(data, "d");
const newOrder = [];
data.order.forEach(({ product, variation }) => {
const newVariantion = [];
variation.forEach(({ qty, color }) => {
newVariantion.push({ qty: parseInt(qty), color });
});
newOrder.push({ product, variation: newVariantion });
});
actions.updateAction(data);
console.log(newOrder, "new order");
navigate("/step2", newOrder);
};
Update:
How would I be able to push the product ID inside the newOrder where it matches the productID of the selected product?
Some development on answer from this question:
You can always add useState with a first product (save entire product, not just an id) and then manage everything through onChange:
import {useState} from 'react';
/*...something here...*/
const FieldArray = ({ control, register, setValue, getValues }) => {
const [prod, setProd] = useState({0: product[0]});
/*...something here...*/
{fields.map((item, index) => {
/*...something here...*/
<Autocomplete
onChange={(e, v)=>{console.log(v); setProd({...prod, [index]:v});}}
value={prod[index] || {}}
options={products}
/*...other stuff here...*/
Have a look at what is available in console.log(v) inside onChange.
Also check out difference between inputValue and value here.
Update
If you need multiple products to be saved - prod must be an object with key to represent "fields" item. For example, something like this {0: prod1, 1: prod3, 2: prod11}. Then for value use prod[index] and change setter appropriately. (I've edited code above for this case). There is multiple ways to do this - that's just one from top of my head.
Update 2:
I don't know what you want in onSubmit exactly, so here is an idea and you change it to what you want.
In Step1.onSubmit you can do something like that:
// forEach 2nd argument is an index (counter)
data.order.forEach(({ product, variation }, indx) => {
// some code
newOrder.push({ product, variation: newVariantion, prod: prod[indx] });
// more code
}

How to delete an object from array?

so i have a store that looks like this:
export class NoteStore {
notes = [
{
id: 1,
name: 'Shopping list',
children: [
{
id: 2,
name: 'Sweet',
items: [
{
id: 3,
name: 'Chocolate',
},
{
id: 4,
name: 'Fudge'
},
{
id: 5,
name: 'Cookies'
}
]
}
]
}
}
i am trying to remove an item from items by pressing a button. so far i have managed to make a remove function that does not throw an error but it doesn't do anything. i have spent so much time with this but i just can't figure out what i need to do to get the removing working properly. this is what i'm working with:
const store = useContext(NoteStore);
function removeItems(id) {
store.notes = store.notes.filter(item => item !== id);
}
//Prints notes content to the screen
const NotesArray = () => {
return store.notes[0].children[0].items.map((item) =>
<View key={item.id} style={{ flexDirection: 'row' }}>
<Text style={styles.note}>{item.name}</Text>
<TouchableOpacity style={{ justifyContent:'center', paddingLeft:5 }} onPress={() => {
removeItems(item.id);
}}>
<Text>
<Icon name={noteDeleteMode ? 'close-circle-outline': null} style={styles.deleteIcon} />
</Text>
</TouchableOpacity>
</View>
)
};
You currently comparing the id with the whole item, not with it's id.
Replace
store.notes = store.notes.filter(item => item !== id);
with
store.notes = store.notes.filter(item => item.id !== id);
(item.id instead of just item)
Probably the issue is that you're saving the reference to the old store.notes somewhere. Fortunately, you don't need to create a new array; you can just modify the old one. Use this:
function removeItems(id) {
store.notes.splice(store.notes.findIndex(item => item.id === id), 1);
}

How to create a dynamic SectionList?

I am trying to build a section list where the list header is the name of a class (like a school class) and the section list is the assignments that are assigned to that class.
This is my section list component:
<SectionList
sections={classData}
keyExtractor={item => item.class.id}
renderSectionHeader={({ section: classProps }) => {
return (
<Text style={styles.textStyle}>{classProps.name}</Text>
);
}}
renderItem={({ item }) => {
return (
<HomeworkItem
homeworkItem={item}
deleteHomework={() => this.props.removeHomework(item)}
/>
);
}}
/>
This is the code to get classData:
const classData = [];
for (let x = 0; x < this.props.classes.length; x++) {
classData.push({
classProps: this.props.classes[x],
assignments: this.filterAssignments(this.props.classes[x], this.props.homework)
});
}
And the filter assignments function is this:
filterAssignments(c, hw) {
return hw.filter(item => item.className === c.name);
}
I am receiving an error that items.length is undefined at the beginning of the list. The data appears to be working. If anyone knows how I can do this correctly your help would be greatly appreciated.
The react native docs state the following format:
const DATA = [
{
title: 'Main dishes',
data: ['Pizza', 'Burger', 'Risotto'],
},
{
title: 'Sides',
data: ['French Fries', 'Onion Rings', 'Fried Shrimps'],
}
];
this format can be achieved with the following loop:
const classData = this.props.classes.map(item => ({
title: item.name
data: this.filterAssignments(item, this.props.homework)
}));
the classData can now be passed to <SectionList as such:
<SectionList
sections={classData}

Categories