Buttons won't render in React after mapping through array - javascript

I'm just learning React. I have two API calls to the backend MongoDB to pull names of vacations related to a user account. The names are saved to a JSON object and I'm trying to map through it to generate buttons on my React side but it's rendering nothing in that div. Everything else renders to the page. API calls are a mess because I thought that was the issue at first.
Profile Page
import React, { Component } from "react";
import Wrapper from "../components/Wrapper";
import { Container, Row, Col } from "../components/Grid";
import PastVacations from "../components/PastVacations";
import VacationBtn from "../components/VacationBtn"
import API from "../utils/API";
class Profile extends Component {
state = {
vacaIDs: [],
vacaNames: []
}
componentDidMount() {
this.getUser()
}
getUser = () => {
let IDsArr = []
API.getUser("Bill")
.then((res) => {
// console.log(res.data) Logs user found
res.data.vacations.forEach((VacaIDs) => {
let obj = {}
obj.name = VacaIDs;
IDsArr.push(obj)
// console.log(items) Logs Vacation IDs
})
console.log(IDsArr)
this.setState({
vacaIDs: IDsArr
})
this.getNames()
}).catch((err) => {
console.log(err)
})
}
getNames = () => {
let namesArr = []
this.state.vacaIDs.forEach((names) => {
console.log(names.name)// Logs vacation IDs
let obj = {}
API.getVacations(names.name).then((res) => {
console.log(res.data.name)// Logs Vacation names
obj.name = res.data.name;
namesArr.push(obj)
}).catch((err) => {
console.log(err.response)
})
})
this.setState({
vacaNames: namesArr
})
}
render() {
return (
<div className="">
<div className="row justify-content-around">
<div className="col-md-6">
{this.state.vacaNames.map(items => (
<VacationBtn
name={items.name}
/>
))}
</div>
<div className="col-md-4">
<div className="card">
<h5 className="card-header">
Card title
</h5>
<div className="card-body">
<p className="card-text">
Card content
</p>
</div>
<div className="card-footer">
Card footer
</div>
</div>
</div>
</div>
</div>
);
}
}
export default Profile;
VacationBtn Component
import React, { Component } from "react";
import "./style.css";
class VacationBtn extends Component {
render() {
return (
<button type="button" className="btn btn-primary">{this.props.name}</button>
);
}
}
export default VacationBtn;

Use Promise.all
Your current code is iterating for API calls but setState happens before any of the api calls are resolved.
getNames = () => {
let namesArr = [];
const promises = [];
this.state.vacaIDs.forEach((names) => {
promises.push(API.getVacations(names.name));
});
Promise.all(promises).then((values) => {
// do data manipulation here
values.forEach((val) => {
namesArr.push(val.data.name);
});
this.setState({
vacaNames: namesArr,
});
});
};

As #chandan_kr_jha noticed you're updating state before API is finished it's work.
A bit fancier code below with the same idea behind:
getNames = async () => {
const promises = this.state.vacaIDs.map((names) => API.getVacations(names.name));
const vacations = await Promise.all(promises);
this.setState({
vacaNames: vacations.map(v => v.data.name),
});
};

Related

Trigger useEffect with anotherComponents

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>
)}
</>
);
}

React js useState&useEffect array duplicates elements after a change

