React conditional styling in a map function problem - javascript

I just want to show toggled item. But all map items showing up. Basically this is the result I'm getting from onclick. I think i need to give index or id to each item but i don't know how to do it. i gave id to each question didn't work.
App.js.
import "./App.css";
import React, { useState, useEffect } from "react";
import bg from "./images/bg-pattern-desktop.svg";
import bg1 from "./images/illustration-box-desktop.svg";
import bg2 from "./images/illustration-woman-online-desktop.svg";
import { data } from "./data";
import Faq from "./Faq";
function App() {
const [db, setDb] = useState(data);
const [toggle, setToggle] = useState(false);
useEffect(() => {
console.log(db);
}, []);
return (
<>
<div className="container">
<div className="container-md">
<div className="faq">
<img src={bg} className="bg" />
<img src={bg1} className="bg1" />
<img src={bg2} className="bg2" />
<div className="card">
<h1>FAQ</h1>
<div className="info">
{db.map((dat) => (
<Faq
toggle={toggle}
setToggle={setToggle}
title={dat.title}
desc={dat.desc}
key={dat.id}
id={dat.id}
/>
))}
</div>
</div>
</div>
</div>
</div>
</>
);
}
export default App;
(map coming from simple data.js file that I created. it includes just id title desc.)
Faq.js
import React from "react";
import arrow from "./images/icon-arrow-down.svg";
const Faq = ({ toggle, setToggle, title, desc, id }) => {
return (
<>
{" "}
<div className="question" onClick={() => setToggle(!toggle)}>
<p>{title}</p>
<img src={arrow} className={toggle ? "ikon aktif" : "ikon"} />
</div>
<p className="answer border">{toggle ? <>{desc}</> : ""}</p>
</>
);
};
export default Faq;

You need to store the index value of the toggle item.
You can modify the code with only 2 lines with the existing codebase.
import "./App.css";
import React, { useState, useEffect } from "react";
import bg from "./images/bg-pattern-desktop.svg";
import bg1 from "./images/illustration-box-desktop.svg";
import bg2 from "./images/illustration-woman-online-desktop.svg";
import { data } from "./data";
import Faq from "./Faq";
function App() {
const [db, setDb] = useState(data);
const [toggle, setToggle] = useState(-1); //Modify Here
useEffect(() => {
console.log(db);
}, []);
return (
<>
<div className="container">
<div className="container-md">
<div className="faq">
<img src={bg} className="bg" />
<img src={bg1} className="bg1" />
<img src={bg2} className="bg2" />
<div className="card">
<h1>FAQ</h1>
<div className="info">
{db.map((dat, index) => ( //Modify Here
<Faq
toggle={index === toggle} //Modify Here
setToggle={() => setToggle(index)} //Modify Here
title={dat.title}
desc={dat.desc}
key={dat.id}
id={dat.id}
/>
))}
</div>
</div>
</div>
</div>
</div>
</>
);
}
export default App;
import React from "react";
import arrow from "./images/icon-arrow-down.svg";
const Faq = ({ toggle, setToggle, title, desc, id }) => {
return (
<>
{" "}
<div className="question" onClick={setToggle}>
<p>{title}</p>
<img src={arrow} className={toggle ? "ikon aktif" : "ikon"} />
</div>
<p className="answer border">{toggle ? <>{desc}</> : ""}</p>
</>
);
};
export default Faq;

You will need state for each toggle. Here is a minimal verifiable example. Run the code below and click ⭕️ to toggle an item open. Click ❌ to close it.
function App({ faq = [] }) {
const [toggles, setToggles] = React.useState({})
const getToggle = key =>
Boolean(toggles[key])
const setToggle = key => event =>
setToggles({...toggles, [key]: !getToggle(key) })
return faq.map((props, key) =>
<Faq key={key} {...props} open={getToggle(key)} toggle={setToggle(key)} />
)
}
function Faq({ question, answer, open, toggle }) {
return <div>
<p>
{question}
<button onClick={toggle} children={open ? "❌" : "⭕️"} />
</p>
{open && <p>{answer}</p>}
</div>
}
const faq = [
{question: "hello", answer: "world"},
{question: "eat", answer: "vegetables"}
]
ReactDOM.render(<App faq={faq} />, document.querySelector("#app"))
p { border: 1px solid gray; padding: 0.5rem; }
p ~ p { margin-top: -1rem; }
button { float: right; }
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.14.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.14.0/umd/react-dom.production.min.js"></script>
<div id="app"></div>

