How to target only one element from row React - javascript

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.

Related

How to properly conditionally render child component in react

I have the following components : (AddBookPanel contains the AddBookForm)
I need to have the ability to display the form on click of the 'AddBookButton', and also
have the ability to hide the form while clicking the 'x' button (img in AddBookForm component), however the component doesn't seem to load properly, what could be wrong here? What do you think is the best way to organize this?
import { AddBookButton } from './AddBookButton';
import { AddBookForm } from './AddBookForm';
import { useState } from 'react';
export const AddBookPanel = () => {
const [addBookPressed, setAddBookPressed] = useState(false);
return (
<div>
<div onClick={() => setAddBookPressed(!addBookPressed)}>
<AddBookButton />
</div>
<AddBookForm initialFormActive={addBookPressed} />
</div>
);
};
import { useState } from 'react';
interface AddBookFormProps {
initialFormActive: boolean;
}
export const AddBookForm: React.FC<AddBookFormProps> = ({
initialFormActive,
}) => {
const [isFormActive, setIsFormActive] = useState(initialFormActive);
return (
<>
{isFormActive && (
<div className="fixed box-border h-2/3 w-1/3 border-4 left-1/3 top-24 bg-white border-slate-600 shadow-xl">
<div className="flex justify-between bg-slate-400">
<div>
<p className="text-4xl my-5 mx-6">Add a new Book</p>
</div>
<div className="h-16 w-16 my-3 mx-3">
<button onClick={() => setIsFormActive(!isFormActive)}>
<img
src="https://upload.wikimedia.org/wikipedia/commons/thumb/a/a0/OOjs_UI_icon_close.svg/1200px-OOjs_UI_icon_close.svg.png"
alt="close-button"
className="object-fit"
></img>
</button>
</div>
</div>
<div className="flex-col">
<div className="flex justify-between my-10 ml-5 text-xl">
<input type="text" placeholder="book name"></input>
</div>
</div>
</div>
)}
</>
);
};
You're always rendering the AddBookForm. That means in the initial render the useState is called with false. Since you never call setIsFormActive it is now stuck as "do not render".
Just don't use the useState and use the property initialFormActive directly. And maybe rename it to isFormActive. Let the parent handle the state change.
import { AddBookButton } from './AddBookButton';
import { AddBookForm } from './AddBookForm';
import { useState } from 'react';
export const AddBookPanel = () => {
const [addBookPressed, setAddBookPressed] = useState(false);
return (
<div>
<div onClick={() => setAddBookPressed(!addBookPressed)}>
<AddBookButton />
</div>
<AddBookForm initialFormActive={addBookPressed} onClose={() => setAddBookPressed(false)} />
</div>
);
};
import { useState } from 'react';
interface AddBookFormProps {
initialFormActive: boolean;
onColse: () => void;
}
export const AddBookForm: React.FC<AddBookFormProps> = ({
initialFormActive,
onClose,
}) => {
// const [isFormActive, setIsFormActive] = useState(initialFormActive); don't use this frozen state, allow the parent to control visibility
return (
<>
{initialFormActive && (
<div className="fixed box-border h-2/3 w-1/3 border-4 left-1/3 top-24 bg-white border-slate-600 shadow-xl">
<div className="flex justify-between bg-slate-400">
<div>
<p className="text-4xl my-5 mx-6">Add a new Book</p>
</div>
<div className="h-16 w-16 my-3 mx-3">
<button onClick={() => onClose()}>
<img
src="https://upload.wikimedia.org/wikipedia/commons/thumb/a/a0/OOjs_UI_icon_close.svg/1200px-OOjs_UI_icon_close.svg.png"
alt="close-button"
className="object-fit"
></img>
</button>
</div>
</div>
<div className="flex-col">
<div className="flex justify-between my-10 ml-5 text-xl">
<input type="text" placeholder="book name"></input>
</div>
</div>
</div>
)}
</>
);
};
Problem with your code is in how you tied initialFormActive to inner state of AddBookForm component. When looking at useState inside AddBookForm it is clear that prop initialFormActive only affects your initial state, and when looking at your parent component I can clearly see that you passed false as value for initialFormActive prop, thus initializing state inside AddBookForm with false value(form will stay hidden). Then you expect when you click on button, and when prop value changes(becomes true), that your <AddBookForm /> will become visible, but it will not because you just used initial value(false) from prop and after that completely decoupled from prop value change.
Since you want to control visibility outside of component, then you should attach two props to AddBookForm, isVisible and toggleVisibiliy. And do something like this:
export const AddBookPanel = () => {
const [addBookPressed, setAddBookPressed] = useState(false);
const toggleAddBookPressed = () => setAddBookPressed(p => !p);
return (
<div>
<div onClick={() => setAddBookPressed(!addBookPressed)}>
<AddBookButton />
</div>
<AddBookForm isVisible={addBookPressed} toggleVisibility={toggleAddBookPressed } />
</div>
);
};
export const AddBookForm: React.FC<AddBookFormProps> = ({
isVisible, toggleVisibility
}) => {
return (
<>
{isVisible&& (
<div className="fixed box-border h-2/3 w-1/3 border-4 left-1/3 top-24 bg-white border-slate-600 shadow-xl">
<div className="flex justify-between bg-slate-400">
<div>
<p className="text-4xl my-5 mx-6">Add a new Book</p>
</div>
<div className="h-16 w-16 my-3 mx-3">
<button onClick={toggleVisibility}>
<img
src="https://upload.wikimedia.org/wikipedia/commons/thumb/a/a0/OOjs_UI_icon_close.svg/1200px-OOjs_UI_icon_close.svg.png"
alt="close-button"
className="object-fit"
></img>
</button>
</div>
</div>
<div className="flex-col">
<div className="flex justify-between my-10 ml-5 text-xl">
<input type="text" placeholder="book name"></input>
</div>
</div>
</div>
)}
</>
);
};