I am a beginner in react js programming. I'm trying to do the todo project, which is a classic project. When I delete or add an element from the list, the newly formed list appears on the screen by combining with the previous one, I will show it with a picture below. I did not understand the source of the eror so wanted to post it here to get some advices suggestions about why it is happening.Thank you.(I am getting and storing data in firebase firestore database)
Before Adding an element initial array state
After adding an element to the array.
I am using useState for array and using useEffect to get initial data
MainPage.js that contains form and the list components.
const MainPage = () => {
const [isLoading, setLoding] = useState(true);
const [array, setArray] = useState([]);
const sub = async (email) => {
var result = [];
await onSnapshot(doc(db, "users", email), (doc) => {
var data = doc.data().todos;
data.forEach((element) => {
Object.keys(element).map(() => {
result.push(element["title"]);
});
});
setArray(result);
setLoding(false);
});
};
useEffect(() => {
sub(auth.currentUser.email);
}, []);
const onAddToDo = (todoTitle) => {
setArray((prevAray) => {
return [...prevAray, todoTitle];
});
};
const onRemove = (title) => {
setArray((prevAray) => {
return [array.pop(array.indexOf(title))];
});
};
return (
<div>
{isLoading && <h1>Loading</h1>}
{!isLoading && (
<div>
<section>
<NavBar></NavBar>
<ToDoForm passData={onAddToDo} />
</section>
<section>
<CardList removeCards={onRemove} array={array} />
</section>
</div>
)}
</div>
);
};
export default MainPage;
Firebase.js that stores the firebase update methods
export const deleteItem = (title) => {
updateDoc(doc(db, "users", auth.currentUser.email), {
todos: arrayRemove({ title: title }),
});
};
export const addnewTodo = (title) => {
updateDoc(doc(db, "users", auth.currentUser.email), {
todos: arrayUnion({ title: title }),
});
};
TodoForm.js component
const ToDoForm = (props) => {
const [todoTitle, setTitle] = useState("");
const titleChangeHandler = (event) => {
setTitle(event.target.value);
};
const newTodoAdder = (event) => {
event.preventDefault();
addnewTodo(todoTitle);
props.passData(todoTitle);
};
return (
<div className="form_holder">
<div className="form_container">
<form onSubmit={newTodoAdder}>
<h3>Add Events</h3>
<label>Title</label>
<input
onChange={titleChangeHandler}
type="text"
placeholder="Title"
id="title"
></input>
<div className="holder">
<button type="sumbit">Add</button>
</div>
</form>
</div>
</div>
);
};
export default ToDoForm;
CardList.js component
const CardList = (props) => {
const array = props.array;
if (array.length === 0) {
return (
<div className="grid_container">
<h2>Found no todos</h2>
</div>
);
}
return (
<div className="grid_container">
{array.map((element, index) => {
return (
<Card
removeSelf={() => {
props.removeCards(element);
}}
key={index}
title={element}
/>
);
})}
</div>
);
};
export default CardList;
Card.js component
const Card = (props) => {
const handleRemove = (event) => {
event.preventDefault();
deleteItem(props.title);
props.removeSelf();
};
return (
<div className="card">
<h2 className="card__title">{props.title}</h2>
<button type="button" onClick={handleRemove}>
Delete
</button>
</div>
);
};
export default Card;
EDIT ;
Index.js file
import React from "react";
import ReactDOM from "react-dom/client";
import App from "./App";
import { BrowserRouter } from "react-router-dom";
const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(
<BrowserRouter>
<App />
</BrowserRouter>
);
SOLUTION
I fixed the issue by changing the add and remove functions that were inside of MainPage.js file You can see the new versions bellow. Hope someday it will help somebody.
Use effect was called once all I had to do get the data again after a change...
New Remove and Add functions
const onAddToDo = (todoTitle) => {
console.log(todoTitle + " Added");
sub(auth.currentUser.email);
};
const onRemove = (title) => {
console.log(title + " Deleted");
sub(auth.currentUser.email);
};

How can I reload a react js component after filtering data

I will appreciate your help with my project.
I created button filters for a component that displays list of courses in a nextjs project. When I click on a button it filters the courses but when I refresh the page it gives me the error below:
./pages/index.js
Module parse failed: Identifier 'courses1' has already been declared (15:15)
File was processed with these loaders:
./node_modules/next/dist/build/webpack/loaders/next-swc-loader.js
You may need an additional loader to handle the result of these loaders.
Please see code below.
import { useState, useEffect } from "react"
import axios from "axios"
import CourseCard from "../components/cards/CourseCard";
import { Button } from 'antd'
const Index = ({ courses }) => {
const allTopics = ['All', ...new Set(courses.map(courses => courses.topic))]
const [courses, setCourses] = useState(courses)
const [buttons, setButtons] = useState(allTopics)
const filter = (button) => {
if (button === 'All') {
setCourses(courses)
}
const filteredData = courses.filter(courses => courses.topic === button)
setCourses(filteredData)
}
return (
<>
<h1 className="jumbotron p-5 text-center bg-primary text-white square">OEP</h1>
<div className="container-fluid">
<div>
{
buttons.map((topic, i) => {
return <Button onClick={() => filter(topic)} className="btn ms-2">{topic}</Button>
})
}
</div>
<div className="row">
{courses.map((course) => <div key={course._id} className="col-md-4">
<CourseCard course={course} />
</div>)}
</div>
</div>
</>
)
};
export async function getServerSideProps() {
const { data } = await axios.get(`${process.env.API}/courses`);
return {
props: {
courses: data,
},
}
}
export default Index;
The constant courses you declared (in line 15) already existed. It was destructured from the parameters of Index. Try to change the name of one of these 2 variables.

How to display 4 separate React components using Axios call and useState array