Instead of doing this (in App component):
const [db, setDb] = useState(data);
const [toggle, setToggle] = useState(false);
you can write an useState hook like below to combine the two hooks and assign an isOpened property for each Faq element:
const [db, setDb] = useState(data.map(value=>{return {...value, isOpened:false}}));
and then right here you can do this (as the child of <div className="info">):
{db.map((dat, index) => (
<Faq
toggle={dat.isOpened}
setToggle={() => toggleById(dat.id)}
title={dat.title}
desc={dat.desc}
key={dat.id}
id={dat.id}
/>
))}
Also you need to declare toggleById function in App component:
const toggleById = (id) => {
const newDb = db.map(dat=>{
if(dat.id==id){
return {...dat,isOpened:!dat.isOpened}
}
return dat;
});
setDb(newDb);
}
and since setToggle prop of Faq, calls toggleById by its defined parameter, there is no need to do this in Faq component:
<div className="question" onClick={() => setToggle(!toggle)}>
you can simply write:
<div className="question" onClick={setToggle}>

Related

passing react hooks into component

I'm new to React. I'm trying to add additional functionality of deleting the record from the list by setting the value.
here is my App.js
import React, { useState } from "react";
import data from "./data";
import List from "./List";
function App() {
const [movies, setMovie] = useState(data);
return (
<main>
<section className='container'>
<h3>{movies.length} Movies to Watch</h3>
<List movies={movies} setMovie />
<button onClick={() => setMovie([])}>clear all</button>
</section>
</main>
);
}
export default App;
In List.js, Im trying to delete the record when clicking on Watched button. Can I call setMovie inside the List component? is it a correct way?
List.js
import React from "react";
const List = ({ movies }, setMovie) => {
return (
<>
{movies.map((movie) => {
const { id, name, year, image } = movie;
return (
<article key={id} className='person'>
<img src={image} alt={name} />
<div>
<h4>{name}</h4>
<button
className='btn'
onClick={(id) =>
setMovie(movies.filter((movie) => movie.id !== id))
}
>
watched
</button>
<p>{year}</p>
</div>
</article>
);
})}
</>
);
};
export default List;
You have two mistakes in your code. First:
<List movies={movies} setMovie />
This shorthand assigns a value of true to setMovie. To assign the setMovie function to it, you must instead do:
<List movies={movies} setMovie={setMovie} />
And secondly this:
const List = ({ movies }, setMovie) => {
Should be this:
const List = ({ movies, setMovie }) => {
try:
<List movies={movies} setMovie={setMovie} />
this way the funcition will appear in the List component as a prop.
The way you were doing, it will just appear as true

My topbar duplicates since adding axios and importing the user image with axios and react

I would like to know why my "topbar" is duplicated when I only want one. And it's since I did my import of the user image via axios and added the .map in the return. I really don't understand why if someone could help me that would be nice. Thanks in advance
import "./topbar.css"
import { Search } from '#mui/icons-material'
import { useState, useEffect, Fragment } from 'react'
import axios from "axios"
function Home() {
const [user, setPosts] = useState([])
useEffect(() => {
console.log("chargement ok")
const fetchData = async () => {
const result = await axios.get(
'http://localhost:4200/api/user/')
setPosts(result.data)
}
fetchData();
}, [])
return (
<Fragment>
{ user
? user.map((users,topbar) => ( <div key={topbar} className="topBarContainer">
<div className="topBarLeft">
<span className="logo">Groupomania</span>
</div>
<div className="topBarCenter">
<div className="searchBar">
<Search className="searchIcon" />
<input placeholder="Vous cherchez quelque chose ?" className="searchInput" />
</div>
</div>
<div className="topBarRight">
<div className="topBarLinks">
<span className="topBarLink">Page d'acceuil</span>
<span className="topBarLink">Deconnexion</span>
</div>
<img src={users.picture} alt="Photo de profil de l'utilisateur" className="topBarImg" />
</div>
</div>))
: (<p></p>)
}
</Fragment>
)
}
export default Home
I'm not sure why, but it may be because of your key.
Some patterns to fix first:
const [user, setPosts] = useState([]) -> const [posts, setPosts] = useState([])
you don't have to use the word Fragment: -> <>
Normally in a .map params are used like this posts.map((post, index) => ...)
posts ? post.map(...) : null
Edit: of course you have to remove your topbar from your .map(...)
Now try with a better key than "topbard" that is the index in the array ... try post.id that should be uniq
Edit solution:
import "./topbar.css";
import { Search } from "#mui/icons-material";
import { useState, useEffect, Fragment } from "react";
import axios from "axios";
function Home() {
const [user, setUser] = useState();
useEffect(() => {
console.log("chargement ok");
const fetchData = async () => {
const result = await axios.get("http://localhost:4200/api/user/");
setUser(result.data);
};
fetchData();
}, []);
return (
<div className="topBarContainer">
<div className="topBarLeft">
<span className="logo">Groupomania</span>
</div>
<div className="topBarCenter">
<div className="searchBar">
<Search className="searchIcon" />
<input
placeholder="Vous cherchez quelque chose ?"
className="searchInput"
/>
</div>
</div>
<div className="topBarRight">
<div className="topBarLinks">
<span className="topBarLink">Page d'acceuil</span>
<span className="topBarLink">Deconnexion</span>
</div>
{user && <img
src={user.picture}
alt="Photo de profil de l'utilisateur"
className="topBarImg"
/>}
</div>
</div>
);
}
export default Home;
As the map is rendering the topbar for every user, you get as many topbars as there are users.
The map function should be inside the top bar container div.
<div key={key} className="topBarContainer">
{ user.map(...) }
</div>
This is because your are making the topbar inside the loop,So you are getting a topbar per user.

Cannot read properties of undefined (reading *)

Hey I am learning reactjs as much as i have learned I am trying to make note app
my code given below
my App.js file
import React , {useEffect, useState} from "react"
import { nanoid } from "nanoid"
import Editor from './Note/Editor'
import Sidebar from "./Note/Sidebar"
function App() {
const [notes , setNotes] = useState(JSON.parse(localStorage.getItem("notes"))||[])
const [currentNoteID , setCurrentNoteID] = useState(false)
useEffect(()=>{
localStorage.setItem("notes" , JSON.stringify(notes))
},[notes])
function createNewNotes(){
const newNotes = {
id: nanoid(),
title:"untitled",
body: "sdasda",
lastModified: Date.now()
}
setNotes(prevNote => [newNotes , ...prevNote])
setCurrentNoteID(newNotes.id)
}
function deleteNote(noteID){
setNotes(prevNote => prevNote.filter(note=> note.id !== noteID ))
}
function getNotes(){
return notes.find((note)=> note.id === currentNoteID)
}
return (
<div className="note">
<Sidebar
notes={notes}
createNewNotes={createNewNotes}
currentNoteID={currentNoteID}
setCurrentNoteID={setCurrentNoteID}
deleteNote={deleteNote}
/>
<Editor
notes={getNotes()}
currentNoteID={currentNoteID}/>
</div>
);
}
export default App;
my Sidebar.js file
import React from 'react'
import './style.css'
export default function Sidebar(props){
return(
<>
<div className='sidebar' >
<div className='sidebar-header'>
<h3>Notes</h3>
<button className='add' onClick={props.createNewNotes} >Add</button>
</div>
{ props.notes.map((note)=>{
return(
<div key={note.id}
className={`${note.id===props.currentNoteID ? "active" : ""}`}
onClick={()=>props.setCurrentNoteID(note.id)}
>
<div>
<div className="sidebar-tab">
<div className='sidebar-title'>
<p className='title'>Untitled</p>
<button className='delete' onClick={()=>props.deleteNote(note.id)}>Delete</button>
</div>
<p className='note-preview'>summary of text</p>
</div>
</div>
</div>
)
})}
</div>
</>
)
}
my Editor.js file
import React , {useState} from "react";
import './style.css'
export default function Editor(props){
const [edit , setEdit] = useState(props.notes)
function handleChange(event){
const {name , value} = event.target
setEdit(prevNote=> {
return {
...prevNote,
[name] : value
}
})
}
if(!props.currentNoteID)
return <div className="no-note">no note active</div>
return(
<>
<div className="main">
<input type="text" className="main-input" name="title" placeholder="Enter title here" value={edit.title} onChange={handleChange} autoFocus/>
<textarea className="main-textarea" name="body" placeholder="Type your notes" value={edit.body} onChange={handleChange} />
<div className="preview">
<h1 className="preview-title">{edit.title}</h1>
<div className="main-preview">{edit.body}</div>
</div>
</div>
</>
)
}
whenever i click add button or any sidebar button it shows me error
Uncaught TypeError: Cannot read properties of undefined (reading 'title')
please help me out how to fix this issue
You're expecting getNotes (which should probably be named getActiveNote, IMHO) to re-run every time notes or currentNoteID change.
To achieve this, you have to declare it as a callback (useCallback) and to declare its dependencies. Also you want to place the result in state (e.g: activeNote):
const getActiveNote = useCallback(
() => notes.find((note) => note.id === currentNoteID),
[notes, currentNoteID]
);
const [activeNote, setActiveNote] = useState(getActiveNote());
useEffect(() => {
setActiveNote(getActiveNote());
}, [getActiveNote]);
// ...
<Editor note={activeNote} />
... at which point, you no longer need the currentNoteID in the <Editor /> as you can get it from props.note.id.
See it working here: https://codesandbox.io/s/crazy-glade-qb94qe?file=/src/App.js:1389-1448
Note: the same thing needs to happen in <Editor>, when note changes:
useEffect(() => setEdit(note), [note]);

Cannot read property 'ArtisanID' of undefined when I reload the page

When I reload the page, I get this error:Cannot read property 'ArtisanID' of undefined.
How to fix this ?
I am new to this framework and thought it might be because the component instructions are not rendered when refreshing the page.
I'm using 3 context so I can't use React.Component classes unless I combine all 3 but I don't think that's a good idea.
import React, { useContext } from 'react'
import { Link } from 'react-router-dom'
import { ProductContext } from '../../global/ProductContext'
import { ArtisanContext } from '../../global/ArtisanContext'
import ProductCard from './ProductCard'
import { CartContext } from '../../global/CartContext'
const ProductPage = (props) => {
//props.preventDefault();
const {products} = useContext(ProductContext)
const {artisans} = useContext(ArtisanContext)
const { dispatch } = useContext(CartContext);
let productIDFind = props.match.params.id
const product = products.find(x=> x.ProductID === productIDFind)
const artisan = artisans.find(x=> x.ArtisanName === product.ProductArtisan)
const otherProducts = []
products.forEach(product => {
if (artisan.ArtisanName === product.ProductArtisan) {
otherProducts.push(<ProductCard key={product.ProductID} product={product} />)
}
});
return (
<section className="productPage main-section">
<div className="productPage-container main-section-constainer">
<Link className="artisanLink" to={'/Artisans/' + artisan.ArtisanID}>
<h1>Par <span>{artisan.ArtisanName}</span></h1>
</Link>
<div className="row">
<div className="productPage-principaleImg bg-img" style={{ backgroundImage: `url(${product.ProductProfilePicture})` }}></div>
<div className="productPage-info col">
<div className="row">
<div className="col">
<h2 className="productPage-Name">{product.ProductName}</h2>
<p className="productPage-Description">{product.ProductDescription}</p>
</div>
<div className="row">
<div className="productPage-Price">{product.ProductPrice}€</div>
<button className="addBtn" onClick={(e) => {
e.preventDefault();
dispatch({ type: 'ADD_TO_CART', id: product.ProductID, product })}}>
<span>Ajouter</span>
<span className="addImg bg-img"></span>
</button>
</div>
</div>
<div className="productPage-moreImg row">
<a className="productPage-YT-link" rel="noreferrer" href={product.ProductYT} target="_blank">
<div className="productPage-YT bg-img" style={{ backgroundImage: `url(${product.ProductProfilePicture})` }}></div>
<div className="productPage-YT-img bg img"></div>
</a>
<div className="productPage-img bg-img" style={{ backgroundImage: `url(${product.ProductSecondImg})` }}></div>
<div className="productPage-img bg-img" style={{ backgroundImage: `url(${product.ProductTreeImg})` }}></div>
</div>
</div>
</div>
<h3>Autre produits de {artisan.ArtisanName}</h3>
<div className="otherProducts">
{otherProducts}
</div>
</div>
</section>
)
}
export default ProductPage
ArtisanContext
import React, { createContext } from 'react'
import { db } from '../data/firebase'
export const ArtisanContext = createContext();
export class ArtisanContextProvider extends React.Component{
// Définition du state initial avec un tableau vide
state = {
artisans:[]
}
componentDidMount(){
const prevArtisans = this.state.artisans;
db.collection('Artisans').onSnapshot(snapshot => {
let changes = snapshot.docChanges();
changes.forEach(change => {
if (change.type === 'added') {
prevArtisans.push({
ArtisanID: change.doc.id,
ArtisanName: change.doc.data().ArtisanName,
ArtisanNbrProduits: change.doc.data().ArtisanNbrProduits,
ArtisanCategorie: change.doc.data().ArtisanCategorie,
ArtisanDescription: change.doc.data().ArtisanDescription,
ArtisanProfilePicture: change.doc.data().ArtisanProfilePicture,
ArtisanBanner: change.doc.data().ArtisanBanner
})
}
this.setState({
artisans: prevArtisans
})
})
})
}
render() {
return (
<ArtisanContext.Provider value={{artisans:[...this.state.artisans]}} >
{this.props.children}
</ArtisanContext.Provider>
)
}
}
I saw artisan was decalre by artisans.find. The result of artisans.find can be undefined if no item was found. So this issue will occur. To avoid this issue, you can use optional chaining:
<Link className="artisanLink" to={'/Artisans/' + artisan?.ArtisanID}>
<h1>Par <span>{artisan?.ArtisanName}</span></h1>

How to add the product to the favorites?

I am currently making a project over the database I created using Mock API. I created a button, created addToFavorites function. When the button was clicked, I wanted the selected product's information to go to the favorites, but I couldn't. I would be glad if you could help me on how to do this.
(Favorites.js empty now. I got angry and deleted all the codes because I couldn't.)
(
Recipes.js
import React, { useState, useEffect } from "react"
import axios from "axios"
import "./_recipe.scss"
import Card from "../Card"
function Recipes() {
const [recipes, setRecipes] = useState([])
const [favorites, setFavorites] = useState([])
useEffect(() => {
axios
.get("https://5fccb170603c0c0016487102.mockapi.io/api/recipes")
.then((res) => {
setRecipes(res.data)
})
.catch((err) => {
console.log(err)
})
}, [])
const addToFavorites = (recipes) => {
setFavorites([...favorites, recipes])
console.log("its work?")
}
return (
<div className="recipe">
<Card recipes={recipes} addToFavorites={addToFavorites} />
</div>
)
}
export default Recipes
Card.js
import React, { useState } from "react"
import { Link } from "react-router-dom"
import { BsClock, BsBook, BsPerson } from "react-icons/bs"
function Card({ recipes, addToFavorites }) {
const [searchTerm, setSearchTerm] = useState("")
return (
<>
<div className="recipe__search">
<input
type="text"
onChange={(event) => {
setSearchTerm(event.target.value)
}}
/>
</div>
<div className="recipe__list">
{recipes
.filter((recipes) => {
if (searchTerm === "") {
return recipes
} else if (
recipes.title.toLowerCase().includes(searchTerm.toLowerCase())
) {
return recipes
}
})
.map((recipe) => {
return (
<div key={recipe.id} className="recipe__card">
<img src={recipe.image} alt="foods" width={350} height={230} />
<h1 className="recipe__card__title">{recipe.title}</h1>
<h3 className="recipe__card__info">
<p className="recipe__card__info--icon">
<BsClock /> {recipe.time} <BsBook />{" "}
{recipe.ingredientsCount} <BsPerson />
{recipe.servings}
</p>
</h3>
<h3 className="recipe__card__desc">
{recipe.description.length < 100
? `${recipe.description}`
: `${recipe.description.substring(0, 120)}...`}
</h3>
<button type="button" className="recipe__card__cta">
<Link
to={{
pathname: `/recipes/${recipe.id}`,
state: { recipe }
}}
>
View Recipes
</Link>
</button>
<button onClick={() => addToFavorites(recipes)}>
Add to favorites
</button>
</div>
)
})}
</div>
</>
)
}
export default Card
Final Output:
I have implemented the addToFavorite() and removeFavorite() functionality, you can reuse it the way you want.
I have to do bit of modification to the code to demonstrate its working, but underlying functionality of addToFavorite() and removeFavotie() works exactly the way it should:
Here is the Card.js where these both functions are implemented:
import React, { useState } from "react";
import { BsClock, BsBook, BsPerson } from "react-icons/bs";
function Card({ recipes }) {
const [searchTerm, setSearchTerm] = useState("");
const [favorite, setFavorite] = useState([]); // <= this state holds the id's of all favorite reciepies
// following function handles the operation of adding fav recipes's id's
const addToFavorite = id => {
if (!favorite.includes(id)) setFavorite(favorite.concat(id));
console.log(id);
};
// this one does the exact opposite, it removes the favorite recipe id's
const removeFavorite = id => {
let index = favorite.indexOf(id);
console.log(index);
let temp = [...favorite.slice(0, index), ...favorite.slice(index + 1)];
setFavorite(temp);
};
// this variable holds the list of favorite recipes, we will use it to render all the fav ecipes
let findfavorite = recipes.filter(recipe => favorite.includes(recipe.id));
// filtered list of recipes
let filtered = recipes.filter(recipe => {
if (searchTerm === "") {
return recipe;
} else if (recipe.title.toLowerCase().includes(searchTerm.toLowerCase())) {
return recipe;
}
});
return (
<div className="main">
<div className="recipe__search">
<input
type="text"
onChange={event => {
setSearchTerm(event.target.value);
}}
/>
</div>
<div className="recipe-container">
<div className="recipe__list">
<h2>all recipes</h2>
{filtered.map(recipe => {
return (
<div key={recipe.id} className="recipe__card">
<img src={recipe.image} alt="foods" width={50} height={50} />
<h2 className="recipe__card__title">{recipe.title}</h2>
<h4 className="recipe__card__info">
<p>
<BsClock /> {recipe.time} <BsBook />{" "}
{recipe.ingredientsCount} <BsPerson />
{recipe.servings}
</p>
</h4>
<h4 className="recipe__card__desc">
{recipe.description.length < 100
? `${recipe.description}`
: `${recipe.description.substring(0, 120)}...`}
</h4>
<button onClick={() => addToFavorite(recipe.id)}>
add to favorite
</button>
</div>
);
})}
</div>
<div className="favorite__list">
<h2>favorite recipes</h2>
{findfavorite.map(recipe => {
return (
<div key={recipe.id} className="recipe__card">
<img src={recipe.image} alt="foods" width={50} height={50} />
<h2 className="recipe__card__title">{recipe.title}</h2>
<h4 className="recipe__card__info">
<p className="recipe__card__info--icon">
<BsClock /> {recipe.time} <BsBook />{" "}
{recipe.ingredientsCount} <BsPerson />
{recipe.servings}
</p>
</h4>
<h4 className="recipe__card__desc">
{recipe.description.length < 100
? `${recipe.description}`
: `${recipe.description.substring(0, 120)}...`}
</h4>
<button onClick={() => removeFavorite(recipe.id)}>
remove favorite
</button>
</div>
);
})}
</div>
</div>
</div>
);
}
export default Card;
Here is the live working app : stackblitz
You can get the previous favourites recipes and add the new ones.
const addToFavorites = (recipes) => {
setFavorites(prevFavourites => [...prevFavourites, recipes])
console.log("its work?")
}

Categories