Changing background based on weather description using next.js - javascript

I am trying to have it so that the background of my weather app will change based on the weather condition using nextjs and the current weather data API. But no image is display. This is my Main
import axios from "axios";
import { useState } from "react";
import Weather from "./Weather";
import Spinner from "./Spinner";
import { data } from "autoprefixer";
import Background from "./Background";
const Main = () => {
const [city, setCity] = useState("");
const [weather, setWeather] = useState({});
const [loading, setLoading] = useState(false);
//In celsius -(&units=metric) in fahrenheit -(&units=imperial)
const url = `https://api.openweathermap.org/data/2.5/weather?q=${city}&units=metric&appid=${process.env.NEXT_PUBLIC_WEATHER_KEY}`;
const fetchWeather = (e) => {
e.preventDefault();
setLoading(true);
axios.get(url).then((response) => {
setWeather(response.data);
// console.log(response.data);
});
setCity("");
setLoading(false);
};
if (loading) {
return <Spinner />;
} else {
return (
<div>
{/* Overlay, so we use a self-closing div */}
<div className="absolute top-0 left-0 right-0 bottom-0 bg-black/40 z-[1]" />
<Background
weatherDescription={data.weather ? data.weather[0].main : null}
/>
<div className="relative flex justify-between items-center max-w-[500px] w-full m-auto pt-4 text-white z-10">
<form
onSubmit={fetchWeather}
className="flex justify-between items-center w-full m-auto p-3 bg-transparent border border-gray-300 text-white rounded-2xl"
>
<div>
<input
onChange={(e) => setCity(e.target.value)}
className="bg-transparent border-none text-white focus:outline-none text-2xl"
type="text"
placeholder="Search"
/>
</div>
<button onClick={fetchWeather}>
<BsSearch size={20} />
</button>
</form>
</div>
{weather.main && <Weather data={weather} />}
</div>
);
}
};
And this is the background component
import React from "react";
import img1 from "../public/assets/Rainy weather.jpg";
import img2 from "../imgs/Cloudy Weather.jpg";
function Background(props) {
const images = [
{
name: "Rain",
background: img1,
},
{
name: "Clouds",
background: img2,
},
];
const imgURL = images.find((el) => el.name === props.weatherDescription)
?.background;
return (
<div className="-z-10">
<img
src={imgURL}
className="object-cover w-full min-h-scren"
key={imgURL}
/>
</div>
);
}
export default Background;
I've tried if statements and functions, but nothing is working, I've found this current template that was working with videos, I've tried to change it to use images, but I can't achieve it. I'm fairly new in coding, so I hope someone can help me.

Related

React Query Hook Not Running On Initial Load

