Infinite loop inside React JS - javascript

So, I found similar issues that were not quite the same and didn't help me understand my problem.
I'm building a simple task manager using React and Firebase. I'm fetching data using a custom hook, since different components need to fetch data from Firebase. When the page loads, I get an infinite loop inside the custom hook, that's my issue.
Here's the component that triggers the custom hook:
const AllTasks = () => {
const [tasks, setTasks] = useState([]);
const renderTasks = (taskObj) => {
const loadedTasks = [];
for (const key in taskObj) {
loadedTasks.unshift({
id: key,
date: taskObj[key].Date,
client: taskObj[key].Client,
task: taskObj[key].Task,
time: taskObj[key].Time,
});
}
setTasks(loadedTasks);
};
const { sendRequest: retrieveTasks } = useHttp();
const refresh = retrieveTasks(
{
url: "https://myurl.com/alltasks.json",
},
renderTasks
);
// Loads tasks
// useEffect(() => {
// retrieveTasks(
// {
// url: "https://myurl.com/alltasks.json",
// },
// renderTasks
// );
// }, []);
// Takes input value from task form and sends it to Firebase
const enteredDateRef = useRef();
const enteredClientRef = useRef();
const enteredTaskRef = useRef();
const enteredTimeRef = useRef();
const renderNewTask = (data) => {
const taskData = {
id: data.name,
date: data.Date,
client: data.Client,
task: data.Task,
time: data.Time,
};
setTasks((tasks) => [taskData, ...tasks]);
};
const { sendRequest: postTask } = useHttp();
const submitTaskHandler = async (event) => {
event.preventDefault();
const enteredDate = enteredDateRef.current.value;
const enteredClient = enteredClientRef.current.value;
const enteredTask = enteredTaskRef.current.value;
const enteredTime = enteredTimeRef.current.value;
const newTaskData = {
Date: enteredDate,
Client: enteredClient,
Task: enteredTask,
Time: enteredTime,
};
postTask(
{
url: "https://myurl.com/alltasks.json",
method: "POST",
body: newTaskData,
headers: { "Content-Type": "application.json" },
},
renderNewTask
);
};
return (
<Fragment>
<TasksHeader />
<section className="w-full mt-4">
<CardDark backgroundType="liquid">
<h2>This month alone</h2>
<div className="flex mt-4 justify-between">
<div>
<p className=" text-6xl font-medium">56.4</p>
<p className="mt-2">Hours input</p>
</div>
<div className="border border-white"></div>
<div>
<p className=" text-6xl font-medium">1,378.45 €</p>
<p className="mt-2">Cash generated</p>
</div>
<div className="border border-white"></div>
<div>
<p className=" text-6xl font-medium">128</p>
<p className="mt-2">Tasks input</p>
</div>
</div>
</CardDark>
</section>
<section>
<SearchBar />
<div className="flex justify-between mt-4">
<h2>Your tasks</h2>
<div className="flex">
<h2 className="mr-2">Filters:</h2>
<form className="text-gray-400" action="">
<input type="date" className=" bg-gray-50 w-[120px] mr-2" />
<input type="date" className=" bg-gray-50 w-[120px] mr-2" />
<select id="cars" name="cars" className="bg-gray-50">
<option value="volvo">Volvo</option>
<option value="saab">Saab</option>
<option value="fiat">Fiat</option>
<option value="audi">Audi</option>
</select>
</form>
</div>
</div>
</section>
<section>
<CardWhite>
<form
action=""
onSubmit={submitTaskHandler}
className="h-[50px] flex"
>
<input
ref={enteredDateRef}
type="date"
className="h-[50px] focus:outline-none w-1/5 rounded-l-2xl border border-gray-300 px-4"
/>
<input
ref={enteredClientRef}
type="text"
className="h-[50px] focus:outline-none w-1/5 border-y border-gray-300 px-4"
placeholder="Client name"
/>
<input
ref={enteredTaskRef}
type="text"
className="h-[50px] focus:outline-none grow border-y border-l border-gray-300 px-4"
placeholder="Task"
/>
<div className="h-[50px] flex w-1/10 rounded-r-2xl border border-gray-300 pl-4">
<input
ref={enteredTimeRef}
type="time"
className="focus:outline-none h-full"
/>
<button type="submit" className="h-full p-2">
<img src={SubmitIcon} className="h-full" alt="" />
</button>
</div>
</form>
<List
tasks={tasks}
refresh={refresh}
/>
</CardWhite>
</section>
</Fragment>
);
};
export default AllTasks;
and here's the custom hook:
const useHttp = () => {
const sendRequest = async (requestConfig, applyData) => {
const response = await fetch(requestConfig.url, {
method: requestConfig.method ? requestConfig.method : "GET",
body: requestConfig.body ? JSON.stringify(requestConfig.body) : null,
headers: requestConfig.headers ? requestConfig.headers : {},
});
const responseData = await response.json();
applyData(responseData);
console.log('request');
};
return { sendRequest };
};
export default useHttp;
I'm guessing it has something to do with the way the custom hook is called inside the component, but I'm completely lost. I don't even know what triggers it in the first place. Can anyone help me? My goal is to trigger it once, when the page loads and when I submit a new task with the form.
I put the whole code since I don't really where the problems are.
Thanks in advance.