Hydration error when fetching data from array of objects

I am using Nextjs and there is a sidebar in which I am trying to get random 5 posts from an array of objects. The defined function is working fine and displaying 5 posts but I am getting a Hydration error showing Prop alt did not match. When I tried to display it on the console the alt value is different.
import Link from 'next/link';
import Image from 'next/image';
import { BlogData } from '../data/blogdata';
function getMultipleRandom() {
const shuffled = [...BlogData].sort(() => 0.5 - Math.random());
return shuffled.slice(0, 5);
}
const Sidebar = () => {
return (
<>
<h2 className='font-roboto text-3xl font-semibold pb-10'>Featured Posts</h2>
{
getMultipleRandom().map((val) => {
return (
<div key={val._id} className='flex flex-col pt-5'>
<div className='w-56 pr-5'><Image src={val.featuredImage} alt={val.alt} width={1200} height={800} className=' rounded-3xl' /></div>
<Link href={`/blog/${val.slug}`}><a><h3 className='text-sm font-poppins font-medium hover:text-[#5836ed] transition-all duration-300'>{val.title}</h3></a></Link>
</div>
);
})
}
</>
)
}
export default Sidebar;
try this code :
import Link from "next/link";
import Image from "next/image";
import { BlogData } from "../data/blogdata";
import { useEffect, useState } from "react";
const Sidebar = () => {
const [shuffled, setShuffled] = useState([]);
const [loading, setLoading] = useState(true); // anyting you want !!!
useEffect(() => {
if (loading) {
const x = [...BlogData].sort(() => 0.5 - Math.random());
setShuffled(x.slice(0, 5));
setLoading(false);
}
}, [loading]);
return (
<>
<h2 className="font-roboto text-3xl font-semibold pb-10">
Featured Posts
</h2>
{loading ? <div>loading ... </div> : shuffled.map((val) => {
return (
<div key={val._id} className="flex flex-col pt-5">
<div className="w-56 pr-5">
<Image
src={val.featuredImage}
alt={val.alt}
width={1200}
height={800}
className=" rounded-3xl"
/>
</div>
<Link href={`/blog/${val.slug}`}>
<a>
<h3 className="text-sm font-poppins font-medium hover:text-[#5836ed] transition-all duration-300">
{val.title}
</h3>
</a>
</Link>
</div>
);
})}
</>
);
};
export default Sidebar;

How can i setState for color change