Here is my React component. The hook I am trying to get to work is useGetNotesQuery.
On initial load of the component, it does not run. If I tab switch, or create a new note, then the hook will be called and the component will update. I don't have this problem with other hooks and I can't seem to figure out what I am doing wrong. I'm still pretty new to react-query. I've verified that when the hook is called on tab switch or the creation of a new note, the correct data is being passed in and the hook works as expected. It just will not run on initial load.
import React, { useState } from 'react';
import useGetNotesQuery from 'hooks/notes/useNotes';
import { useCreateNote } from 'hooks/notes/useCreateNote';
import Note from './Note';
export default function Notes({ projectItemId, itemId, itemType }: any) {
const createNote = useCreateNote();
const {
data: notes,
isLoading,
isError
} = useGetNotesQuery({ projectItemId, itemId, itemType });
const [body, setBody] = useState('');
const createNewNote = async () => {
await createNote.mutateAsync({
body,
projectItemId,
itemId,
itemType
});
setBody('');
};
return (
<section aria-labelledby="notes-title">
{!isLoading && (
<div className="sm:overflow-hidden sm:rounded-lg">
{console.log(notes)}
<div className="divide-y divide-gray-200">
<div className="px-4 py-5 sm:px-6">
<h2
id="notes-title"
className="text-lg font-medium text-gray-900"
>
Notes
</h2>
</div>
<div className="px-4 py-6 sm:px-6">
<ul role="list" className="space-y-8">
{notes?.map((note) => (
<Note key={note.id} note={note} />
))}
</ul>
</div>
</div>
<div className="bg-gray-50 px-4 py-6 sm:px-6">
<div className="flex space-x-3">
{/* <div className="flex-shrink-0">
<img
className="h-10 w-10 rounded-full"
src={user.imageUrl}
alt=""
/>
</div> */}
<div className="min-w-0 flex-1">
<form action="#">
<div>
<label htmlFor="comment" className="sr-only">
About
</label>
<textarea
id="comment"
name="comment"
rows={3}
className="block w-full p-2 rounded-md border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500 sm:text-sm text-black"
placeholder="Add a note"
value={body}
onChange={(e) => setBody(e.target.value)}
/>
</div>
<div className="mt-3">
<button
type="submit"
onClick={(e) => {
e.preventDefault();
createNewNote();
}}
className="inline-flex items-center justify-center rounded-md border border-transparent bg-blue-600 px-4 py-2 text-sm font-medium text-white shadow-sm hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2"
>
Comment
</button>
</div>
</form>
</div>
</div>
</div>
</div>
)}
</section>
);
}
Here is the hook
import { getNotesById } from './../../queries/notes/get-notes';
import { useQuery } from 'react-query';
function useGetNotesQuery({ projectItemId, itemId, itemType }: any) {
return useQuery('notes', async () => {
if (projectItemId) {
return getNotesById({ projectItemId, itemId, itemType }).then(
(result) => result.data
);
}
});
}
export default useGetNotesQuery;
My _app.tsx file in NextJS
import 'styles/main.css';
import 'styles/chrome-bug.css';
import '#/styles/tailwind.css';
import 'katex/dist/katex.css';
import '../styles/globals.css';
import 'react-datepicker/dist/react-datepicker.css';
import { useEffect, useState } from 'react';
import React from 'react';
import { SessionContextProvider } from '#supabase/auth-helpers-react';
import { createBrowserSupabaseClient } from '#supabase/auth-helpers-nextjs';
import { AppProps } from 'next/app';
import { MyUserContextProvider } from 'utils/useUser';
import type { Database } from 'types_db';
import { SidebarProvider } from 'context/SidebarContext';
import { QueryClient, QueryClientProvider } from 'react-query';
// import { ReactQueryDevtools } from 'react-query/devtools';
import { ThemeProvider } from 'next-themes';
const queryClient = new QueryClient({
defaultOptions: {
queries: {
retry: 3
}
}
});
export default function MyApp({ Component, pageProps }: AppProps) {
const [initialContext, setInitialContext] = useState();
const [supabaseClient] = useState(() =>
createBrowserSupabaseClient<Database>()
);
useEffect(() => {
document.body.classList?.remove('loading');
}, []);
return (
<QueryClientProvider client={queryClient}>
<SessionContextProvider supabaseClient={supabaseClient}>
<MyUserContextProvider initial={initialContext}>
<SidebarProvider>
<ThemeProvider
attribute="class"
enableColorScheme={false}
defaultTheme="light"
>
<Component {...pageProps} />
</ThemeProvider>
{/* <ReactQueryDevtools initialIsOpen={false} /> */}
</SidebarProvider>
</MyUserContextProvider>
</SessionContextProvider>
</QueryClientProvider>
);
}
What do your other hooke return? Your query getNotesById is returning result.data, but the useQuery structure expects a parsed result object, like the example below:
const fetchUsers = async () => {
const res = await fetch("https://jsonplaceholder.typicode.com/users");
return res.json();
};
const response = useQuery("users", fetchUsers);

problem in sending or loading data to localstorage, when I do refresh- cart data is not there to show. I am loading data from firebase

I am loading data from firebase, so when I refresh, it takes time to load it. And added data are not present in the cart that,s why I am not getting any product in the cart. But I did follow the same process to store user data, and it,s working fine, which means I am getting a registered profile in console. Can anyone suggest me a solution for getting data in the cart?
sending data:
import { motion } from "framer-motion";
import React, { useEffect, useRef, useState } from "react";
import { MdShoppingBasket } from "react-icons/md";
import { actionType } from "../context/reducer";
import { useGlobalState } from "../context/stateProvider";
import NotFound from "../img/NotFound.svg";
const RowContainer = ({ flag, data, scrollValue }) => {
const [{ cartItems }, dispatch] = useGlobalState();
const [items, setItems] = useState([]);
const rowContainer = useRef();
const addToCart = () => {
dispatch({
type: actionType.SET_CARTITEMS,
cartItems: items,
});
localStorage.setItem("cartItems", JSON.stringify(items));
};
useEffect(() => {
addToCart();
}, [items]);
useEffect(() => {
rowContainer.current.scrollLeft += scrollValue;
}, [scrollValue]);
return (
<div
ref={rowContainer}
className={`w-full my-12 flex items-center ${
flag
? "overflow-x-scroll scrollbar-none scroll-smooth"
: "overflow-x-hidden flex-wrap justify-center"
}`}
>
{data && data.length > 0 ? (
data.map((item) => {
const { id, price, imageURL, calories, title } = item;
return (
<div
key={id}
className="w-350 min-w-[300px] md:min-w-[380px] md:my-10 backdrop-blur-lg mx-1 my-2 lg:mx-2 "
>
<div className="w-full flex flex-row items-center justify-between bg-white rounded-lg drop-shadow-lg py-2 px-4 hover:bg-whiteAlpha min-h-[150px]">
<motion.img
whileHover={{ scale: 1.2 }}
src={imageURL}
alt="img"
className="w-30 max-h-40 -mt-8 duration-100 drop-shadow-2xl"
/>
<div className="flex flex-col items-end justify-end gap-4">
<motion.div
whileTap={{ scale: 0.75 }}
className="w-8 h-8 rounded-md bg-orange-500 hover:bg-orange-600 "
onClick={() => setItems([...new Set(cartItems), item])}
>
<MdShoppingBasket className="text-white text-2xl m-1" />
</motion.div>
<div className=" w-full">
<p className="text-gray-800 md:text-lg text-base text-right">
{title}
</p>
<p className="text-sm text-gray-500 text-right">
<span className="text-orange-600">$</span> {price}
</p>
</div>
</div>
</div>
</div>
);
})
) : (
<div className="w-full flex flex-col items-center justify-center">
<img src={NotFound} className="h-340" alt="" />
<p className="text-center my-2 text-xl text-red-500">No data found</p>
</div>
)}
</div>
);
};
export default RowContainer;
fetching data
export const fetchCart = () => {
const cartInfo =
localStorage.getItem("cartItems") !== "undefined"
? JSON.parse(localStorage.getItem("cartItems"))
: localStorage.clear();
return cartInfo ? cartInfo : [];
};
You are clearing your data for no reason
localStorage.getItem("cartItems") !== "undefined"
should be
localStorage.getItem("cartItems") !== undefined

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;

