I have two React components: ul list and each li item, which is a flash-card. In each item there are two strings: searching word and its translation (api.source, api.target) and also checkbox. After clicking on the checkbox and later on the button (CardList.js) I would like to get my data and send it to my backend. Can anybody please tell me how to get these two strings and pass them into addFlashCards function? Right now I have hardcoded it as dataSet
const CardsList = ({ fetchedApi }) => {
const addFlashCards = () => {
const dataSet = [
{
apiSource: "sampleDataCheckbox",
apiTarget: "sampleDataCheckbox2",
},
{
apiSource: "sampleDataCheckbox",
apiTarget: "sampleDataCheckbox",
},
];
const url = "/api/saveToDB";
axios
.post(url, { data: dataSet, user: "SOME USER" })
.then((res) => {
const fetchedData = res.data;
console.log(fetchedData);
})
.catch((err) => console.error(err));
};
return (
<div className="cards-list">
<button onClick={addFlashCards}>Add</button>
<ul className="cards-list__list">
{fetchedApi.map((api) => {
return <CardItem api={api} />;
})}
</ul>
</div>
);
};
export default CardsList;
const CardItem = ({ api }) => {
const [isChosenCard, setIsChosenCard] = useState(false);
const clickedCheckbox = () => {
setIsChosenCard(!isChosenCard);
};
return (
<>
<li className="card-item">
<input
className="fiszkiBox"
type="checkbox"
onClick={clickedCheckbox}
/>
<div className="flip-card">
<div className="flip-card-inner">
<div className="flip-card-front">
<p id="api-source">{api.source}</p>
</div>
<div className="flip-card-back">
<p className="api-target">{api.target}</p>
</div>
</div>
</div>
</li>
</>
);
};
export default CardItem;
Many thanks!
So as summary you have a list which you render and you want to select items which would be sent to backend. In this case you can add a empty array to your CardList by using useState hook which will contain the selected items.
const CardsList = ({ fetchedApi }) => {
const [selectedItems, setSelectedItems] = useState([]);
const addFlashCards = () => {
// Here you can do some kind of check with mapping the selectedItems Array, as you need
const url = "/api/saveToDB";
axios
.post(url, { data: selectedItems, user: "SOME USER" })
.then((res) => {
const fetchedData = res.data;
console.log(fetchedData);
})
.catch((err) => console.error(err));
};
function selectItem(item){
// So below, we do it to make component render by creating new array instead of mutating the current array. Actually try to play with code, you can even remove these and mutate current and set.
let selectedItemsArray = [...selectedItems];
// Here below, we check if selectedItem already exists in selectedItemsArray, if it exists we would like to remove it right? if it does not exist, we just add it to array by pushing it.
let itemCheck = selectedItemsArray.find((arrayItem) => arrayItem.source === item.source);
if (itemCheck) {
// Here if selected item was in array, we just remove it by filtering the array
selectedItemsArray = selectedItemsArray.filter((arrayItem) => arrayItem.source !== item.source)
}
else{
// Here selected item was not in our array so we just push it
selectedItemsArray.push(item)
}
setSelectedItems(selectedItemsArray);
}
return (
<div className="cards-list">
<button onClick={addFlashCards}>Add</button>
<ul className="cards-list__list">
{fetchedApi.map((api, index) => {
return <CardItem api={api} onItemSelected={item=> selectItem(item)} />;
})}
</ul>
</div>
);
};
const CardItem = ({ api, onItemSelected }) => {
return (
<>
<li className="card-item">
<input
className="fiszkiBox"
type="checkbox"
onClick={() => onItemSelected({
source: api.source,
target: api.target
})}
/>
<div className="flip-card">
<div className="flip-card-inner">
<div className="flip-card-front">
<p id="api-source">{api.source}</p>
</div>
<div className="flip-card-back">
<p className="api-target">{api.target}</p>
</div>
</div>
</div>
</li>
</>
);
};
These codes should work, hope you got the logic also. This approach would be logical in your case.
Related
I'm trying to delete ToDo list by event delegation so I placed delete onClick handler on the ul element. Question I have is why toDo != e.target.parentNode.children[0].innerText never equal to true even when I place same value for both elements. So after I add "study" and "eat" for toDo list then click on delete button next to "study", e.target.parentNode.children[0].innerText shows "study" and toDo shows "study" as well, but it doesn't get filtered out. They both have string type so I'm confused. So why doesn't this work?
import './styles.css';
import React, {useState} from 'react';
export default function App() {
const [toDoList, setToDoList] = useState([]);
const [value, setValue] = useState([]);
const addToDo = () => {
let copyToDoList = [...toDoList];
setToDoList([...copyToDoList, value]);
setValue("");
}
const deleteToDo = (e) => {
let copyToDoList = [...toDoList];
let filteredList = copyToDoList.filter((toDo) => {
return toDo != e.target.parentNode.children[0].innerText})
console.log(filteredList)
setToDoList(filteredList);
}
const handleChange = (e) => {
setValue(e.target.value);
}
return (
<div>
<h1>Todo List</h1>
<div>
<input value={value} onChange={handleChange} type="text" placeholder="Add your task" />
<div>
<button onClick={addToDo}>Submit</button>
</div>
</div>
<ul onClick={deleteToDo}>
{toDoList.map((toDo, idx) => {
return(
<li>
<span>{toDo} </span>
<button>Delete</button>
</li>
)
})}
</ul>
</div>
);
}
Try the following command. Then you would see that toDo and e.target.parentNode.children[0].innerText have different lengths.
e.target.parentNode.children[0].innerText has one more lengths than toDo
console.log(toDo.length, e.target.parentNode.children[0].innerText.length);
And the solution is below
change this line {toDo} to {toDo}
Remove space.
This happens because you left a space character " " inside the span. This means "aaa" !=="aaa ".
To avoid this error you can add the trim like I did. (but just removing the space is enough...)
const deleteToDo = e => {
const copyToDoList = [...toDoList]
const filteredList = copyToDoList.filter( toDo => {
return toDo !== e.target.parentNode.children[0].innerText.trim()
})
setToDoList(filteredList)
}
<ul onClick={deleteToDo} > {
toDoList.map((toDo, idx) => {
return (
<li key={idx}>
<span>{toDo}</span>
<button>Delete</button>
</li>
)
})
}</ul>
I have 2 components, the Favorites component, makes a request to the api and maps the data to Card.
I also have a BtnFav button, which receives an individual item, and renders a full or empty heart according to a boolean.
Clicking on the BtnFav render removes a certain item from the favorites database.
What I need is that in the Favorites component, when I click on the BtnFavs component, the useEffect of Favorites is triggered again to bring the updated favorites.
How can i solve this? I have partially solved it with a global context(favoritesUser), but is there any other neater alternative?
The data flow for now would be something like this:
Favorites component fetches all the complete data and passes it to the Card component, the Card component passes individual data to the BtnFavs component.
Favorites Component:
const fetchWines = async () => {
try {
const vinos = await axios.get(`/api/favoritos/${id}`);
const arrVinos = vinos.data.map((vino) => {
return vino.product;
});
setVinosFavs(arrVinos);
} catch (err) {
console.error(err);
}
};
useEffect(() => {
fetchWines();
}, [favoritesUser]);
return (
<div>
<h1>Mis favoritos</h1>
<Card listWines={vinosFavs} />
</div>
);
BtnFavs:
const handleClickFav = (e) => {
if (!boton) {
axios.post("/api/favoritos/add", { userId, productId }).then((data) => {
setBoton(true);
return;
});
}
axios.put("/api/favoritos/delete ", { userId, productId }).then((data) => {
setBoton(false);
setFavoritesUser(data);
});
};
What I need is that in the Favorites component, when I click on the BtnFavs component, the useEffect of Favorites is triggered again to bring the updated favorites.
How can i solve this? I have partially solved it with a global context(favoritesUser), but is there any other neater alternative?
The pattern you want is called a callback function, just like the onClick of a button. You pass a function to your components that get executed given a condition. If you want fetchWines to be called again, then just pass the function in as a prop.
Favorites Component:
<Card listWines={vinosFavs} refresh={fetchWines} />
Card Component
<BtnFavs onDelete={refresh} ... />
BtnFavs Component
onDelete();
You can name it whatever you want, but generally callbacks will be named like on<condition>.
If you really wanted useEffect to be triggered then you would pass a setState function that set one of the dependencies, but I don't see a point in this case.
I will share code, because this problem its normal for me, i really want to learn and improve that.
const Favorites = () => {
const { favoritesUser } = useFavoritesContext();
const user = useSelector((state) => state.user);
const id = user.id;
const [vinosFavs, setVinosFavs] = useState([]);
const fetchWines = async () => {
try {
const vinos = await axios.get(`/api/favoritos/${id}`);
const arrVinos = vinos.data.map((vino) => {
return vino.product;
});
setVinosFavs(arrVinos);
} catch (err) {
console.error(err);
}
};
useEffect(() => {
fetchWines();
}, [favoritesUser]);
return (
<div>
<h1>My favorits</h1>
<Grid listVinos={vinosFavs} />
</div>
);
};
export default Favorites
Grid
export default function Grid({ listVinos }) {
return (
<div>
<ul className={styles.layoutDeVinos}>
{listVinos?.map((element) => {
return <WineCard key={element.id} vino={element} />;
})}
</ul>
</div>
);
}
Card
export default function WineCard({ vino }) {
return (
<>
<div>
<Link to={`/products/${vino.id}`}>
<li>
<div className={styles.card}>
<div
className={styles.img1}
style={{
backgroundImage: `url(${vino.images})`,
}}
></div>
<div className={styles.text}>{vino.descripcion}</div>
<div className={styles.catagory}>
{vino.nombre}
<i className="fas fa-film"></i>
</div>
<div className={styles.views}>
{vino.bodega}
<i className="far fa-eye"></i>{" "}
</div>
</div>
</li>
</Link>
<div className="botonesUsuario">
<BtnFavs vino={vino} />
</div>
</div>
</>
);
}
BTN FAVS
export default function BtnFavs({ vino }) {
const { setFavoritesUser } = useFavoritesContext();
const [boton, setBoton] = useState(false);
const user = useSelector((state) => state.user);
const userId = user.id;
const productId = vino.id;
useEffect(() => {
axios
.post("/api/favoritos/verify", { userId, productId })
.then((bool) => setBoton(bool.data));
}, []);
const handleClickFav = (e) => {
if (!boton) {
axios.post("/api/favoritos/add", { userId, productId }).then((data) => {
setBoton(true);
return;
});
}
axios.put("/api/favoritos/delete ", { userId, productId }).then((data) => {
setBoton(false);
setFavoritesUser(data);
});
};
return (
<>
{!user.id ? (
<div></div>
) : boton ? (
<span
class="favIcons material-symbols-rounded"
onClick={handleClickFav}
>
favorite
</span>
) : (
<span className="material-symbols-rounded" onClick={handleClickFav}>
favorite
</span>
)}
</>
);
}
first time asking questions haha, I'm trying to remove a specific JSX element from my array of elements but I am not sure how to identify said element is the one being clicked to get removed so I wanted to know if anyone encountered this issue before and how you would go about solving it.
This is the code for the input Container
const InputComponent = (props) => {
return (
<div className="input-container" onClick={e =>console.log(e)}>
<input className="input-name" type="text" />
<input className="input-value" type="text" />
<button className="remove-button" onClick={props.remove}><img src={minusIcon} alt="remove-icon" /></button>
</div>
);
}
export default InputComponent;
and this is the code for the parent component to manage the removal of said element
const Main = () => {
const [newInput, setInput] = useState([]);
const [currentInput, setCurrentInput] = useState([<InputComponent key={0}/>]);
const [currentIndex, setIndex] = useState(0)
const [currentPlanName, setCurrentPlanName] = useState('Current-Plan');
const [newPlanName, setNewPlanName] = useState('New-Plan');
// const [currentInputValue, setCurrentValue] = useState('')
// const [newInputValue, setNewValue] = useState('')
// Sets Keys for each New Element in Array
const [newKey, setNewKey] = useState(0);
const [currentKey, setCurrentKey] = useState(0)
// Handle Removal of specific array by using key
const handleCurrentRemoval = () => {
let newArray = currentInput.filter(element => element.key !== )
console.log(newArray)
setCurrentInput(newArray)
}
// Initialize Keys for each Array
const currentArrayElement = {
element: <InputComponent key={currentKey} remove={handleCurrentRemoval} />,
index: currentKey
};
const newArrayElement = <InputComponent key={newKey+1}/>
// Adds new Element to array
const handleCurrentClick = () => {
setCurrentInput(prevValues => [...prevValues, currentArrayElement.element])
setCurrentKey(currentKey+1);
console.log(currentArrayElement)
console.log(currentInput)
};
const handleNewClick = () => {
setInput(prevValues => [...prevValues, newArrayElement])
};
// const handleRemoveClick = (value) => {
// currentInput.filter(current => value !=)
// }
return (
<div className="main-container">
<div className="quote-container">
<div className="current-plan">
<h2>{currentPlanName}</h2>
{
currentInput.map((inputs) => {
return inputs
})
}
<div className="button-container">
<button className="add-input" onClick={handleCurrentClick}><img src={addIcon} alt="add"/></button>
</div>
</div>
<div className="new-plan">
<h2>{newPlanName}</h2>
{
newInput.map((inputs) => {
return inputs
})
}
<div className="button-container">
<button className="add-input" onClick={handleNewClick}><img src={addIcon} alt="add"/></button>
</div>
</div>
</div>
</div>
);
}
export default Main;
I do apologize in advance if I posted this incorrectly.
Thank you for your assistance
I think you've made this entirely more complex than it needs to be with all the index value storage in state. It is also anti-pattern in React to store JSX in state, you should store data in state and render the data to JSX.
I suggest storing generated input ids in the arrays instead and mapping these to JSX.
Example:
// id generator
let id = 0;
const getId = () => id++;
export default function App() {
const [newInput, setInput] = useState([]);
const [currentInput, setCurrentInput] = useState([]);
const [currentPlanName, setCurrentPlanName] = useState("Current-Plan");
const [newPlanName, setNewPlanName] = useState("New-Plan");
// Handle Removal of specific element by using id
const handleCurrentRemoval = (removeId) => {
setCurrentInput((ids) => ids.filter((id) => id !== removeId));
};
// Adds new id to array
const handleCurrentClick = () => {
setCurrentInput((ids) => ids.concat(getId()));
};
const handleNewClick = () => {
setInput((ids) => ids.concat(getId()));
};
const handleRemoveClick = (removeId) => {
setInput((ids) => ids.filter((id) => id !== removeId));
};
return (
<div className="main-container">
<div className="quote-container">
<div className="current-plan">
<h2>{currentPlanName}</h2>
{currentInput.map((id) => {
return (
<InputComponent
key={id}
remove={() => handleCurrentRemoval(id)}
/>
);
})}
<div className="button-container">
<button className="add-input" onClick={handleCurrentClick}>
<img src={addIcon} alt="add" />
</button>
</div>
</div>
<div className="new-plan">
<h2>{newPlanName}</h2>
{newInput.map((id) => {
return (
<InputComponent key={id} remove={() => handleRemoveClick(id)} />
);
})}
<div className="button-container">
<button className="add-input" onClick={handleNewClick}>
<img src={addIcon} alt="add" />
</button>
</div>
</div>
</div>
</div>
);
}
I'm making a cart component where a user can add/delete products in a cart. Each time a button (Add to Cart) is clicked, the product should be added to the cart. When I try adding different types of products, the state resets each time to whatever product I clicked. But when I add multiple of the same product, the state updates accordingly, showing the product the same amount of times I had clicked. I need to be able to add different types of products to the cart. Any help is appreciated!
Displaying products on page:
export default function Products(props) {
const [cartItems, setCartItems] = useState([]);
//access first image from imageURLs section in object
const image = props.img;
const obj = {};
const result = Object.assign(obj, image);
//update cart
function addToCart(item) {
const updateCart = [...cartItems, props];
updateCart.forEach((e) => console.log("foreach", e));
setCartItems(updateCart);
}
console.log("new state", cartItems);
return (
<div className="product-container">
<img src={result[0]} alt="furniture" className="image" />
<div className="product-info">
<div className="product-name">{props.name}</div>
<div className="product-brand">{props.brand}</div>
<div className="product-price">${props.price}</div>
<button
type="submit"
className="add-button"
onClick={() => addToCart({ ...props })}
>
+ Add to Cart
</button>
</div>
</div>
);
}
Parent component:
import { useState } from "react";
import "../styles/Home.scss";
import "../styles/Products.scss";
import Products from "./Products";
export default function Home() {
const [product, setProduct] = useState([]);
//get product info from api
async function getProducts(e) {
e.preventDefault();
const data = await fetch(
`https://main-api.fulhaus.com/fulhaus-tech-test/get-products`
)
.then((res) => res.json())
.then((data) => data);
//set state for products
setProduct(data);
}
//display product info on page
function displayProduct() {
const productToDisplay = [
...new Set(
product.map((product, index) => (
<Products
key={product._id}
id={product._id}
img={product.imageURLs}
name={product.vendorProductName}
brand={product.vendorName}
price={product.MSRP}
/>
))
),
];
return productToDisplay;
}
return (
<div>
<div className="home-container"></div>
<div className="home-title">
<h1>Patio Furniture</h1>
<button
type="submit"
className="home-button"
onClick={(e) => getProducts(e)}
>
SHOP
</button>
</div>
<div className="product-section">{displayProduct()}</div>
</div>
);
}
The state resets since you remount the components on every render by invoking the displayProduct function on every render:
<div>{displayProduct()}</div>
Instead you should rewrite the logic to mount it once, the Set object is useless here (it contains objects, which you can't apply uniqueness to):
export default function Home() {
const [product, setProduct] = useState([]);
//get product info from api
async function getProducts(e) {
e.preventDefault();
const data = await fetch(
`https://main-api.fulhaus.com/fulhaus-tech-test/get-products`
)
.then((res) => res.json())
.then((data) => data);
//set state for products
setProduct(data);
}
return (
<div>
<div className="home-container"></div>
<div className="home-title">
<h1>Patio Furniture</h1>
<button
type="submit"
className="home-button"
onClick={(e) => getProducts(e)}
>
SHOP
</button>
</div>
<div className="product-section">
{product.map((product, index) => (
<Products
key={product._id}
id={product._id}
img={product.imageURLs}
name={product.vendorProductName}
brand={product.vendorName}
price={product.MSRP}
/>
))}
</div>
</div>
);
}
I'm trying to filter through some posts based on their category if a button is clicked. For example I have a button that when clicked the only posts that show up are related to software projects.
I have set up a function called searchHandler that I've passed through to my SidebarOptions component, which has the onclick event. But when I pass it through nothing happens.
Here is the code in the (parent) Home Component where the searchHandler is:
function Home() {
const [posts, setPosts] = useState([]);
const [filteredPosts, setFilteredPosts] = useState(null);
const searchHandler = (event) => {
const { value } = event.target;
setFilteredPosts(
value
? posts.filter(
(post) =>
post.question.question.includes(value)
)
: null
);
};
useEffect(() => {
db.collection("questions")
.orderBy("timestamp", "desc")
.onSnapshot((snapshot) =>
setPosts(
snapshot.docs.map((doc) => ({
id: doc.id,
question: doc.data(),
}))
)
);
}, []);
return (
<div className="home">
<div></div>
<Header searchHandler={searchHandler} />
<div className="home__content">
<Sidebar searchHandler={searchHandler} />
<Feed posts={filteredPosts || posts} />
<Widget />
</div>
</div>
);
}
Here is the (child) Sidebar component that receives it:
import React from "react";
import "../Style/Sidebar.css";
import SidebarOptions from "./SidebarOptions";
function Sidebar({ searchHandler }) {
return (
<div className="sidebar">
<SidebarOptions searchHandler={searchHandler} />
</div>
);
}
export default Sidebar;
And here is the (grandchild)SidebarOptions that the function is finally sent to:
function SidebarOptions({ searchHandler }) {
return (
<div className="sidebarOptions">
<div className="sidebarOption" onChange={() => searchHandler}>
<img
src="https://c.pxhere.com/photos/7b/1a/code_coding_computer_developer_developing_development_macbook_notebook-913320.jpg!d"
srcset="https://c.pxhere.com/photos/7b/1a/code_coding_computer_developer_developing_development_macbook_notebook-913320.jpg!d"
alt="Software Projects"
/>
<p>Software Projects</p>
</div>
);
};
I think you need to revisit your SideBarOptions component. I wonder if the onChange handler makes sense on a div. I think it should be input rather than a div if you want your user to type. Also, you need to call your handler with the value that is typed, here you are not calling the handler (notice the missing () after searchHandler in your code for SideBarOptions). Also, it will be better to add something like a debounce so that the filter is not triggered for every character that a user types. It should ideally be triggered once a user stops typing, debounce is precisely that.
Putting some code snippet below based on my guess about how it might work.
const SideBarOptions = ({ searchHandler }) => {
const [filterText, setFilterText] = useState("");
const handleFilter = () => {
searchHandler(filterText);
}
return (
<div className="sidebarOptions">
<input name="filterText" value={filterText} onChange={(e) => setFilterText(e.target.value)} />
<div className="sidebarOption" onChange={() => searchHandler}>
<img src="https://c.pxhere.com/photos/7b/1a/code_coding_computer_developer_developing_development_macbook_notebook-913320.jpg!d" srcset="https://c.pxhere.com/photos/7b/1a/code_coding_computer_developer_developing_development_macbook_notebook-913320.jpg!d"
alt="Software Projects"
/>
<p>Software Projects</p>
<button onClick={handleFilter}>Filter</button>
</div>
</div>
);
}
So I was able to solve this by making a new function called categoryfilter in the Home component that went through the options and looked for the category of the posts in the database:
const categoryFilter = (category = "All") => {
const filtered =
category === "All"
? posts
: posts.filter(({ question }) => question.option === category);
setFilteredPosts(filtered);
};
I then passed that code as a prop to the sidebarOptions div after cleaning up the code a bit and used it to filter the posts based on the category name:
function SidebarOptions({ categoryFilter }) {
const categories = [
//Add all projects
{
name: "All",
imgUrl: "",
},
{
name: "Software Project",
imgUrl:
"https://c.pxhere.com/photos/7b/1a/code_coding_computer_developer_developing_development_macbook_notebook-913320.jpg!d",
},
{
name: "Engineering Project",
imgUrl:
"https://c.pxhere.com/photos/a7/72/gears_cogs_machine_machinery_mechanical_printing_press_gears_and_cogs_technology-818429.jpg!d",
},
];
return (
<div className="sidebarOptions">
{categories.map((category) => (
<div
className="sidebarOption"
onClick={() => categoryFilter(category.name)}
>
{category.imgUrl && (
<img
src={category.imgUrl}
srcSet={category.imgUrl}
alt={category.name}
/>
)}
<p>{category.name}</p>
</div>
))}
<div className="sidebarOption">
<Add />
<p>Suggest Project Space</p>
</div>
</div>
);
}
export default SidebarOptions;