Related

How to target only one element from row React

How do I edit the item I clicked in the table? When I click on a specific '+' sign I want only it to change but instead everything in the row changes with it.
My main page:
'use client'
import React, { useEffect, forwardRef, useState, useRef } from 'react'
import moment from 'moment'
import DatePicker, { CalendarContainer } from "react-datepicker";
import "react-datepicker/dist/react-datepicker.css";
import "components/Calendar/datepicker.scss"
import staff from 'data/users/staff.json';
import shifts from 'data/shifts/shifts.json';
import timetable from './page.module.scss';
import formStyle from 'components/Forms/Form.module.scss';
import AddShiftDay from 'components/Forms/Others/AddShiftDay';
/* eslint-disable react/display-name */
export default function TimeTable() {
const [startDate, setStartDate] = useState(new Date());
//
const [employeeShift, setEmployeeShift] = useState<any>('+');
const days = useRef<any>(null)
//Filter
const [searchInput, setSearchInput] = useState('');
const [filteredResults, setFilteredResults] = useState<any>(staff);
//Open
const [ShiftDay, setShiftDay] = useState(false);
//Edit/Add
const [editShift, setEditShift] = useState<any>({})
useEffect(() => {
let result = [...staff];
if (searchInput) {
result = result.filter((item) => Object.values(item).join(' ').toLowerCase().includes(searchInput.toLowerCase()));
}
setFilteredResults(result);
}, [searchInput])
const getCurPerson = (e:any) => {
document.body.classList.add('mso');
const getID = e.target.closest('.person').getAttribute('data-id');
const day = e.target.getAttribute('data-id');
const options:any = { month: "long" };
const month = new Intl.DateTimeFormat("en-US", options).format(startDate)
const curPerson = staff.find(s => s.id == getID)
const date = `${day} ${month} ${startDate.getFullYear()}`
let d = [...days.current.children]
d.forEach(dday => {
if (dday.getAttribute('data-id'), day) {
setEditShift({...curPerson, date: date, day: dday.getAttribute('data-id')})
}
})
}
const openEdit = (e:any) => {
setShiftDay(!ShiftDay)
getCurPerson(e)
}
const handleSave = (e:any) => {
const getID = e.target.closest('form').getAttribute('data-id');
const curPerson = filteredResults.find(s => s.id == getID)
const newState = filteredResults.map((obj:any) => {
// 👇️ if id equals 2, update country property
if (obj.id === editShift.id) {
return {...obj, day: 'ASD'};
}
// 👇️ otherwise return the object as is
return obj;
});
setFilteredResults(newState);
eventClose()
}
console.log(editShift)
const eventClose = () => {
setShiftDay(false);
document.body.classList.remove('mso')
}
const monthDays = Array.from(Array(moment(startDate).daysInMonth()).keys())
const ToggleDatePicker = forwardRef(({ value, onClick }:any, ref:any) => (
<button className="toggle-date-picker" onClick={onClick} ref={ref}>
{value}
</button>
))
return <>
<section>
<header className="grid grid-cols-2 lg:grid-cols-11 gap-20 mb-20 lg:mb-40 items-center">
<h1 className="order-1 lg:col-span-2">Time Table <strong className="text-grey-light"></strong></h1>
<div className="order-3 lg:order-2 col-span-2 lg:col-span-7 text-center flex flex-col">
<DatePicker
selected={startDate}
onChange={(date: Date) => setStartDate(date)}
dateFormat="LLLL - yyyy"
customInput={<ToggleDatePicker />}
showMonthYearPicker
calendarClassName="requirement-calendar"
className="requirement-toggle"
/>
</div>
<div className='order-2 lg:order-3 lg:col-span-2'>
</div>
</header>
<div className="w-full h-full rounded-8 shadow-DS overflow-auto">
<div className={`${timetable.timetable}`}>
<header className="grid grid-cols-12 gap-10 items-center sticky top-0 bg-white">
<div className="col-span-3 pl-10 ">
<form
className={`${formStyle.Form} ${formStyle.Form_filter} !mb-0`}
action=""
>
<input
className='!mb-0'
type="search"
name=""
placeholder="Search"
onChange={(e:any) => setSearchInput(e.target.value)}
/>
</form>
</div>
<div className="col-span-9 text-center">
<div className='flex justify-between'>
{monthDays.map((day) => (
<span className={`${timetable.Day}`} key={day + 1}>
{day + 1}
</span>
))}
</div>
</div>
</header>
<AddShiftDay
person={editShift}
style={`${formStyle.Form} ${ShiftDay ? `${formStyle.active}` : ''}`}
setEmployeeShift={setEmployeeShift}
eventClose={eventClose}
eventSave={handleSave}
shifts={shifts}
/>
{filteredResults.map((person, i) => {
return (
<div
key={i}
data-id={person.id}
className={`person ${timetable.Person}`}
>
<div className="grid grid-cols-2 gap-10 col-span-3 items-center pl-10">
<p>{person.first_name} {person.last_name }</p>
<div className='text-12'>
<p><strong className='text-primary'>{person.working_hours}</strong> hrs/month</p>
<p><strong className='text-secondary'>-10</strong> hours left</p>
<p>{person.holidays} Holidays left</p>
</div>
</div>
<div className="col-span-9">
<div ref={days} className='flex justify-between'>
{monthDays.map((day) => (
<button
key={day + 1}
data-id={day+1}
type="button"
className={`${timetable.Person_day}`}
onClick={openEdit}
>
{person.day ? person.day : '+'}
</button>
))}
</div>
</div>
</div>
)
})}
</div>
</div>
</section>
</>
}
and for the editing I use a form which is this:
'use client'
import { FormEvent, useState } from 'react';
import styles from "../Form.module.scss";
import CancelButton from 'components/Buttons/CancelButton'
import PrimaryButton from 'components/Buttons/PrimaryButton'
import save from 'images/icons/save.svg';
const AddShiftDay = ({person, style, eventSave, eventClose, shifts, setEmployeeShift}:any) => {
const [shift, setShift] = useState('');
const handleSubmit = (e: FormEvent) => {
e.preventDefault()
setEmployeeShift(shift)
}
return <>
<form
onSubmit={handleSubmit}
className={`${style} xxl:h-auto overflow-auto`}
data-id={person.id}
>
<header className="sticky top-0 bg-secondary text-white py-20 text-center z-1">
<p>Add Shift</p>
</header>
<div className='p-24'>
<p className='text-secondary font-b mb-10'>{person.date}</p>
<p className="text-20 font-b mb-10">{person.first_name} {person.last_name}</p>
<p className='text-grey-light mb-20'>ID {person.id}</p>
<label>Shift</label>
<div className={`${styles.select} before:content-[url("/arrows/arrow-sub.svg")]`}>
<select name="shift" onChange={(e) => setShift(e.target.value)}>
{shifts.map((shift:any) => (
<option key={shift.name} value={shift.name}>{shift.name}</option>
))}
</select>
</div>
<div className="grid grid-cols-2 gap-24 items-center">
<CancelButton
text='Cancel'
style='text-left'
event={eventClose}
></CancelButton>
<PrimaryButton
type='submit'
text="Save"
style="py-12 px-24"
event={eventSave}
icon={save}
alt='save'
></PrimaryButton>
</div>
</div>
</form>
</>
}
export default AddShiftDay
I basically map all the users and inside I have another map for all the days of the month. Cannot understand why it updates the whole row and not a single box instead.

