Currently, my react/nextjs dynamic list is not updating correctly, I've given an array to map so it'll show a new row on frame update since it's stored on useState, Usually, the issue is with the key of the list that is not unique, but my key is unique
Heres my code
const [allSelectedMaterials, setAllSelectedMaterials] = useState([]) // This variable stores a javascript object into the array
{console.log('-- Updated --')}
{allSelectedMaterials.map((material, i) => {
const key = Object.keys(material).toString()
console.log(`${i} - [${key}] ${material}`)
return (
<div key={key}>
<Row className='align-items-center'>
<Col xs='auto'>
<h6>{material[key].name}</h6>
</Col>
<Col>
<Button variant='danger' className='mb-1' onClick={() => handleDeleteMaterial(key)}>
Remove
</Button>
</Col>
</Row>
<InputGroup>
<InputGroup.Text>
<Image src={material[key].image} className={`${styles.allMaterialsImage}`} />
</InputGroup.Text>
<Form.Control type='number' min={1} ref={(e) => (selectedMaterials.current[i] = e)} required />
</InputGroup>
<div className='mt-1' />
</div>
)
})}
The problem is after I added the first item on the list and adding a new one it won't update the view, here's the console output
And here's me adding a second entry to the list
It clearly says on the log that the array (stored in useState) is updated but it's not changing the view it's still the same as the previous one. But if I reupdate the frame by updating any useState variable it updated the list
Update:
Here's my code for adding new material
(loadedMaterials is just a list of materials that is retrieved from an API)
const handleAddSelectedMaterial = () => {
loadedMaterials.map((material) => {
const key = Object.keys(material)
if (key == currentSelectedMaterial) {
let _material
if (allSelectedMaterials.length > 0) _material = allSelectedMaterials
else _material = []
_material.push({ [material[key].id]: material[key] })
setAllSelectedMaterials(_material)
}
})
}
Try replacing
from
_material.push({ [material[key].id]: material[key] })
setAllSelectedMaterials(_material)
to
_material.push({ [material[key].id]: material[key] })
setAllSelectedMaterials([..._material])
Thank you everyone for the input and responses, but I've managed to solve this issue by redoing how the form & dynamic list work and it's working perfectly now.
If anyone is wondering I just basically follow this person implementation on a dynamic list
https://www.cluemediator.com/add-or-remove-input-fields-dynamically-with-reactjs
Related
I'm developing with React.js and below is a simplified version of my component. As you can see, when I click the button state(number) would get a number. And here is what I want, make div tags as much as a number in state(number) in the form tag(which its class name is 'b').
Could you tell me how to make this possible? Thanks for reading. Your help will be appreciated.
//state
const[number,setNumber] = useState('')
//function
const appendChilddiv =(e)=>{
e.preventDefault()
setNumber('')
}
<div>
<form className="a" onSubmit={appendChilddiv}>
<input
value={number}
onChange={(e)=>setNumber(e.target.value)}
type="number"/>
<button type="submit">submit</button>
</form>
<div>
<form className="b">
</form>
</div>
</div>
I've created a codesandbox which includes the following code, previously you were storing the value as a string, it'd be best to store it as number so you can use that to map out, which I do via the Array() constructor (this creates an array of a fixed length, in this case the size of the divCount state - when we update the state by changing the input value this creates a re-render and thats why the mapping is updated with the new value)
import "./styles.css";
import * as React from "react";
export default function App() {
//state
const [divCount, setDivCount] = React.useState(0);
//function
const appendChilddiv = (e) => {
e.preventDefault();
// Submit your form info etc.
setDivCount(0);
};
return (
<div>
<form className="a" onSubmit={appendChilddiv}>
<input
value={divCount}
onChange={(e) => setDivCount(Number(e.target.value))}
type="number"
/>
<button type="submit">submit</button>
</form>
<div>
<form className="b">
{Array(divCount)
.fill(0)
.map((x, idx) => (
<div key={idx}>Div: {idx + 1}</div>
))}
</form>
</div>
</div>
);
}
You can map over an array whose length is same as that entered in input & create divs (if number entered is valid):
{!isNaN(number) &&
parseInt(number, 10) > 0 &&
Array(parseInt(number, 10))
.fill(0)
.map((_, idx) => <div key={idx}>Hey!</div>)
}
isNaN checks is number is valid
parseInt(number, 10) converts string into number,
Array(n) creates a new array with n elements (all empty) (try console logging Array(5)) - so you need to fill it
.fill(n) fill the array (make each element n)
and map is used to render different elements from existing things
So, in this way, you can achieve the mentioned result.
Here's a link to working Code Sandbox for your reference
You can do the following.
First thing it does is creates a new array of some length number.
Next, it fills that array with undefined, because creating new arrays like this doesn't really create an array of that length.
Lastly, we map over this array, we use the index as our key.
We return an empty div for each item in the array.
Note, using an index as a key isn't the best idea. In general it should be something as unique as possible. If you have data you can use that is unique, then you should use that as a key.
return new Array(number).fill(undefined).map((_, key) => <div key={key}></div>);
You can even do it without 'Submit' button. See the codesandbox link and the code snippet below:
import "./styles.css";
import * as React from 'react';
import { useState } from 'react';
export default function App() {
const [divTags, setDivTags] = useState([])
const appendChilddiv = (e) => {
const numberAsString = e.target.value;
let numberDivTags;
if (numberAsString.length === 0) {
numberDivTags = 0
} else {
numberDivTags = parseInt(numberAsString, 10)
}
setDivTags([...Array(numberDivTags)])
console.log(divTags)
}
return (
<>
<form className="a">
<input type="number" onChange={appendChilddiv}/>
</form>
<form className="b">
{divTags.map((e, i) => {
return <p key={i}>Div number {i}</p>
})}
</form>
</>
);
}
I am learning Javascript / React and I have been doing this react project to make memes and I have all the data regarding the memes in an array of objects but I am stuck now for days without getting it to do what I want.
Inside each object is a name with a value, an ID with a value, A blank with the image URLs. I want to display the names to the user in a dropdown and I want to get access to the ID and blank value of the selected name from the dropdown so that I can feed the image to the user.
I have tried extracting the names and the IDs into separate strings and check for a matching word but sometimes the string value of the ID is not even included in the string values of the name, so that didn't work at all and now I did it like a kind of a hack with a counter that increases each time the onChange function is called and I used the number to feed the image according to the index matching the number but this gives me the wrong image anytime that a user decides to choose image randomly. This may sound a bit abstract so I have included some screenshots here and my code with a link to a code sandbox where all the program lives and hoping to get the help that I need, please.
Hers is the Code Snippets;
export default function MyComponent() {
const [bulkImageArray, setBulkImageArray] = useState([]);
const [counter, setCounter] = useState(1);
const [imageUrls, setImageUrls] = useState();
const [imagesNames, setImagesNames] = useState();
// This is the function that should get me access to the object id value when any value is selected.
function handleChange(event) {
setCounter(counter + 1);
console.log('You just selected: ' + event.target.value);
// setImagesNames(event.target.value);
setImagesNames(bulkImageArray[counter].name);
setImageUrls(bulkImageArray[counter].blank);
console.log(typeof event.target.value);
}
return (
<div>
<select onChange={handleChange}>
{bulkImageArray.map((item) => (
<option key={item.id}>
{/* {item.id} */}
{item.name}
</option>
))}
</select>
<img src={imageUrls} alt={imagesNames} />
<div>
{bulkImageArray.map((item) => (
<img
style={{
width: '400px',
height: '400px',
cursor: 'pointer',
}}
key={item.id}
alt={item.name}
src={item.blank}
/>
))}
</div>
</div>
);
}
}
screenshots:
Array of Objects Structure:
Single Object Structure:
Function Tigered on select:
The dropdown for users to select the image:
I don't know if all this information is enough but this is the code sandbox to the complete project.
Thanks in advance.
You should add value={item.id} to option, so that select onChange can select the value to id, then use the id to find the selected item.
I am a beginner and I am trying to create a recipe app. I managed to set up an API that gives an Array of 10 objects each time I search for a meal like so.
I access the elements of each recipe using a map
{recipes.map(recipe =>(
<RecipeCard
key={recipe.recipe.label}
title ={recipe.recipe.label}
calories={recipe.recipe.calories}
image={recipe.recipe.image}
ingredients={recipe.recipe.ingredients}
/>
))}
Here is also my Const Recipe Card just for some more context. It functions fine.
const RecipeCard = ({title, calories, image, ingredients}) => {
const round = Math.round(calories);
return(
<div className = {style.recipe}>
<h1 >{title}</h1>
<ol className = {style.list}>
{ingredients.map(ingredient => (
<li>{ingredient.text}</li>
))}
</ol>
<p>calories: {round} </p>
<img className = {style.image} src={image} alt=""/>
<button>Add to Favorites</button>
</div>
)
}
I currently only want to access the information from the first array, but whenever I change recipes.map to recipes[0] it says that function does not exist. How should I go about accessing individual elements from the arrays provided from the API?
You can use .slice(0, 1) to create a shallow copy (a new array with just first element):
{recipes.slice(0, 1).map(recipe =>(
<RecipeCard
key={recipe.recipe.label}
title ={recipe.recipe.label}
calories={recipe.recipe.calories}
image={recipe.recipe.image}
ingredients={recipe.recipe.ingredients}
/>
))}
Or use destructuring:
const [recipe] = recipes // before "return"
// ....
<RecipeCard
key={recipe.recipe.label}
title ={recipe.recipe.label}
calories={recipe.recipe.calories}
image={recipe.recipe.image}
ingredients={recipe.recipe.ingredients}
/>
Or use index:
<RecipeCard
key={recipes[0]?.recipe.label}
title ={recipes[0]?.recipe.label}
calories={recipes[0]?.recipe.calories}
image={recipes[0]?.recipe.image}
ingredients={recipes[0]?.recipe.ingredients}
/>
The ?. is called optional chaining, you can use it to avoid error like Can't Read property of undefined, i.e. when the first element is undefined and you try to read its properties.
I'm new in React and i'm learning to use it with hooks.
I tried to put dynamics inputs which works so far but I have a display problem.
If I delete the last input, no problem but if I delete others inputs than the last one then the correct values does not show on the browser.
For example I add 2 inputs.
first one titled "one" and second titled "two".
If I delete "one", the remain input on screen shows "one" or it should show "two".
However, in the array where I collect the inputs infos, there is the correct remaining input.
(see screenshot).
How can I do to show the correct title in the input on the browser ?
const [titleSelected, setTitleSelected] = useState(true);
const [questionType, setQuestionType] = useState([]);
const [questionsTitle, setQuestionsTitle] = useState([]);
{questionType ? (
questionType.map((questions, index) => {
return (
<div key={index} className="questions">
<div className={questions === "texte" ? "orange" : "red"}>
<span>{questions === "texte" ? "1" : "2"}</span>
<img src={Minus} alt="" />
<img
src={questions === "texte" ? FileWhite : StarWhite}
alt=""
/>
</div>
<input
type="text"
placeholder="Ecrivez votre question"
onChange={(event) => {
let tab = [...questionsTitle];
// si index de l'objet existe on update index de l'objet par index sinon on push le nouvel objet
let tabIndex = tab.findIndex(
(element) => element.index === index
);
if (tabIndex !== -1) {
tab[tabIndex].type = questionType[index];
tab[tabIndex].title = event.target.value;
} else {
tab.push({
index: index,
type: questionType[index],
title: event.target.value,
});
}
setQuestionsTitle(tab);
}}
></input>
<div>
<img src={ChevronUp} alt="move up" />
<img src={ChevronDown} alt="move down" />
<img
src={SmallTrash}
alt="delete question"
onClick={() => {
let tab = [...questionType];
tab.splice(index, 1);
setQuestionType(tab);
let tabTitle = [...questionsTitle];
tabTitle.splice(index, 1);
setQuestionsTitle(tabTitle);
}}
/>
</div>
</div>
);
})
) : (
<div></div>
)}
<div className="questionType">
<div
className="addText"
onClick={() => {
let tab = [...questionType];
tab.push("texte");
setQuestionType(tab);
}}
>
<img src={File} alt="" />
<p>Ajouter une question "Texte"</p>
</div>
<div
className="addNote"
onClick={() => {
let tab = [...questionType];
tab.push("note");
setQuestionType(tab);
}}
>
<img src={Star} alt="" />
<p>Ajouter une question "Note"</p>
</div>
</div>
screenshot
Issues
You are mutating the array you are mapping so don't use the array index as the react key. If you remove the ith element all the elements shift up, but the keys remain unchanged and react bails on rerendering.
Lists & Keys
questionType.map((questions, index) => (
<div key={index} className="questions">
...
</div>
)
The array index as a React key doesn't "stick" to the element object it "identifies".
You've also some state object mutation occurring.
tab[tabIndex].type = questionType[index];
tab[tabIndex].title = event.target.value;
This is masked by the shallow copy let tab = [...questionsTitle];.
Solution
Select a react key that is unique among siblings and related to the elements, like an id.
Since you enclose the index when adding new elements to the array I think you can resolve the key issue by simply using the saved index.
questionType.map((questions, index) => (
<div key={questionsTitle[index].index} className="questions">
...
</div>
)
This may be a little confusing so I suggest changing the property to id.
questionType.map((questions, index) => (
<div key={questionsTitle[index].id} className="questions">
...
<input
...
onChange={event => {
...
tab.push({
id: index,
type: questionType[index],
title: event.target.value,
});
...
}}
/>
...
</div>
)
A further suggestion is to avoid using the array index at all. The following code can quickly get out of sync when the array index being mapped doesn't align to the saved index in the element.
let tabIndex = tab.findIndex(element => element.index === index);
Generate id's for your elements and use that to determine if elements should be updated or appended. When updating make sure to shallow copy the array and then also shallow copy the element object being updated.
I was learning React and I came to a point which created confusion. Everywhere I was using props while writing Function components.
I always use props.profile and it works fine. But in one code component, I had to write
const profiles=props; and it worked fine.
I tried using const profiles=props.profile; and also I tried using inside return in 'Card' function component
{props.profile.avatar_url} but both of them failed
Below is my code which works fine
const Card=(props)=>{
const profiles=props; //This I dont understand
return(
<div>
<div>
<img src={profiles.avatar_url} width="75px" alt="profile pic"/>
</div>
<div>
<div>{profiles.name}</div>
<div>{profiles.company}</div>
</div>
</div>
);
}
const CardList=(props)=>{
return(
<div>
{testDataArr.map(profile=><Card {...profile}/>)}
</div>
);
}
Can someone please help me understand why I can't use const profiles=props.profile?
What are the other ways to achieve the correct result?
Your testDataArr might be this,
testDataArr = [{avatar_url:"",name:"",company:""},{avatar_url:"",name:"",company:""},{avatar_url:"",name:"",company:""}]
Now when you do this,
{testDataArr.map(profile=><Card {...profile}/>)}
here profile = {avatar_url:"",name:"",company:""},
and when you do,
<Card {...profile}/>
is equivalent to,
<Card avatar_url="" name="" company=""/>
In child component, when you do this,
const profiles=props;
here props = {avatar_url:"",name:"",company:""}
So you can access it's values,
props.avatar_url
props.name
props.company
But when you do this,
const profiles=props.profile
profile key is not present in {avatar_url:"",name:"",company:""} object and it fails.
OK. Here is the issue, the props object does not contain a profile attribute, but IT IS the profile attribute. Becouse you are spreading the profile variable when you render the Card element (in the CardList), you basically are writing:
<Card avatarUrl={profile.avatarUrl} comapny={profile.comany} />
Instead, you should do
<Card profile={profile} />
and then in your Card component access the data this way
const Card = (props) => {
const profile = props.profile
}
or even simpler
const Card = ({profile}) => {
return <div>{profile.comany}</div>
}