how to handle multiple refs inside a map function - javascript

I need to target all the components inside map function, but I am only getting the last component inside it.
products?.map((product, i) => (
<div
key={product.id}
className="product__card"
onMouseEnter={() => sliderRef.current.slickNext()}
onMouseLeave={() => sliderRef.current.slickPause()}
>
<StyledSlider {...settings} ref={sliderRef}>
{product.assets.map(({ url, id, filename }) => (
<div className="product__image__container" key={id}>
<img src={url} alt={filename} />
</div>
))}
</StyledSlider>
<div className="product__content__container">
<h3 className="slim__heading">{valueChopper(product.name, 23)}</h3>
<p contentEditable="true" dangerouslySetInnerHTML={{ __html: valueChopper(product.description, 30) }} />
<h6 className="slim__heading">Rs. {product.price.formatted}</h6>
</div>
</div>
)

The simplest way is to use one ref to target multiple elements. See the example below. I've modified your code to make it work.
const sliderRef = useRef([]);
products?.map((product, i) => (
<div
key={product.id}
className="product__card"
onMouseEnter={() => sliderRef.current[i].slickNext()}
onMouseLeave={() => sliderRef.current[i].slickPause()}
>
<StyledSlider {...settings} ref={el => sliderRef.current[i] = el}>
{product.assets.map(({ url, id, filename }) => (
<div className="product__image__container" key={id}>
<img src={url} alt={filename} />
</div>
))}
</StyledSlider>
<div className="product__content__container">
<h3 className="slim__heading">{valueChopper(product.name, 23)}</h3>
<p contentEditable="true" dangerouslySetInnerHTML={{ __html:valueChopper(product.description, 30) }} />
<h6 className="slim__heading">Rs. {product.price.formatted}</h6>
</div>
</div>
)

Related

React, conditional rendering inside map method

I have the following code and I want to apply a conditional rendering, because the index 0 does not exist. I don't want to render the information for this particular index. How can I do it?
return (
<section>
{pokemonCards.map((pokemon, index) =>
<div key={index}>
<img
src={`https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/${index}.png`}
alt={pokemon.name}/>
<p>{pokemon.name}</p>
</div>
)}
<button
className="get-more-button"
onClick={getMorePokemons}>Get more pokemons
</button>
</section>
)
This should work:
return (
<section>
{pokemonCards.map((pokemon, index) =>
{index === 0 ? null : <div key={index}>
<img
src={`https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/${index}.png`}
alt={pokemon.name}/>
<p>{pokemon.name}</p>
</div>
)}
<button
className="get-more-button"
onClick={getMorePokemons}>Get more pokemons
</button>}
</section>
)
You could do as below if you do not want to render an empty div for the nonexisting pokemon:
return (
<section>
{pokemonCards.map(
(pokemon, index) =>
index !== 0 && (
<div key={index}>
<img
src={`https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/${index}.png`}
alt={pokemon.name}
/>
<p>{pokemon.name}</p>
</div>
)
)}
<button className="get-more-button" onClick={getMorePokemons}>
Get more pokemons
</button>
</section>
);

React change icon onClick from loop

I included an Icon while mapping an array and I need to change the icon on that specific iteration onclick, I also want to render the test results when the icon is clicked.
I'm not sure how to get a unique value that is specific to this iteration that will handle this change or is my structure all wrong?
** went to sleep found solution = encapsulation **
map the component so the boolean variable is local scoped to that iteration.
shared solution below
const Students = ({students}) => {
const [showTests, setShowTests] = useState(false);
return (
<div className="wrapper" key={student.id}>
<div className="img-container">
<img src={student.pic} alt="student" />
</div>
<div className="content">
<h1>
{student.firstName.toUpperCase()} {student.lastName.toUpperCase()}
</h1>
<p>Email: {student.email}</p>
<p>Company: {student.company}</p>
<p>Skill: {student.skill}</p>
<p>Average: {averageGrade(student.grades)}%</p>
{showTests && <TestResults results={student.tests} />}
</div>
{showTests ? (
<FontAwesomeIcon
onClick={() => setShowTests(!showTests)}
className="faIcon"
icon={faMinus}
/>
) : (
<FontAwesomeIcon
onClick={() => setShowTests(!showTests)}
className="faIcon"
icon={faPlus}
/>
)}
</div>
);
}
const Main = () => {
return (
<div className="main" id="main">
{filteredStudents.map(student => (
//each iteration is a unique component
<Students student={student} />
))}
</div>
);
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
Thanks ahead of time for any suggestions
The return must always have a top-level tag.
const Students = ({ students }) => {
return (
<>
{students.map((student, i) => (
<div className="wrapper" key={student.id}>
<div className="img-container">
<img src={student.pic} alt="student" />
</div>
<div className="content">
<h1>
{student.firstName.toUpperCase()}{" "}
{student.lastName.toUpperCase()}
</h1>
<p>Email: {student.email}</p>
<p>Company: {student.company}</p>
<p>Skill: {student.skill}</p>
<p>Average: {averageGrade(student.grades)}%</p>
{faMinus && <TestResults results={student.tests} />}
</div>
<FontAwesomeIcon
id={i}
onClick={(e) => handleTests(e.target.id)}
className="faIcon"
icon={current ? faPlus : faMinus}
/>
</div>
))}
</>
);
};
Let me know if this is what you're thinking. Just store the index in a state variable and compare against it. When they click change the index to the one they clicked on.
const Students = ({students}) => {
const [selectedIndex, setSelectedIndex] = useState(0)
function handleTests(id, index) { //NEW
//Your current code
setSelectedIndex(index) //NEW
}
return (
{students.map((student, i) => (
<div className="wrapper" key={student.id}>
<div className="img-container">
<img src={student.pic} alt="student" />
</div>
<div className="content">
<h1>
{student.firstName.toUpperCase()} {student.lastName.toUpperCase()}
</h1>
<p>Email: {student.email}</p>
<p>Company: {student.company}</p>
<p>Skill: {student.skill}</p>
<p>Average: {averageGrade(student.grades)}%</p>
{selectedIndex == i && <TestResults results={student.tests} />} //NEW
</div>
<FontAwesomeIcon
id={i}
onClick={e => handleTests(e.target.id, i)} //NEW
className="faIcon"
icon={selectedIndex == i ? faPlus : faMinus}
/>
</div>
))}
)
}

My code gives "TypeError: Cannot read property 'map' of undefined" in ReactJs

This piece of code giving me type error: "TypeError: Cannot read property 'map' of undefined"
I don't understand what is the problem here.
const ShowMainData = ({ name, rating, summary, tags, image }) => {
return (
<div>
<img src={image ? image.original : IMG_PLACEHOLDER} alt="show-cover" />
<div>
<div>
<h1>{name}</h1>
<div>
<Star />
<span>{rating.average || 'N/A'}</span>
</div>
</div>
<div dangerouslySetInnerHTML={{ __html: summary }} />
<div>
Tags:{' '}
<div>
{tags.map((tag, i) => (
<span key={i}>{tag}</span>
))}
</div>
</div>
</div>
</div>
);
}
I added?. before map:
const ShowMainData = ({ name, rating, summary, tags, image }) => {
return (
<div>
<img src={image ? image.original : IMG_PLACEHOLDER} alt="show-cover" />
<div>
<div>
<h1>{name}</h1>
<div>
<Star />
<span>{rating.average || 'N/A'}</span>
</div>
</div>
<div dangerouslySetInnerHTML={{ __html: summary }} />
<div>
Tags:{' '}
<div>
{tags?.map((tag, i) => (
<span key={i}>{tag}</span>
))}
</div>
</div>
</div>
</div>
);
}
or you can check if tags is not undifined by doing this:
const ShowMainData = ({ name, rating, summary, tags, image }) => {
return (
<div>
<img src={image ? image.original : IMG_PLACEHOLDER} alt="show-cover" />
<div>
<div>
<h1>{name}</h1>
<div>
<Star />
<span>{rating.average || 'N/A'}</span>
</div>
</div>
<div dangerouslySetInnerHTML={{ __html: summary }} />
<div>
Tags:{' '}
<div>
{tags && tags.map((tag, i) => (
<span key={i}>{tag}</span>
))}
</div>
</div>
</div>
</div>
);
}
Issue happens because your tags variable is undefined, you may not be passing it as a prop. Safer alternative is:
const ShowMainData = ({ name, rating, summary, tags, image }) => {
return (
<div>
<img src={image ? image.original : IMG_PLACEHOLDER} alt="show-cover" />
<div>
<div>
<h1>{name}</h1>
<div>
<Star />
<span>{rating.average || 'N/A'}</span>
</div>
</div>
<div dangerouslySetInnerHTML={{ __html: summary }} />
<div>
Tags:{' '}
<div>
{tags&& tags.length> 0 ? tags.map((tag, i) => (
<span key={i}>{tag}</span>
)):null}
</div>
</div>
</div>
</div>
);
}

React trigger only one element in array

I am in the process of making a comment system like the one on youtube. In my implementation, when I click on modify, all comments are now inputs but only the value of the selected input will be modified. how to trigger only the element i clicked.
as you can see it triggers all the array elements
function App() {
const [open, setOpen] = useState(false);
return (
<div className="container mt-5">
<MDBRow>
{data &&
data.map((item) => (
<MDBCol md="7" lg="7" key={item.id} className="mb-4">
{!open && (
<>
<div className="font-weight-bolder float-left pr-2">
{item.name}
</div>
<div className="float-right pr-2">
<button
onClick={() => {
setOpen(true);
}}
>
Modifier
</button>
</div>
</>
)}
{open && (
<UpdateData
id={item.id}
name={item.name}
onAbort={() => setOpen(false)}
submit={() => setOpen(false)}
/>
)}
</MDBCol>
))}
</MDBRow>
</div>
);
}
export const UpdateData = ({ name, id, onAbort, submit }) => {
const formik = useFormik({
initialValues: {
id: id,
name: name,
},
onSubmit: async (values) => {
console.log(values);
submit();
},
});
return (
<form onSubmit={formik.handleSubmit}>
<MDBInput
value={formik.values.name}
name="name"
onChange={formik.handleChange}
/>
<div className="float-right">
<span onClick={onAbort} className="text-capitalize grey-text">
Cancel
</span>
<button type="submit">confirm</button>
</div>
</form>
);
};
And this is the sandbox
that i have created
To trigger only one element to be clicked you have to pass the index
function App() {
const [open, setOpen] = useState(false);
const [selectedRow, setSelectedRow] = useState(undefined);
const onSelectedRow = (index) => {
setSelectedRow(index);
setOpen(true);
}
return (
<div className="container mt-5">
<MDBRow>
{data &&
// here you will get the index
data.map((item,index) => (
<MDBCol md="7" lg="7" key={item.id} className="mb-4">
{!open && (
<>
<div className="font-weight-bolder float-left pr-2">
{item.name}
</div>
<div className="float-right pr-2">
// Now onClick pass the index of selected row to onSelectedRow
<button
onClick={() =>onSelectedRow(index)}
>
Modifier
</button>
</div>
</>
)}
// here add condition to open selected row
{ (open === true && selectedRow === index) ? (
<UpdateData
id={item.id}
name={item.name}
onAbort={() => setOpen(false)}
submit={() => setOpen(false)}
/>
) : null
}
</MDBCol>
))}
</MDBRow>
</div>
);
}
Sandbox code https://codesandbox.io/s/youthful-wave-k4eih?file=/src/App.js
If you have any queries comment below!
instead of having a false default value in your hook you should have a unique key for each element. By default, it applies to all elements.

React map - don't return item if missing

I'm using map in react to show a feed of facebook posts, but i don't want the post to render if the message field is missing from the json data.
If I do this I get an error ('if' is an unexpected token) and my project won't build
return (
<Slider
{...Settings}>
{postsAvailable && posts.map((post, index) => (
if (!post.message) return null
return (<div key={post.id}>
{ index === 0 && <Item /> }
{ index > 0 && <div className='item'>
<img data-original={post.full_picture} className='img-responsive' />
<div className={`facebook-content slide-${post.id}`}>
<p className='text'>{post.message}</p>
</div>
</div> }
</div>)
))}
</Slider>
)
You can use filter first:
posts.filter((post) => post.message).map((post, index) => (...
Although, if possible you should filter when you get the posts, and not in the render function. Do as little as possible in the render function - for a good overview, and for performance.
You are not giving your function given to map a body. Change () to {} and it will work as expected.
return (
<Slider {...Settings}>
{postsAvailable &&
posts.map((post, index) => {
if (!post.message) return null;
return (
<div key={post.id}>
{index === 0 && <Item />}
{index > 0 && (
<div className="item">
<img
data-original={post.full_picture}
className="img-responsive"
/>
<div className={`facebook-content slide-${post.id}`}>
<p className="text">{post.message}</p>
</div>
</div>
)}
</div>
);
})}
</Slider>
);
Alternatively you could filter out all the posts that don't have a message first and then map those that does.
return (
<Slider {...Settings}>
{postsAvailable &&
posts
.filter(post => post.message)
.map((post, index) => (
<div key={post.id}>
{index === 0 && <Item />}
{index > 0 && (
<div className="item">
<img
data-original={post.full_picture}
className="img-responsive"
/>
<div className={`facebook-content slide-${post.id}`}>
<p className="text">{post.message}</p>
</div>
</div>
)}
</div>
))}
</Slider>
);

Categories

Resources