is this a result of firestore latency or normal behavior

I have a form I am using to allow users to add comments to my site. The form has an input field, a textarea field, and a button. When the button is clicked it runs my addComment() function which adds the name, comment, and timestamp to my firestore collection as a new doc.
It seems like after I click the button to add a comment I have to wait a few seconds before I can post another one. If I try to add a new comment too quickly then request doesn't get sent to my firestore collection, but if I wait a few seconds everything works as expected.
I am curious if this is normal behavior? How can I set it up so users can always post comments without having to wait a few seconds? Can someone explain to me what is happening?
Thanks
Update:
I have been doing some debugging, and I have noticed that both of the functions getVisitorCount() and getUserComments() from the first useEffect run every time I type something into the name or comment input boxes. I have attached screenshots to showcase what is happening.
On the first initial load of the app:
After typing something in the name input box:
Finally, typing something into the comment box as well:
This is not the desired behavior I want these two functions should not be running when I am typing something into either text field. The getUserComments function should only run on the initial render of the app, and whenever the add comment button is clicked.
Could this be what is causing the problems I am experiencing?
import React, { useState, useEffect } from "react";
import { NavBar, Footer, Home, About } from "./imports";
import { BrowserRouter as Router, Route, Routes } from "react-router-dom";
import { db } from "./firebase-config";
import {
collection,
getDocs,
doc,
updateDoc,
addDoc,
Timestamp,
} from "firebase/firestore";
export default function App() {
const [formData, setFormdata] = useState([]);
const [numberOfVisitors, setnumberOfVistors] = useState([]);
const [userComments, setUserComments] = useState([]);
const portfolioStatsRef = collection(db, "portfolio-stats");
const userCommentsRef = collection(db, "user-comments");
const currentNumberOfVisitors = numberOfVisitors.map((visitors) => {
return (
<h2 className="p-3 mb-0 bg-dark bg-gradient text-white" key={visitors.id}>
Number of vistors: {visitors.visitor_count}
</h2>
);
});
const listOfUserComments = userComments.map((comment) => {
return (
<li className="list-group-item" key={comment.id}>
<div className="d-flex w-100 justify-content-center">
<h5 className="mb-1">{comment.name}</h5>
<small>{comment.date.toDate().toString()}</small>
</div>
<p className="d-flex justify-content-center mb-1">{comment.comment}</p>
</li>
);
});
useEffect(() => {
const getVisitorCount = async () => {
const dataFromPortfolioStatsCollection = await getDocs(portfolioStatsRef);
setnumberOfVistors(
dataFromPortfolioStatsCollection.docs.map((doc) => {
return { ...doc.data(), id: doc.id };
})
);
};
const getUserComments = async () => {
const dataFromUserCommentsCollection = await getDocs(userCommentsRef);
setUserComments(
dataFromUserCommentsCollection.docs.map((doc) => {
return { ...doc.data(), id: doc.id };
})
);
};
getVisitorCount();
getUserComments();
}, [numberOfVisitors, portfolioStatsRef, userCommentsRef]);
useEffect(() => {
const updateVisitorCount = async () => {
const portfolioStatsDoc = doc(
db,
"portfolio-stats",
numberOfVisitors[0].id
);
const updatedFields = {
visitor_count: numberOfVisitors[0].visitor_count + 1,
};
await updateDoc(portfolioStatsDoc, updatedFields);
};
if (!numberOfVisitors.length) return;
let sessionKey = sessionStorage.getItem("sessionKey");
if (sessionKey === null) {
sessionStorage.setItem("sessionKey", "randomString");
updateVisitorCount();
}
}, [numberOfVisitors]);
const handleFormData = (event) => {
setFormdata((prevFormData) => {
return {
...prevFormData,
[event.target.name]: event.target.value,
};
});
};
const addComment = async () => {
const newComment = {
name: formData.name,
comment: formData.comment,
date: Timestamp.now(),
};
await addDoc(userCommentsRef, newComment);
};
return (
<>
<div className="d-flex flex-column overflow-hidden min-vh-100 vh-100">
<NavBar />
<div className="row">
<div className="col text-center">
{numberOfVisitors.length === 0 && (
<h2 className="p-3 mb-0 bg-dark bg-gradient text-danger">
Sorry, the Firestore free tier quota has been met for today.
Please come back tomorrow to see portfilio stats.
</h2>
)}
{currentNumberOfVisitors}
</div>
</div>
<div className="bg-image">
<div className="postion-relative">
<main className="flex-grow-1">
<div className="container-fluid p-0">
<Router>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
</Routes>
<div className="row">
<div className="center-items col">
<h4 className="">Comments</h4>
</div>
</div>
<div className="row">
<div className="center-items col">
<div className="comments-container">
{userComments.length === 0 && (
<h4 className="text-danger bg-dark m-1 p-1">
Sorry, the Firestore free tier quota has been met
for today. Please come back tomorrow to see
portfilio comments.
</h4>
)}
{listOfUserComments}
</div>
</div>
</div>
<div className="row">
<div className="center-items col">
<h4 className="text-dark">Leave a comment</h4>
</div>
</div>
<div className="row">
<div className="center-items col">
<form className="comment-form">
<div className="form-floating mb-3">
<input
type="text"
className="bg-transparent form-control"
id="floatingInput"
name="name"
onChange={handleFormData}
/>
<label htmlFor="floatingInput">Name</label>
</div>
<div className="form-floating">
<textarea
className="form-textarea-field bg-transparent form-control mb-1"
name="comment"
id="floatingTextarea"
onChange={handleFormData}
/>
<label htmlFor="floatingTextarea">Comment</label>
</div>
<div className="d-grid">
<button
className="btn btn-primary mb-4"
onClick={() => addComment()}
>
Add Comment
</button>
</div>
</form>
</div>
</div>
</Router>
</div>
</main>
</div>
</div>
<Footer />
</div>
</>
);
}