is there a way to change form values on submit

i have a situation where there are two buttons that i want acting as a submit.
how can i make the value of the zip code (inside formik) be combined with the value of the option (button onclick), e.g. {"option": "adult", "zip_code": "00000"}
i've found this: https://github.com/formium/formik/issues/467#issuecomment-369833667
but it's for a class component, and doesn't show how to get the button value into formik values...
import React, { useState, useEffect } from 'react';
import { Form, Formik, useField } from 'formik';
import * as Yup from 'yup';
const FORM_INPUT_BOX = "m-2 border border-gray-300 bg-white text-gray-700 placeholder-gray-400 shadow-sm text-base focus:outline-none focus:ring-2 focus:ring-yellow-500 focus:border-transparent"
const FORM_INPUT_LABEL = "ml-2 text-gray-700"
const FORM_INPUT_ERROR = "break-normal relative bottom-2 left-2 text-sm text-red-500"
const FORM_DROPDOWN = "m-2 focus:ring-yellow-500 border border-gray-300 focus:border-yellow-500 h-full border-transparent bg-transparent text-gray-500 sm:text-sm rounded"
export const FORM_BUTTON = "ml-2 mr-2 py-1 border border-transparent text-medium rounded-md text-white bg-gray-600 hover:bg-gray-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-gray-500"
const CustomTextInput = ({ label, ...props }) => {
const [field, meta] =useField(props); //using the hook useField, and destructuring
return(
<> {/*so we don't return any div tags - its just a react fragment */}
<label htmlFor={props.id || props.name} className={FORM_INPUT_LABEL}> {label} </label>
<input className={FORM_INPUT_BOX}{...field}{...props} />
{meta.touched && meta.error ? (
<div className={FORM_INPUT_ERROR}>{meta.error}</div>
) : null}
</>
)
}
const ButtonMaker = (props) => {
const {mainprops,userprops,adoptprops,...optionprops} = props
return(
<>
<button className={FORM_BUTTON} id={optionprops.id} onClick={()=>{optionprops.setButtonClick(true);
optionprops.setOption(optionprops.id)}}
>{optionprops.label}</button>
</>
)
};
export default function RegistrationForm(props) {
const {mainprops,userprops,...adoptprops} = props;
const [buttonClick, setButtonClick] = useState(false);
const [option, setOption] = useState('')
const allprops = {
mainprops,
userprops,
adoptprops,
buttonClick,
setButtonClick,
setOption
};
useEffect(()=>{
if (buttonClick){
alert(option)};
},[buttonClick])
// https://regex101.com/
const zipRegExp= /^([0-9]{5})$/;
//zipRegExp: only 5 digits
const zipRegExp_plus4= /^([0-9]{5})(?:[-\s]*([0-9]{4}))?$/;
return (
<Formik
initialValues={{
zip_code : ''
}}
validationSchema={Yup.object({
zip_code: Yup.string()
.min(5,'five digits needed')
.matches(zipRegExp, 'invalid zipcode')
.required('required field'),
})}
onSubmit={ (values, { setSubmitting, resetForm }) => {
setTimeout(() => {
// alert(JSON.stringify(values, null, 2));
resetForm();
setSubmitting(false);
}, 1000)
}}
>
{props => (
<Form className='bg-white rounded px-8 py-8 mt-4 mb-4'>
<div className='flex justify-center align-center'>
<section className='m-2 w-52 flex flex-col border'>
<CustomTextInput label='Enter ZIP Code' name='zip_code' placeholder='zipcode' />
</section>
</div>
<br></br>
<div className='flex justify-center align-center'>
<ButtonMaker {...allprops} groupa='pet' groupb='adopt' id='adult' label='adult'/>
<ButtonMaker {...allprops} groupa='pet' groupb='adopt' id='puppy' label='puppy'/>
</div>
</Form>
)}
</Formik>
)
}
something else i tried:
added this to each button: formkprops={props}
and this to the button maker on click function: formkprops.submitForm()
but it doesn't work.
Try the following:
import React, { useState, useEffect } from 'react';
import { Form, Formik, useField } from 'formik';
import * as Yup from 'yup';
const FORM_INPUT_BOX = "m-2 border border-gray-300 bg-white text-gray-700 placeholder-gray-400 shadow-sm text-base focus:outline-none focus:ring-2 focus:ring-yellow-500 focus:border-transparent"
const FORM_INPUT_LABEL = "ml-2 text-gray-700"
const FORM_INPUT_ERROR = "break-normal relative bottom-2 left-2 text-sm text-red-500"
const FORM_DROPDOWN = "m-2 focus:ring-yellow-500 border border-gray-300 focus:border-yellow-500 h-full border-transparent bg-transparent text-gray-500 sm:text-sm rounded"
export const FORM_BUTTON = "ml-2 mr-2 py-1 border border-transparent text-medium rounded-md text-white bg-gray-600 hover:bg-gray-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-gray-500"
const CustomTextInput = ({ label, ...props }) => {
const [field, meta] =useField(props); //using the hook useField, and destructuring
return(
<> {/*so we don't return any div tags - its just a react fragment */}
<label htmlFor={props.id || props.name} className={FORM_INPUT_LABEL}> {label} </label>
<input className={FORM_INPUT_BOX}{...field}{...props} />
{meta.touched && meta.error ? (
<div className={FORM_INPUT_ERROR}>{meta.error}</div>
) : null}
</>
)
}
const ButtonMaker = (props) => {
const {mainprops,userprops,adoptprops,...optionprops} = props
return(
<>
<button className={FORM_BUTTON} id={optionprops.id} onClick={()=>{optionprops.setButtonClick(true);
optionprops.setOption({"option": optionprops.id, "zipcode": optionprops.zip_code})}}
>{optionprops.label}</button>
</>
)
};
export default function RegistrationForm(props) {
const {mainprops,userprops,...adoptprops} = props;
const [buttonClick, setButtonClick] = useState(false);
const [option, setOption] = useState(null)
const allprops = {
mainprops,
userprops,
adoptprops,
buttonClick,
setButtonClick,
setOption
};
useEffect(()=>{
if (buttonClick){
alert(JSON.stringify(option, null, 4))};
},[buttonClick])
// https://regex101.com/
const zipRegExp= /^([0-9]{5})$/;
//zipRegExp: only 5 digits
const zipRegExp_plus4= /^([0-9]{5})(?:[-\s]*([0-9]{4}))?$/;
return (
<Formik
initialValues={{
zip_code : ''
}}
validationSchema={Yup.object({
zip_code: Yup.string()
.min(5,'five digits needed')
.matches(zipRegExp, 'invalid zipcode')
.required('required field'),
})}
onSubmit={ (values, { setSubmitting, resetForm }) => {
setTimeout(() => {
// alert(JSON.stringify(values, null, 2));
resetForm();
setSubmitting(false);
}, 1000)
}}
>
{props => (
<Form className='bg-white rounded px-8 py-8 mt-4 mb-4'>
<div className='flex justify-center align-center'>
<section className='m-2 w-52 flex flex-col border'>
<CustomTextInput label='Enter ZIP Code' name='zip_code' placeholder='zipcode' />
</section>
</div>
<br></br>
<div className='flex justify-center align-center'>
<ButtonMaker {...allprops} groupa='pet' groupb='adopt' id='adult' label='adult' zip_code={props.values.zip_code}/>
<ButtonMaker {...allprops} groupa='pet' groupb='adopt' id='puppy' label='puppy' zip_code={props.values.zip_code}/>
</div>
</Form>
)}
</Formik>
)
}
You needed to create an option property and add a zip_code propery when the actionprops' setoption function is called. In order to display this in the alert, you needed to use the JSON.stringify function to display the option using the alert function.
**Solution for Information Requested In Comments Section by Original Poster **
You can create state management for your application or use a library for this like Redux. Inside the state management, you could have an array of objects similar to the one created above. You can then give whichever component you want access to this array. You would also need a final submit for the user to indicate when they are done, or a required amount of options the user has to choose before they can move on.
References:
StackOverflow. how to alert javascript object. https://stackoverflow.com/a/64941710/8121551. (Accessed 10 October, 2021).

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