I am trying to use an Axios get to get an object that contains all of the information from a single row of a Postgres table that matches the name of a specific book for a "book selling" website. The book name is stored in a localStorage array that is taken from a previous shop page where the user chooses the books they want to buy and the localStorage array of "book names" is then passed into the Axios get in the loop. My problem is within the first Axios get (axios.get(http://localhost:3001/cart/${book})), I believe my code overwrites the component each time and only 1 component is displayed instead of 3 or 4 depending on the number of items that are in the cart. I looked at the React documentation and know I have to use the spread operator but when I tried using it as described in the documentation, it started creating arrays within arrays and broke my page. I know I'm doing something wrong but I'm not familiar enough with React to solve it. Thanks for any help and sorry for the messy code and all the console.log lines, I am still testing!
import React, { useState, useEffect } from "react"; import axios from 'axios'; import '../styles/Cart.css'; import { FontAwesomeIcon } from '#fortawesome/react-fontawesome'; import { faCartShopping, faPlus, faMinus, faTimes } from '#fortawesome/free-solid-svg-icons';
function Cart() {
const [cart, setCart] = useState([]);
let currentcart;
let currentuser;
let cartstatus;
let empty = "empty";
currentcart = JSON.parse(localStorage.getItem("currentCart"));
currentuser = JSON.parse(sessionStorage.getItem('currentUser'));
cartstatus = localStorage.getItem("cartStatus");
let d = new Date();
let date = d.toLocaleString();
console.log(currentcart);
console.log(cartstatus);
const BookItem = ({
book_name,
price,
book_img
}) => (
<div classname='shopInventory'>
<div id='bookCard'>
<img src={book_img} width=
'100%' height='100%'/>
<h3>{book_name}</h3>
${price}
<br/><br/>
<button className="addBtn"><FontAwesomeIcon icon={faPlus}/> Add</button>
<span id='bookQuantity'>0</span>
<button className='minusBtn'><FontAwesomeIcon icon={faMinus} /> Subtract</button>
</div>
</div>
);
useEffect(() => {
currentcart.map((book) =>
axios.get(`http://localhost:3001/cart/${book}`).then((response) => {
const data = response.data
setCart(data)
})
.catch((error) => {
console.log(error)
})
)}, [currentcart.length]);
let orderData ={
username: currentuser,
order: currentcart,
date: date
};
const submitButton = (event) => {
axios.post(`http://localhost:3001/checkout`,orderData).then((response)
=> {
})
.catch((error) => {
console.log(error)
})
}
const clearCart = () => {
cartstatus = localStorage.setItem('cartStatus', empty);
window.location.reload();
}
console.log(cart);
return (
<>
{cartstatus != "empty" ? (
<div>
<div className='cartSection'>
<div>
{cart.map(cart => <BookItem key={cart.book_name} {...cart} />)}
</div>
</div>
<div id='clear-both'></div>
<div id='checkoutBtns'>
<button className='checkoutBtn' onClick={submitButton}><FontAwesomeIcon icon={faCartShopping}/> Checkout</button>
<button className='clearBtn' onClick={clearCart}><FontAwesomeIcon icon={faTimes}/> Clear Cart</button>
</div>
<div id='clear-both'></div>
</div>
) : (
<center>
<h3>
The Cart is empty
</h3>
</center>
)}
</>
); }
export default Cart;

React "map" is undefined/empy while there is a state array?

I have a weird problem when I console log my component on load to check if there is a state. I Get an array back with data. But when I try to loop through it. I get map undefined? I don't understand why it's driving me crazy.
What am i doing wrong? I used the same thing on other components without any problems.
Thanks!
My code:
import React, { Component } from 'react';
import ReactHtmlParser from 'react-html-parser';
// API settings
import { WP_DATA_URL } from 'constants/import';
// Axios fetching
import axios from 'axios';
// components
import Youtube from 'components/Youtube/Youtube';
import Slider from 'react-slick';
import SpinnerLoader from 'components/SpinnerLoader/SpinnerLoader';
class College extends Component {
state = {
page_college: [],
loading: true,
};
getCoffee() {
return new Promise(resolve => {
setTimeout(() => resolve('☕'), 1000); // it takes half of a second to make coffee
});
}
async showData() {
try {
const wpCollege = axios(`${WP_DATA_URL}/pages?slug=college`);
await this.getCoffee();
await Promise.all([wpCollege]).then(response => {
this.setState({
page_college: response[0].data[0].acf,
loading: false,
});
console.log(this.state.page_college);
});
} catch (e) {
console.error(e); // 💩
}
}
componentDidMount() {
this.showData();
}
render() {
const { loading } = this.state;
const { title, description, page_college: college } = this.state;
return (
<div className="pages--container">
<div className="pages">
<div className="row center-xs pages--wrapper">
<div className="page">
<div className="page--content">
{loading ? (
<SpinnerLoader />
) : (
<React.Fragment>
<div className="col-xs-12 col-md-5">
<h2>HOI</h2>
</div>
<div className="col-xs-12 col-md-6">
{college.map(data => {
console.log(data);
})}
</div>
</React.Fragment>
)}
</div>
</div>
</div>
</div>
</div>
);
}
}
export default College;
setState is asynchronous so your console.log after it may be reflecting the previous state. Pass setState a callback as the 2nd param and check the state there. response[0].data[0].acf might not be an array.
async componentDidMount() {
await this.showData();
}
Just make the componentDidMount wait for the showData to complete.

Categories

Resources