How Can i send image and text user information in react js

Sign up Form Written in JSX
import './SignUp-Form-CSS.css'
import { Link } from 'react-router-dom';
import { useState, useContext } from 'react';
import AuthContext from '../Context_store/AuthContext/AuthContext';
const SignUp = () => {
const [name, setName] = useState(null);
const [password, setPassword] = useState(null);
const [confirmPassword, setConfirmPassword] = useState(null);
const [image, setImage] = useState(null);
const authCtx = useContext(AuthContext);
const nameChangeHandeler = (event) => {
setName(event.target.value)
}
const passwordChangeHandeler = (event) => {
setPassword(event.target.value)
}
const confirmPasswordChangeHandeler = (event) => {
setConfirmPassword(event.target.value);
}
const imageChangeHandeler = (event) => {
setImage(event.target.files[0]);
console.log(event.target.files[0]);
}
const onSubmitHandeler = (event) => {
event.preventDefault()
// const data = {
// username: name,
// password: password,
// confirmPassword: confirmPassword,
// image: image
// }
const data=new FormData();
data.append("name",name);
data.append("password",password);
data.append("confirmPassword",confirmPassword);
data.append("image",image);
// data.append('username',name);
console.log(data);
authCtx.signup(data)
}
return (
<div class="container">
<div class="row">
<div class="col-lg-10 col-xl-9 mx-auto">
<div class="card flex-row my-5 border-0 shadow rounded-3 overflow-hidden">
<div class="card-img-left d-none d-md-flex">
{/* <!-- Background image for card set in CSS! --> */}
</div>
<div class="card-body p-4 p-sm-5">
<h5 class="card-title text-center mb-5 fw-light fs-5">Register</h5>
<form onSubmit={onSubmitHandeler} encType='multipart/form-data' >
<div class="form-floating mb-3">
<input type="text" class="form-control"
id="floatingInputUsername"
onChange={nameChangeHandeler}
placeholder="myusername" required autofocus />
<label for="floatingInputUsername">Username</label>
</div>
{/* <div class="form-floating mb-3">
<input type="email" class="form-control" id="floatingInputEmail" placeholder="name#example.com" />
<label for="floatingInputEmail">Email address</label>
</div> */}
<hr />
<div class="form-floating mb-3">
<input type="password"
class="form-control"
onChange={passwordChangeHandeler}
id="floatingPassword" placeholder="Password" />
<label for="floatingPassword">Password</label>
</div>
<div class="form-floating mb-3">
<input type="password" class="form-control"
onChange={confirmPasswordChangeHandeler}
id="floatingPasswordConfirm" placeholder="Confirm Password" />
<label for="floatingPasswordConfirm">Confirm Password</label>
</div>
<div>
<label>Select Logo </label>
<input name='image' onChange={imageChangeHandeler} type="file" class="form-control my-4" id="logo" placeholder="Select Logo " />
</div>
<div class="d-grid mb-2">
<button class="btn btn-lg btn-primary btn-login fw-bold text-uppercase" type="submit">Register</button>
</div>
<a class="d-block text-center mt-2 small" >Have an account?<Link class="nav-link" to={'/login'}> Sign In</Link></a>
<hr class="my-4" />
<div class="d-grid mb-2">
<button class="btn btn-lg btn-google btn-login fw-bold text-uppercase" type="submit">
<i class="fab fa-google me-2"></i> Sign up with Google
</button>
</div>
<div class="d-grid">
<button class="btn btn-lg btn-facebook btn-login fw-bold text-uppercase"
// onClick={onSubmitHandeler}
type="submit">
<i class="fab fa-facebook-f me-2"></i> Sign up with Facebook
</button>
</div>
</form>
</div>
</div>
</div>
</div>
</div>
)
}
export default SignUp;
CONTEXT API
import React, { useState } from "react";
import AuthContext from "./AuthContext";
const AuthContextProvider = (props) => {
const [token, setToken] = useState(null);
const login = (loginDetails) => {
}
const signUp = (signUpDetails) => {
console.log("Sign Up Called ");
fetch('http://localhost:5000/register',
{
method:'POST',
body:signUpDetails
// body:JSON.stringify(signUpDetails),
// headers:{
// 'Content-Type': 'application/json'
// }
}).then((resp) => {
return resp.json()
}).then((data) => {
console.log(data);
return data;
}).catch((err) => {
console.log(err);
})
}
const logout = () => {
}
const values = {
login: login,
signup: signUp,
logout: logout,
token: {
token: token,
setToken: setToken
},
isLoggedIn: !!token
}
return (
<div>
<AuthContext.Provider value={values}>
{props.children}
</AuthContext.Provider>
</div>
)
}
export default AuthContextProvider;
But When AT Node server End All Other filed Is recieved as Empty Except image Data
OUTPUT OF DATA SAVED IN DATABASE
And If I follow the Approach in Which I use Simple Object as the data instead of form Data(), I and with header ( that are under // ) I do not receive images at the backend and only receive user info
One solution could be to send the image as a string (base64) to the backend to save the image.
try to implement this in your react:
import { useState } from "react";
import "./styles.css";
export default function App() {
const [img, setImg] = useState("Image string will come here");
const handleChangeImg = (e) => {
console.log(e.target.files[0]);
const reader = new FileReader();
reader.readAsDataURL(e.target.files[0]);
reader.onloadend = () => {
setImg(reader.result);
};
};
return (
<div className="App">
<h1>Hello CodeSandbox</h1>
<h2>Start editing to see some magic happen!</h2>
<input onChange={handleChangeImg} type="file" name="image" />
<button onClick={()=>setImg("Image string will come here")}>Reset</button>
<h1>{img}</h1>
</div>
);
}
Check the code iin codesandbox here.
Related resources:
FileReader

React useState - save variables to local storage

I'm new in React and state hooks.I'm trying to make a website about creating booklist and save it to local storage. Also uploading image to cloudinary. My problem is ; when i trying to save cloudinary image url to the localstorage, it saves previous url. I think i have a problem about useState hooks but i couldnt figure it. Below is my code.
LocalStorage.js
import React, { useState, useEffect } from 'react'
import BookList from '../../components/BookList'
import CreateBook from '../../components/CreateBook'
const getLocalStorage = () => {
let list = localStorage.getItem('liste')
if (list) {
return JSON.parse(localStorage.getItem('liste'))
} else {
return []
}
}
const LocalStorage = () => {
//state hooks
const [list, setList] = useState(getLocalStorage)
const [bookName, setBookName] = useState('')
const [writerName, setWriterName] = useState('')
const [pageNumber, setPageNumber] = useState('')
const [info, setInfo] = useState('')
const [image, setImage] = useState('')
const [uploadUrl, setUploadUrl] = useState('')
let id
//Function for submit button
const handleSubmit = async (e) => {
e.preventDefault()
// conditions for fill the blanks
if (!bookName || !writerName || !pageNumber || !image) {
setInfo('Please fill the blanks')
} else {
try {
const data = new FormData()
data.append('file', image)
data.append('upload_preset', 'book-list-project')
data.append('cloud_name', 'book-list')
let response = await fetch(
'https://api.cloudinary.com/v1_1/book-list/image/upload',
{
method: 'POST',
body: data,
}
)
let result = await response.json()
setUploadUrl(result.url)
id = new Date().getTime().toString()
const newBook = {
id: id,
bookName: bookName,
writerName: writerName,
pageNumber: pageNumber,
uploadUrl: uploadUrl,
}
setList([...list, newBook])
setBookName('')
setWriterName('')
setPageNumber('')
setInfo('Book created')
setImage('')
} catch (error) {
console.log(error)
}
}
}
//Function for remove specific book from local storage
const removeSpecificBook = (id) => {
setList(list.filter((book) => book.id !== id))
}
// Function for clear all books from local storage
const removeAllBooks = () => {
setList([])
}
useEffect(() => {
localStorage.setItem('liste', JSON.stringify(list))
}, [list])
return (
<div>
<CreateBook
bookName={bookName}
writerName={writerName}
pageNumber={pageNumber}
handleSubmit={handleSubmit}
info={info}
setBookName={setBookName}
setWriterName={setWriterName}
setPageNumber={setPageNumber}
setImage={setImage}
/>
<BookList
items={list}
removeSpecificBook={removeSpecificBook}
removeAllBooks={removeAllBooks}
/>
</div>
)
}
export default LocalStorage
Booklist.js
import React from 'react'
const BookList = ({ items, removeSpecificBook, removeAllBooks }) => {
return (
<div className='container mx-auto'>
<div className='mt-20 flex flex-wrap items-center justify-center'>
{items.map((item) => {
return (
<div key={item.id} className='p-2 m-2 bg-yellow-100 w-1/4'>
<div className='p-1 m-1 flex justify-center'>
<img
className='object-contain h-52 w-52'
src={item.uploadUrl}
alt='some img'
/>
</div>
<div className='p-1 m-1'>
<h5 className='font-semibold'>Book Name</h5>
<h3>{item.bookName}</h3>
</div>
<div className='p-1 m-1'>
<h5 className='font-semibold'>Writer Name</h5>
<h3>{item.writerName}</h3>
</div>
<div className='p-1 m-1'>
<h5 className='font-semibold'>Total Page</h5>
<h3>{item.pageNumber}</h3>
</div>
<div className='flex justify-end'>
<button
onClick={() => removeSpecificBook(item.id)}
className='px-4 py-2 bg-red-500 rounded-full text-white'
>
Remove
</button>
</div>
</div>
)
})}
</div>
{items.length > 1 && (
<div className='flex justify-center my-5'>
<button
onClick={removeAllBooks}
className='px-8 py-4 bg-red-500 rounded-full text-white'
>
Remove All
</button>
</div>
)}
</div>
)
}
export default BookList
CreateBook.js
import React from 'react'
const CreateBook = ({
bookName,
writerName,
pageNumber,
handleSubmit,
info,
setBookName,
setWriterName,
setPageNumber,
setImage,
}) => {
return (
<div>
<div>
<nav className='bg-blue-500 text-center text-white px-6 py-3'>
Create Book
</nav>
</div>
<div className='bg-red-200 mx-auto w-96 rounded-lg flex justify-center mt-20'>
<form onSubmit={handleSubmit}>
<div>
<div className='p-3 text-center'>
<h6>Enter Book Name</h6>
<input
value={bookName}
onChange={(e) => setBookName(e.target.value)}
className='rounded-md'
type='text'
placeholder='Book Name'
/>
</div>
<div className='p-3 text-center'>
<h6>Enter Writer Name</h6>
<input
value={writerName}
onChange={(e) => setWriterName(e.target.value)}
className='rounded-md'
type='text'
placeholder='Writer Name'
/>
</div>
<div className='p-3 text-center'>
<h6>Enter Total Page Number </h6>
<input
value={pageNumber}
onChange={(e) => setPageNumber(e.target.value)}
className='rounded-md'
type='number'
placeholder='Page Number'
/>
</div>
<div className='p-3 text-center'>
<div>
<h6>Upload Image</h6>
</div>
<div className='p-3'>
<input
type='file'
onChange={(e) => setImage(e.target.files[0])}
/>
</div>
</div>
<div className='flex justify-center p-3'>
<button className='bg-blue-500 py-3 px-6 rounded-full text-white'>
Submit
</button>
</div>
<div className='p-3 text-center text-white'>
<h3>{info}</h3>
</div>
</div>
</form>
</div>
</div>
)
}
export default CreateBook
Also please if you have any suggestions about my code structure, tell me. I dont have any programming history and trying to learn from beginning. I need every suggestions to go further learning programming. Thank you in advance.
setUploadUrl(result.url);
id = new Date().getTime().toString();
const newBook = {
id: id,
bookName: bookName,
writerName: writerName,
pageNumber: pageNumber,
uploadUrl: uploadUrl
};
In here you are updating the uploadUrl to the newBook object. But the value of uploadUrl hold the previous record. Instead of setting from uploadUrl, set it from result.url.

React toggle button after mapping through list

After getting results from api call to Google books i'd like to hide the description paragraphs and have a toggle button using the css class of hidden (tailwinds css). I'm currently just console.logging the elements on the "view description" button & I'm just not sure how to target a single element after looping through the nodeList with my toggleDesc() function
React SearchBar component
import React, { useState, useEffect } from 'react';
import { FontAwesomeIcon } from '#fortawesome/react-fontawesome';
import axios from 'axios';
import { faSearch } from '#fortawesome/free-solid-svg-icons';
import SearchResult from '../search-result/search-result.component';
const SearchBar = () => {
const [searchTerm, setSearchTerm] = useState('');
const [books, setBooks] = useState({ items: [] });
useEffect(() => {
async function fetchBooks() {
const newRes = await fetch(`${API_URL}?q=${searchTerm}`);
const json = await newRes.json();
const setVis = Object.keys(json).map(item => ({
...item, isDescVisible: 'false'
}))
setBooks(setVis);
}
fetchBooks();
}, []);
const toggleDesc = (id) => {
const newBooks = books.items.map(book => book.id === id ? {...book, isDescVisible: !book.isDescVisible} : book);
console.log(newBooks);
setBooks(newBooks);
}
const onInputChange = (e) => {
setSearchTerm(e.target.value);
};
let API_URL = `https://www.googleapis.com/books/v1/volumes`;
const fetchBooks = async () => {
// Ajax call to API via axios
const result = await axios.get(`${API_URL}?q=${searchTerm}`);
setBooks(result.data);
};
// Handle submit
const onSubmitHandler = (e) => {
// prevent browser from refreshing
e.preventDefault();
// call fetch books async function
fetchBooks();
};
// Handle enter press
const handleKeyPress = (e) => {
if (e.key === 'Enter') {
e.preventDefault();
fetchBooks();
}
}
return (
<div className="search-bar p-8">
<div className="bg-white flex items-center rounded-full shadow-xl">
<input
className="rounded-l-full w-full py-4 px-6 text-gray-700 leading-tight focus:outline-none"
id="search"
type="text"
placeholder="Try 'The Hunt For Red October by Tom Clancy' "
onChange={onInputChange}
value={searchTerm}
onKeyPress={handleKeyPress}
/>
<div className="p-4">
<button
onClick={onSubmitHandler}
className="bg-blue-800 text-white rounded-full p-2 hover:bg-blue-600 focus:outline-none w-12 h-12 flex items-center justify-center"
>
<FontAwesomeIcon icon={faSearch} />
</button>
</div>
</div>
<div className='result mt-8'>
<ul>
<SearchResult books={books} toggleDesc={toggleDesc} />
</ul>
</div>
</div>
);
};
export default SearchBar;
SearchResults Component
import React from 'react';
import { LazyLoadImage } from 'react-lazy-load-image-component';
import 'react-lazy-load-image-component/src/effects/blur.css';
import './search-result.styles.scss';
const SearchResult = ({ books, toggleDesc }) => {
return (
<div className="search-result mb-6">
{books.items !== undefined &&
books.items !== null &&
books.items.map((book, index) => {
return (
<div key={index} className="book-info mb-2">
<li className="ml-4">
<div className="flex">
<LazyLoadImage
className="book-img px-4 py-2"
effect="blur"
alt={`${book.volumeInfo.title} book`}
src={`http://books.google.com/books/content?id=${book.id}&printsec=frontcover&img=1&zoom=1&source=gbs_api`}
/>
<div className="flex-1">
<h3 className="text-2xl">{book.volumeInfo.title}</h3>
<div>
<p className="flex">
<button
onClick={() => toggleDesc(book.id)}
className="bg-blue-800 mt-2 text-gray-200 rounded hover:bg-blue-400 px-4 py-3 text-sm focus:outline-none"
type="button"
>
View Description
</button>
</p>
{book.isDescVisible &&
<div
className="block border px-4 py-3 my-2 text-gray-700 desc-content"
>
<p>{book.volumeInfo.description}</p>
</div>
}
</div>
<h3 className="text-xl text-blue-800 mt-2 p-2">
Average time to read:{' '}
</h3>
</div>
</div>
<hr />
</li>
</div>
);
})}
</div>
);
};
export default SearchResult;
console
You will have to add a property to each item of your books to handle the description visibility and change it when you click the button, this is a basic example
useEffect(()=> {
fetch(url).then(res => {
const newRes = res.map(item=> ({ ...item, isDescVisible: 'false' })) // <— add the new property to all items set to false
setBooks(newRes);
})
})
<p className='flex'>
<button
onClick={() => toggleDesc(book.id)} // <—- pass the id or the whole book
className='bg-blue-800 mt-2 text-gray-200 rounded hover:bg-blue-400 px-4 py-3 text-sm focus:outline-none'
type='button'
>
View Description
</button>
</p>
//here show the desc according to the value of the property you added, no need for the hidden class
{book.isDescVisible && <div className='block border px-4 py-3 my-2 text-gray-700 desc-content'>
<p>{book.volumeInfo.description}</p>
</div>}
This function needs to be on the parent where you are setting the state
const toggleDesc = (id) => {
const newBooks = books.map(book => book.id === id ? {...book, isDescVisible: !book.isDescVisible} : book); <-- toggle the property
setBooks(newBooks); <—- save it in the state again
};

Categories

Resources