I already have the condition, but i need to set the state, if i setColor on the if method, give me error- Too many re-renders. React limits the number of renders to prevent an infinite loop.
State:
const [color, setColor] = useState();
Map:
{data.map((doc) => {
let verificacao = "";
if (doc.status === "Não Necessário") {
verificacao = "Proxima Verificação:";
setColor(true)
} else if (doc.status === "Verificado e Conforme") {
verificacao = "Data:";
setColor(false)
} else {
console.log("ERRO");
}
return (
The span i want to change the color:
<div className="row">
<div className="pontos">
<span className={color ? "red" : "green"}>
{doc.status}
</span>
<span className="data-status">{verificacao}</span>
{JSON.stringify(
doc.dateVerificado
.toDate()
.toISOString()
.replace(/T.*/, "")
.split("-")
.reverse()
.join("-")
)}
</div>
</div>
Full Code:
import React, { useEffect, useState } from "react";
import { Button } from "react-bootstrap";
import ManutencaoDataService from
"../../Services/ManutecaoDataService";
import "./ManutencaoInfo.css";
const ManutencaoInfo = ({ getDataId }) => {
const [data, setData] = useState([]);
const [color, setColor] = useState();
useEffect(() => {
getData();
return () => {
setData([]);
};
}, []);
const getData = async () => {
const data = await ManutencaoDataService.getAllData();
console.log(data.docs);
setData(data.docs.map((doc) => ({ ...doc.data(), id: doc.id })));
};
const deleteHandler = async (id) => {
await ManutencaoDataService.deleteData(id);
getData();
};
return (
<>
<div className=" container mb-2">
<Button variant="dark edit" onClick={getData}>
Atualizar Lista
</Button>
</div>
{/* <pre>{JSON.stringify(books, undefined, 2)}</pre>} */}
{data.map((doc) => {
let verificacao = "";
if (doc.status === "Não Necessário") {
verificacao = "Proxima Verificação:";
setColor(true)
} else if (doc.status === "Verificado e Conforme") {
verificacao = "Data:";
setColor(false)
} else {
console.log("ERRO");
}
return (
<div key={doc.id} className="container-principal">
<div className="container">
<div className="row relatorio">
<div className="col">
<div className="departamento">
<h3>{doc.departamentos}</h3>
</div>
</div>
</div>
<div className="row detalhes">
<div className="col">
<div className="row">
<div className="pontos">
<span className="identificacao">Equipamento:
</span>
{doc.equipamentos}
</div>
</div>
<div className="row">
<div className="pontos">
<span className="identificacao">Responsável:
</span>
{doc.responsaveis}
</div>
</div>
<div className="row">
<div className="pontos">
<span className="codigo">{doc.codigos.codigo}
</span>
<span className="tipo">{doc.tipo}</span>
</div>
</div>
</div>
<div className="col ">
<div className="row">
<div className="pontos">
<span className="identificacao">Data Manutenção:
</span>
{JSON.stringify(
doc.dateManutencao
.toDate()
.toISOString()
.replace(/T.*/, "")
.split("-")
.reverse()
.join("-")
)}
</div>
</div>
<div className="row">
<div className="pontos">
<span className={color ? "red" : "green"}>
{doc.status}
</span>
<span className="data-status">{verificacao}</span>
{JSON.stringify(
doc.dateVerificado
.toDate()
.toISOString()
.replace(/T.*/, "")
.split("-")
.reverse()
.join("-")
)}
</div>
</div>
<div className="row">
<div className="pontos">{doc.codigos.observacoes}
</div>
</div>
</div>
</div>
<div className="row botoes">
<div className="col">
<span className="botao-editar">
<Button
variant="secondary"
className="edit"
onClick={(e) => getDataId(doc.id)}
>
Editar
</Button>
</span>
<span className="botao-apagar">
<Button
variant="danger"
className="delete"
onClick={(e) => deleteHandler(doc.id)}
>
Apagar
</Button>
</span>
</div>
</div>
</div>
</div>
);
})}
</>
);
};
export default ManutencaoInfo;
Instead of using state, I would recommend using doc's status property for using the correct CSS class like this:
<span className={doc.status === "Não Necessário" ? "red" : "green"}>
{doc.status}
</span>
import React, { useEffect, useState } from "react";
import { Button } from "react-bootstrap";
import ManutencaoDataService from
"../../Services/ManutecaoDataService";
import "./ManutencaoInfo.css";
import classNames from 'classnames';
const ManutencaoInfo = ({ getDataId }) => {
const [data, setData] = useState([]);
useEffect(() => {
getData();
}, []);
const getData = async () => {
const data = await ManutencaoDataService.getAllData();
console.log(data.docs);
setData(data.docs.map((doc) => ({ ...doc.data(), id: doc.id })));
}
return (
<>
...
{data.map((doc) => {
return (
<div key={doc.id} className="container-principal">
<div className="container">
...
<div className="row detalhes">
...
<div className="col ">
...
<div className="row">
<div className="pontos">
<span className={classNames({red: doc.status === "Não Necessário", green:doc.status === "Verificado e Conforme" })}>
{doc.status}
</span>
<span className="data-status">{
doc.status === "Não Necessário" ? "Proxima Verificação:" : doc.status === "Verificado e Conforme" ? "Data:" :""
}</span>
....
</div>
</div>
....
</div>
</div>
...
</div>
</div>
);
})}
</>
);
};
export default ManutencaoInfo;
create a new component which renders inside the map function, it will be better to maintain
I think you are overcomplicating it with introducing state variable.
You can just use local variable in your function like this:
// Remove this state
// const [color, setColor] = useState();
{data.map((doc) => {
let verificacao = "";
let color; // add this var
if (doc.status === "Não Necessário") {
verificacao = "Proxima Verificação:";
color = 'red' // changed
} else if (doc.status === "Verificado e Conforme") {
verificacao = "Data:";
color = 'green' // changed
} else {
console.log("ERRO");
}
return (
// ...
<span className={color}> {/* changed */}
{doc.status}
</span>
// ...
)

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