I am trying to upload a image but when I try I don't see the image on the web page.
import "./styles.css";
const App = () => {
return (
<div className="grid bg-white rounded-lg shadow-xl w-11/12 md:w-9/12 lg:w-1/2 h-1 w-1">
<div className="grid grid-cols-1 mt-5 mx-7 h-3 w-3">
<label className="uppercase md:text-sm text-xs text-gray-500 text-light font-semibold mb-1">
Upload Photo
</label>
<div className="flex items-center justify-center w-full">
<label className="flex flex-col border-4 border-dashed w-full h-32 hover:bg-gray-100 hover:border-purple-300 group">
<div className="flex flex-col items-center justify-center pt-7">
<svg
className="w-10 h-10 text-purple-400 group-hover:text-purple-600"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M4 16l4.586-4.586a2 2 0 012.828 0L16 16m-2-2l1.586-1.586a2 2 0 012.828 0L20 14m-6-6h.01M6 20h12a2 2 0 002-2V6a2 2 0 00-2-2H6a2 2 0 00-2 2v12a2 2 0 002 2z"
></path>
</svg>
<p className="lowercase text-sm text-gray-400 group-hover:text-purple-600 pt-1 tracking-wider">
Select a photo
</p>
</div>
<input type="file" className="hidden" />
</label>
</div>
</div>
</div>
);
};
export default App;
Here is my code : Code
I don't undertand how I can do to see the picture after selected the pic.
Could you help me please ?
Thank you very much !
Update:
Your code sandbox working
The short answer is you set the src attribute of the image tag to URL.createObjectURL(photo) where photo is the file that was selected by the form input: event.target.files[0].
Here's my code:
import React, { useState } from 'react'
import logo from '../static/logo.svg'
type FormSubmitFunction = (formdata: React.FormEvent<HTMLFormElement>) => Promise<void>
interface LandingPageProps {
handleSubmit: FormSubmitFunction
}
const LandingPage = ({ handleSubmit }: LandingPageProps) => {
const [photo, setPhoto] = useState<File>()
return (
<div className='flex flex-col items-center max-w-sm mx-auto'>
<div className='w-5/12 mt-3 sm:mt-4'><img src={logo} alt='logo' /></div>
<form className='w-full' onSubmit={handleSubmit}>
<div>
<div className="w-11/12 mx-auto">
<div className={photo ? 'hidden' : 'block'}>
<h1 className='m-6 text-lg font-bold text-center'>Please upload a photo</h1>
<input
id="photoupload"
name="photo"
type="file"
required
placeholder="Upload photo"
accept="image/gif, image/jpeg, image/png"
className="block w-full px-3 py-2 mx-auto mt-2 text-base placeholder-gray-600 bg-white border-2 border-gray-300 shadow-md focus:placeholder-gray-500 focus:bg-white focus:border-gray-600 focus:outline-none focus:ring-0"
onChange={
(event) => {
event.target.files && setPhoto(event.target.files[0])
}
}
/>
</div>
<div className={photo ? 'block' : 'hidden'}>
<h1 className='m-6 text-lg font-bold text-center'>Success! Thanks.</h1>
<img src={photo ? URL.createObjectURL(photo) : undefined} alt={photo ? photo.name : undefined} />
</div>
</div>
</div>
</form>
</div >
)
}
export default LandingPage
OK ... I updated your code sandbox.
Past this into App.js and it works...
import "./styles.css";
import React, { useState } from "react";
const App = () => {
const [photo, setPhoto] = useState();
return (
<div className="grid bg-white rounded-lg shadow-xl w-11/12 md:w-9/12 lg:w-1/2 h-1 w-1">
<div className="grid grid-cols-1 mt-5 mx-7 h-3 w-3">
<label className="uppercase md:text-sm text-xs text-gray-500 text-light font-semibold mb-1">
Upload Photo
</label>
<div className="flex items-center justify-center w-full">
<label className="flex flex-col border-4 border-dashed w-full h-32 hover:bg-gray-100 hover:border-purple-300 group">
<div className="flex flex-col items-center justify-center pt-7">
{photo && (
<img src={URL.createObjectURL(photo)} alt={photo.name} />
)}
{!photo && (
<svg
className="w-10 h-10 text-purple-400 group-hover:text-purple-600"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M4 16l4.586-4.586a2 2 0 012.828 0L16 16m-2-2l1.586-1.586a2 2 0 012.828 0L20 14m-6-6h.01M6 20h12a2 2 0 002-2V6a2 2 0 00-2-2H6a2 2 0 00-2 2v12a2 2 0 002 2z"
></path>
</svg>
)}
<p className="lowercase text-sm text-gray-400 group-hover:text-purple-600 pt-1 tracking-wider">
Select a photo
</p>
</div>
<input
type="file"
className="hidden"
onChange={(event) => {
event.target.files && setPhoto(event.target.files[0]);
}}
/>
</label>
</div>
</div>
</div>
);
};
export default App;
Related
Here is my Signup.js
import React from "react";
import { useToken } from "./AuthenticateUser";
import { useState } from "react";
import { useNavigate } from "react-router-dom";
export default function Signup() {
const [email, setEmail] = useState("");
const [password, setPassword] = useState("");
const [first_name, setFirst] = useState("");
const [last_name, setLast] = useState("");
const [zipcode, setZip] = useState("");
const [errorMessage, setErrorMessage] = useState("");
const navigate = useNavigate();
const [, , , signup] = useToken();
const modal = document.getElementById("signup");
const handleFormSubmit = async (e) => {
e.preventDefault();
if (formValidation() === false) {
return;
}
signup(first_name, last_name, email, zipcode, password);
setEmail("");
setPassword("");
setFirst("");
setLast("");
setZip("");
navigate("/me");
setErrorMessage("");
modal.classList.remove("show");
};
function formValidation() {
let blankInputs = 0;
if (email.length === 0) {
blankInputs++;
}
if (password.length === 0) {
blankInputs++;
}
if (first_name.length === 0) {
blankInputs++;
}
if (last_name.length === 0) {
blankInputs++;
}
if (zipcode.length === 0) {
blankInputs++;
}
if (blankInputs === 5) {
setErrorMessage("Form submission is completely blank.");
return false;
}
if (blankInputs > 1) {
setErrorMessage("Form has multiple blank inputs.");
return false;
}
if (!validateEmail()) {
setErrorMessage("Whoops! Email format is invalid.");
return false;
}
if (zipcode.length < 5) {
setErrorMessage("Whoops! Zipcode needs to be at least 5 characters");
return false;
}
if (!password) {
setErrorMessage("Whoops! Password is required.");
return false;
}
if (!first_name) {
setErrorMessage("Whoops! First name is required.");
return false;
}
if (!last_name) {
setErrorMessage("Whoops! Last name is required.");
return false;
}
return true;
}
function validateEmail() {
const regex =
/^(([^<>()\[\]\\.,;:\s#"]+(\.[^<>()\[\]\\.,;:\s#"]+)*)|(".+"))#((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
return regex.test(String(email).toLowerCase());
}
return (
<>
<div
className="modal fade fixed top-0 left-0 hidden w-full h-full outline-none overflow-x-hidden overflow-y-auto"
id="signup"
tabIndex="-1"
aria-labelledby="signupLabel"
aria-modal="true"
role="dialog"
>
<div className="modal-dialog modal-dialog-centered relative w-auto pointer-events-none">
<div className="modal-content border-none shadow-lg relative flex flex-col w-full pointer-events-auto bg-[#F0C797] bg-clip-padding rounded-md outline-none text-current">
<div className="modal-header p-6 mt-2 text-center">
<div className="flex justify-center items-center">
<h1 className="text-3xl font-bold mr-4">SIGN UP</h1>
<img src={require("../images/checklist.png")} width="50px" />
</div>
<svg
className="w-9 h-9 absolute top-3 right-2.5 text-black bg-transparent rounded-lg text-sm p-1.5 ml-auto inline-flex items-center hover:bg-[#FEF5ED] hover:text-white ease-linear transition-all duration-150 cursor-pointer"
fillRule="currentColor"
data-bs-dismiss="modal"
viewBox="0 0 20 20"
xmlns="http://www.w3.org/2000/svg"
>
<path
fillRule="evenodd"
d="M4.293 4.293a1 1 0 011.414 0L10 8.586l4.293-4.293a1 1 0 111.414 1.414L11.414 10l4.293 4.293a1 1 0 01-1.414 1.414L10 11.414l-4.293 4.293a1 1 0 01-1.414-1.414L8.586 10 4.293 5.707a1 1 0 010-1.414z"
clipRule="evenodd"
></path>
</svg>
</div>
<div className="modal-body relative p-4">
<form
onSubmit={handleFormSubmit}
className="container max-w-sm mx-auto flex-1 flex flex-col items-center justify-center px-3"
>
<input
type="text"
className="block border border-grey-light w-full p-3 rounded mb-4 placeholder:text-sm"
placeholder="First Name"
onChange={(e) => setFirst(e.target.value)}
value={first_name}
/>
<input
type="text"
className="block border border-grey-light w-full p-3 rounded mb-4 placeholder:text-sm"
placeholder="Last Name"
onChange={(e) => setLast(e.target.value)}
value={last_name}
/>
<input
type="email"
className="block border border-grey-light w-full p-3 rounded mb-4 placeholder:text-sm"
placeholder="Email"
onChange={(e) => setEmail(e.target.value)}
value={email}
/>
<input
type="text"
className="block border border-grey-light w-full p-3 rounded mb-4 placeholder:text-sm"
placeholder="Zipcode"
onChange={(e) => setZip(e.target.value)}
value={zipcode}
/>
<input
type="password"
className="block border border-grey-light w-full p-3 rounded mb-4 placeholder:text-sm"
placeholder="Password"
onChange={(e) => setPassword(e.target.value)}
value={password}
/>
{errorMessage ? (
<div className="flex p-4 mb-4 text-sm text-red-700 border border-red-300 rounded-lg bg-red-50 dark:bg-gray-800 dark:text-red-400 dark:border-red-800 items-center">
<img
src={require("../images/warning.png")}
width="30px"
style={{ marginRight: "15px" }}
/>
{errorMessage}
</div>
) : null}
<div className="flex flex-col items-center justify-end p-3 border-solid border-slate-200 rounded-b">
<button
// data-bs-dismiss="modal"
className="bg-black text-white font-bold uppercase text-sm px-6 py-3 rounded inline-flex group items-center justify-center cursor-pointer"
>
<span className="absolute w-0 h-0 transition-all duration-300 ease-out bg-[#F0C797] group-hover:w-32 group-hover:h-24 opacity-10"></span>
Order up!
</button>
</div>
</form>
<div className="flex items-center">
<a
data-bs-toggle="modal"
data-bs-target="#login"
className="mb-6 mt-4 mx-auto text-black-500 background-transparent font-bold underline uppercase text-sm focus:outline-none ease-linear transition-all duration-150 hover:text-white cursor-pointer"
>
Already have an account?
</a>
</div>
</div>
</div>
</div>
</div>
</>
);
}
**
Here is my Nav.js**
mport { useState } from "react";
function Nav() {
let [nav, setNav] = useState(false);
// nav = false
function handleNav() {
setNav(!nav);
}
return (
<nav className="flex justify-between items-center bg-[#FDECA9] py-3">
<div className="mx-auto mr-25">
<a href="/">
<div className="flex space-x-1 tracking-[4px] text-xl font-semibold items-center">
<span>PLATE</span>
<img
src={require("./images/plate.png")}
className="h-9"
alt="PlateMate Logo"
/>
<span>MATE</span>
</div>
</a>
</div>
<div className="hidden md:flex items-center">
<button
type="button"
className="bg-[#BB5855] mx-0 rounded text-[#FDECA9] text-sm py-1 px-4 relative inline-flex group items-center justify-center cursor-pointer"
data-bs-toggle="modal"
data-bs-target="#signup"
>
<span className="absolute w-0 h-0 transition-all duration-300 ease-out bg-white rounded-full group-hover:w-32 group-hover:h-32 opacity-10"></span>
SIGNUP
</button>
<button
type="button"
className="text-[#BB5855] mx-6 rounded text-sm outline outline-offset-4 outline-2 py-0 px-4 relative font-semibold text-center no-underline transition-all duration-300 ease-in-out cursor-pointer hover:text-[#bb58557c] "
data-bs-toggle="modal"
data-bs-target="#login"
>
LOGIN
</button>
</div>
<div className="block md:hidden">
{/* Mobile Hamburger Icon */}
<button
onClick={handleNav}
className="inline-flex items-center p-2 ml-3 text-sm md:hidden "
>
<img src={require("../src/images/hamburger.png")} width="30px" />
</button>
{/* Dropdown menu */}
<div
className={
nav
? "block absolute right-0 z-10 mt-0 w-56 mr-2 origin-top-right rounded-md bg-[#BB5855] shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none"
: "hidden"
}
>
<ul className="py-1 text-sm text-gray-100 divide-y ">
<div>
<a
data-bs-toggle="modal"
data-bs-target="#signup"
className="block px-4 py-2 transition-all duration-300 ease-in-out cursor-pointer hover:text-black"
>
SIGNUP
</a>
</div>
<div>
<a
data-bs-toggle="modal"
data-bs-target="#login"
className="block px-4 py-2 transition-all duration-300 ease-in-out cursor-pointer hover:text-black"
>
LOGIN
</a>
</div>
</ul>
</div>
</div>
</nav>
);
}
export default Nav;
I tried researching everything online but I can't find anything with Tailwind, only Bootstrap. I'm expecting the modal to not dismiss when there is an error message but dismiss completely after signed up. It does that but my issue is the grey background not disappearing. I have nothing in my CSS files that would tie to it. I've also tried commenting everything out on all of my files to see if anything was triggering it, however, it still appears. Please help!
Your code shows that you aren't really using React in the way it's intended. You should me managing the state in React, and not manipulating the DOM manually.
A common pattern for removing a modal is to use a state variable to handle the visibility of the component.
const [modalOpen, setModalOpen] = useState(false);
Then around your modal, you can render it conditionally:
return (<>
{modalOpen && (<YourModelHere />)}
</>)
You can then hide/show your modal using the setModalOpen() method.
The search filter works practically as I wish, you enter a city and when you press the button it returns the results of the clinics that match the list of cities. The only problem is that I can only do a single search, then I have to reload the page, I need to delete the city from the input so that the complete list appears again and I can do another search, using typescript is complicating this part. To make it clearer, what I am trying to do is that whenever I enter a new city in the search bar and press the button, I get the result of the clinics in that city, now it just gives me the result only once, the next search gives me the result of clinic not found.
import React, { useState, useEffect } from 'react'
import { getClinic } from '../../api/drupalAPI'
import {Clinic} from '#icofcv/common';
import contentUtils from '../../lib/contentUtils'
import Loader from '../spinner/Loader';
interface Props {
showModalLocator: boolean,
closeModalLocator: () => void
}
export const ClinicLocator: React.FC<Props> = ({ children, showModalLocator, closeModalLocator }) => {
const [clinicList, setClinicList] = useState<Clinic[] | undefined >([]);
const [text, setText] = useState("")
const textInput = () => {
text === "" ? clinicList : null
}
const fetchClinicList = async () => {
getClinic().then((response)=>{
console.log(response)
setClinicList(response)
}).catch ( (error) => {
console.error(error);
throw error;
});
}
const handleChange = () => {
const filterClinicList = clinicList && clinicList?.length > 0
? clinicList?.filter((clinic) => clinic?.province?.toLowerCase() === text.toLowerCase())
: undefined;
setClinicList(filterClinicList)
}
useEffect (() => {
fetchClinicList();
}, []);
return (
<>
<div>
{showModalLocator ? (
<>
<div className="justify-center items-center flex overflow-x-hidden overflow-y-auto fixed inset-0 z-50 outline-none focus:outline-none">
<div className="relative p-2 w-full max-w-3xl h-full md:h-auto">
{/*content*/}
<div className="relative bg-white rounded-lg shadow">
{/*header*/}
<div className="flex justify-between items-start px-4 py-3 rounded-t border-b">
<h3 className="text-lg font-medium">Localizador de clinicas</h3>
<button className="text-gray-400 bg-transparent hover:bg-gray-200 hover:text-gray-900 rounded-lg text-sm p-1.5 ml-auto inline-flex items-center" onClick={closeModalLocator}>
<svg aria-hidden="true" className="w-5 h-5" fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" d="M4.293 4.293a1 1 0 011.414 0L10 8.586l4.293-4.293a1 1 0 111.414 1.414L11.414 10l4.293 4.293a1 1 0 01-1.414 1.414L10 11.414l-4.293 4.293a1 1 0 01-1.414-1.414L8.586 10 4.293 5.707a1 1 0 010-1.414z" clip-rule="evenodd"></path>
</svg>
</button>
</div>
{/*body*/}
<div className="relative px-3 py-3 flex-auto overflow-auto modal-body">
<h2 className="text-sm font-medium mb-2">¿Dónde te encuentras?</h2>
<input
value={text}
onChange= {(e) => {setText(e.target.value)
textInput}}
type="search"
className="w-100 bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block p-2"
placeholder="Introduce una ubicación"
/>
<div>
<h2 className="text-sm font-medium my-3">Resultados</h2>
<div className="w-100">
<iframe className="w-100" src="https://www.google.com/maps/embed?pb=!1m18!1m12!1m3!1d2664.3238269926374!2d-0.3805919350162851!3d39.46959682083709!2m3!1f0!2f0!3f0!3m2!1i1024!2i768!4f13.1!3m3!1m2!1s0xd604f4bee0957f3%3A0x6686ff7d230b3965!2zQy4gZGUgU2FudC
BWaWNlbnQgTcOgcnRpciwgNjEsIHBpc28gMsK6LCBwdGEgMsKqLCA0NjAwMiBWYWzDqG5jaWEsIEVzcGHDsWE!5e0!3m2!1ses!2sus!4v1662388390673!5m2!1ses!2sus" loading="lazy"></iframe>
</div>
<div className="md:mt-4 overflow-auto relative py-2">
<div className="grid grid-cols-1 md:grid-cols-2 gap-2">
{clinicList && clinicList?.length === 0 && (
<div>Clinica no encontrada</div>
)}
{!clinicList ? <Loader /> :
clinicList.map((clinicFilter) => (
<div className="card bg-white px-2 py-3 h-36 md:h-32">
<button key={clinicFilter.id} type="button" className="text-left">
<div className="flex items-center gap-2 md:gap-4 md:gap-4">
<img className="h-24 w-2/5 min-w-40 object-cover object-center rounded-lg" src={contentUtils.getLargeImageUrl(clinicFilter.logo)} alt="#"/>
<div className="w-3/5">
<div className="text-md font-medium leading-5 clinic-title uppercase">{clinicFilter.title}</div>
<div className="flex items-center gap-2">
<div className="text-neutral-500 text-sm">{clinicFilter.propsPhone}</div>
<div className="text-neutral-500 text-sm">{clinicFilter.mobile}</div>
</div>
<div className="text-teal-600 text-sm underline clinic-mail">{clinicFilter.email}</div>
<div className="text-neutral-500 text-sm">{clinicFilter.registry}</div>
</div>
</div>
</button>
</div>
))
}
</div>
</div>
</div>
</div>
{/*footer*/}
<div className="flex items-center justify-end px-4 py-2 border-t border-solid border-slate-200 rounded-b gap-2">
<button className="btn text-black text-sm background-transparent px-8 outline-none focus:outline-none focus:ring-teal-600 focus:border-teal-600" type="button" onClick={closeModalLocator}>Cancelar</button>
<button className="btn bg-teal-600 hover:bg-teal-700 text-white text-sm active:bg-teal-700 px-8 outline-none focus:outline-none" type="button" onClick={handleChange} >Buscar</button>
</div>
</div>
</div>
</div>
<div className="opacity-25 fixed inset-0 z-40 bg-black"></div>
</>
) : null}
</div>
</>
)
}
In your handleChange just return the List itself when text is null
const filterClinicList = clinicList && clinicList?.length > 0
? clinicList?.filter((clinic) => (!text || clinic?.province?.toLowerCase() === text.toLowerCase()))
: undefined; //!text evaluates to true when text is '' i.e. short circuit the filter
```
ideally what you do would be:
```
let filterClinicList = clinicList;
//only filter when you *need* to filter
if (text) {
filterClinicList = clinicList?.filter((clinic) => (!text || clinic?.province?.toLowerCase() === text.toLowerCase()));
}
```
The search filter should filter the result once I press the search button, but when I press the button I receive the complete list of clinics without filtering and once I receive the complete list, if I write again in the input the city I want to filter then I receive the correct answer, without pressing the button, could someone tell me the reason of this behavior and what am I doing wrong?
import React, { useState, useEffect } from 'react'
import { getClinic } from '../../api/drupalAPI'
import {Clinic} from '#icofcv/common';
import contentUtils from '../../lib/contentUtils'
import Loader from '../spinner/Loader';
interface Props {
showModalLocator: boolean,
closeModalLocator: () => void
}
export const ClinicLocator: React.FC<Props> = ({ children, showModalLocator, closeModalLocator }) => {
const [clinicList, setClinicList] = useState<Clinic[]>([]);
const [clinicListFiltered, setClinicListFiltered] = useState<Clinic[]>([]);
const [searchClinic, setSearchClinic] = useState("");
const [isLoading, setIsLoading] = useState (false)
useEffect(() => {
if (searchClinic.length > 0) {
const filterList = clinicList.filter((element) => element.province?.toString().toLowerCase().includes(searchClinic));
setClinicListFiltered(filterList);
} else {
setClinicListFiltered(clinicList);
}
}, [searchClinic]);
const handleChange= (e) => {
e.preventDefault()
setIsLoading(true)
getClinic().then((response)=>{
console.log(response)
setClinicList(response);
setClinicListFiltered(response)
setSearchClinic(e.target.value);
setIsLoading(false)
}).catch ( (error) => {
console.error(error);
throw error;
});
return (
<>
<div>
{showModalLocator ? (
<>
<div className="justify-center items-center flex overflow-x-hidden overflow-y-auto fixed inset-0 z-50 outline-none focus:outline-none">
<div className="relative p-2 w-full max-w-3xl h-full md:h-auto">
{/*content*/}
<div className="relative bg-white rounded-lg shadow">
{/*header*/}
<div className="flex justify-between items-start px-4 py-3 rounded-t border-b">
<h3 className="text-lg font-medium">Localizador de clinicas</h3>
<button className="text-gray-400 bg-transparent hover:bg-gray-200 hover:text-gray-900 rounded-lg text-sm p-1.5 ml-auto inline-flex items-center" onClick={closeModalLocator}>
<svg aria-hidden="true" className="w-5 h-5" fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" d="M4.293 4.293a1 1 0 011.414 0L10 8.586l4.293-4.293a1 1 0 111.414 1.414L11.414 10l4.293 4.293a1 1 0 01-1.414 1.414L10 11.414l-4.293 4.293a1 1 0 01-1.414-1.414L8.586 10 4.293 5.707a1 1 0 010-1.414z" clip-rule="evenodd"></path>
</svg>
</button>
</div>
{/*body*/}
<div className="relative px-3 py-3 flex-auto overflow-auto modal-body">
<h2 className="text-sm font-medium mb-2">¿Dónde te encuentras?</h2>
<input
value={searchClinic}
onChange={(e) => setSearchClinic(e.target.value)}
type="search"
className="w-100 bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block p-2"
placeholder="Introduce una ubicación"
/>
<div>
<h2 className="text-sm font-medium my-3">Resultados</h2>
<div className="w-100">
<iframe className="w-100" src="https://www.google.com/maps/embed?pb=!1m18!1m12!1m3!1d2664.3238269926374!2d-0.3805919350162851!3d39.46959682083709!2m3!1f0!2f0!3f0!3m2!1i1024!2i768!4f13.1!3m3!1m2!1s0xd604f4bee0957f3%3A0x6686ff7d230b3965!2zQy4gZGUgU2FudC
BWaWNlbnQgTcOgcnRpciwgNjEsIHBpc28gMsK6LCBwdGEgMsKqLCA0NjAwMiBWYWzDqG5jaWEsIEVzcGHDsWE!5e0!3m2!1ses!2sus!4v1662388390673!5m2!1ses!2sus" loading="lazy"></iframe>
</div>
<div className="md:mt-4 overflow-auto relative py-2">
<div className="grid grid-cols-1 md:grid-cols-2 gap-2">
{!clinicListFiltered ? <Loader /> :
clinicListFiltered.map((clinicFilter) => (
<div className="card bg-white px-2 py-3 h-36 md:h-32">
<button key={clinicFilter.id} type="button" className="text-left">
<div className="flex items-center gap-2 md:gap-4 md:gap-4">
<img className="h-24 w-2/5 min-w-40 object-cover object-center rounded-lg" src={contentUtils.getLargeImageUrl(clinicFilter.logo)} alt="#"/>
<div className="w-3/5">
<div className="text-md font-medium leading-5 clinic-title uppercase">{clinicFilter.title}</div>
<div className="flex items-center gap-2">
<div className="text-neutral-500 text-sm">{clinicFilter.propsPhone}</div>
<div className="text-neutral-500 text-sm">{clinicFilter.mobile}</div>
</div>
<div className="text-teal-600 text-sm underline clinic-mail">{clinicFilter.email}</div>
<div className="text-neutral-500 text-sm">{clinicFilter.registry}</div>
</div>
</div>
</button>
</div>
))
}
</div>
</div>
</div>
</div>
{/*footer*/}
<div className="flex items-center justify-end px-4 py-2 border-t border-solid border-slate-200 rounded-b gap-2">
<button className="btn text-black text-sm background-transparent px-8 outline-none focus:outline-none focus:ring-teal-600 focus:border-teal-600" type="button" onClick={closeModalLocator}>Cancelar</button>
<button className="btn bg-teal-600 hover:bg-teal-700 text-white text-sm active:bg-teal-700 px-8 outline-none focus:outline-none" type="button" onClick={handleChange} >Buscar</button>
</div>
</div>
</div>
</div>
<div className="opacity-25 fixed inset-0 z-40 bg-black"></div>
</>
) : null}
</div>
</>
)
}
I’m new to React but trying to build a responsive website with a “desktop header” and “mobile header” that is shown when the user clicks on a menu-icon-toggle and closes when the user clicks on Close-icon.
I’m obviously doing it wrong but can’t seem to figure out what the problem is, I believe that NextJS doesn’t know what to open or close.
**Note: I´m using TailwindCSS and this is a component that will be rendered on the index page
My code looks something like this (simplified, without all the content):
import React, { useState } from 'react'
import Image from 'next/Image'
function header() {
const \[mobile__Header, setMobile__Header\] = useState(false)
const showMobile__Header = () =\> setMobile__Header(!mobile__Header)
return (\<div\>
{/* mobile header */}
<div className='absolute flex flex-col w-screen h-screen place-content-between bg-white text-black p-5 z-50'>
<div className='flex items-center justify-between'>
{/* Left Logo */}
<div className='cursor-pointer'>
</div>
{/* close icon */}
<div className='cursor-pointer' onClick={showMobile__Header}>
<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
<path stroke-linecap="round" stroke-linejoin="round" d="M6 18L18 6M6 6l12 12" />
</svg>
</div>
</div>
{/* nav links */}
<div className='flex'>
<div className='flex flex-col text-xl space-y-3'>
</div>
</div>
{/* Social links and languaje changer */}
<div className='flex justify-between font-light'>
<div>
<a className="link" href="">EN</a>
</div>
<div className='flex flex-col'>
</div>
</div>
</div>
{/* desktop header */}
<header className="flex w- px-10 py-1 justify-between">
<div className="flex">
{/* Left Logos */}
<div className="flex md:hidden cursor-pointer">
</div>
<div className="hidden md:flex cursor-pointer">
</div>
</div>
<div className="flex items-center">
{/* Menu icon toggle */}
<div className='flex md:hidden cursor-pointer' onClick={showMobile__Header}>
<svg xmlns="http://www.w3.org/2000/svg" className="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2}>
<path strokeLinecap="round" strokeLinejoin="round" d="M4 6h16M4 12h16M4 18h16" />
</svg>
</div>
{/* Right Nav Links and language changer */}
<div className="space-x-4 px-5 py-5 hidden md:flex ">
</div>
</div>
</header>
</div>
)}
export default header
You have many HTML/CSS issues like positioning and element structure.
The free tailwindui example is a solid example to reference. It has nice transitions and accessibility baked in, which I removed for the example. It also uses headlessui and heroicons, both were built by the TW team. The TW menu components handle the state internally, so you will not be able to see the logic in their example.
The below responsive example is based on the above-referenced version but without external dependencies.
import { useState } from "react";
const Navbar = () => {
const [isOpen, setOpen] = useState(false);
const toggleMenu = () => setOpen(!isOpen);
return (
<header className="relative bg-white">
<div className="max-w-7xl mx-auto px-4 sm:px-6">
<div className="flex justify-between items-center border-b-2 border-gray-100 py-6 md:justify-start md:space-x-10">
<div className="flex justify-start lg:w-0 lg:flex-1">
<a href="#">
<span className="h-8 w-auto sm:h-10">LOGO</span>
</a>
</div>
<div className="-mr-2 -my-2 md:hidden">
<button
onClick={toggleMenu}
className="bg-white rounded-md p-2 inline-flex items-center justify-center text-gray-400 hover:text-gray-500 hover:bg-gray-100 focus:outline-none focus:ring-2 focus:ring-inset focus:ring-indigo-500"
>
Open
</button>
</div>
<nav className="hidden md:flex space-x-10">
<a href="#" className="text-base font-medium text-gray-500 hover:text-gray-900">
About
</a>
</nav>
</div>
</div>
{isOpen && (
<div className="absolute top-0 inset-x-0 p-2 transition transform origin-top-right md:hidden">
<div className="rounded-lg shadow-lg ring-1 ring-black ring-opacity-5 bg-white divide-y-2 divide-gray-50">
<div className="pt-5 pb-6 px-5">
<div className="flex items-center justify-between">
<div>
<span className="h-8 w-auto">LOGO</span>
</div>
<div className="-mr-2">
<button
onClick={toggleMenu}
className="bg-white rounded-md p-2 inline-flex items-center justify-center text-gray-400 hover:text-gray-500 hover:bg-gray-100 focus:outline-none focus:ring-2 focus:ring-inset focus:ring-indigo-500"
>
X
</button>
</div>
</div>
<div className="mt-6">
<nav className="grid gap-y-8">
<a href="#" className="p-3 flex items-center rounded-md hover:bg-gray-50">
About
</a>
</nav>
</div>
</div>
</div>
</div>
)}
</header>
);
};
You will also likely need to handle the closing of the menu on route change.
import { useRouter } from "next/router";
import { useEffect, useState } from "react";
const Navbar = () => {
const [isOpen, setOpen] = useState(false);
const toggleMenu = () => setOpen(!isOpen);
const router = useRouter();
useEffect(() => {
const closeMenu = () => isOpen && setOpen(false);
router.events.on("routeChangeStart", closeMenu);
return () => {
router.events.off("routeChangeStart", closeMenu);
};
}, [isOpen, router]);
return (
...see above example
Without knowing exactly what you are asking, this should set you down the right path, at least from a logic standpoint.
import React, { useState } from 'react'
import Image from 'next/Image'
function header() {
const [mobile__Header, setMobile__Header] = useState(false)
const showMobile__Header = (e) => {
if (e.target.className.includes('mobile')) {
setMobile__Header(true)
} else if (e.target.className.includes('desktop')){
setMobile__Header(false)
}
}
return (
<div>
<div className='absolute flex flex-col w-screen h-screen place-content-between bg-white text-black p-5 z-50'>
<div className='flex items-center justify-between'>
<div className='cursor-pointer'>
</div>
<div className={mobile__Header === true ? 'cursor-pointer-mobile' : 'remove-display'} onClick={showMobile__Header}>
<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
<path stroke-linecap="round" stroke-linejoin="round" d="M6 18L18 6M6 6l12 12" />
</svg>
</div>
</div>
<div className='flex'>
<div className='flex flex-col text-xl space-y-3'>
</div>
</div>
<div className='flex justify-between font-light'>
<div>
<a className="link" href="">EN</a>
</div>
<div className='flex flex-col'>
</div>
</div>
</div>
<header className="flex w- px-10 py-1 justify-between">
<div className="flex">
<div className="flex md:hidden cursor-pointer">
</div>
<div className="hidden md:flex cursor-pointer">
</div>
</div>
<div className="flex items-center">
<div className={mobile__Header === false ? 'cursor-pointer-desktop' : 'remove-display'} onClick={showMobile__Header}>
<svg xmlns="http://www.w3.org/2000/svg" className="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2}>
<path strokeLinecap="round" strokeLinejoin="round" d="M4 6h16M4 12h16M4 18h16" />
</svg>
</div>
<div className="space-x-4 px-5 py-5 hidden md:flex ">
</div>
</div>
</header>
</div>
)
}
export default header
Basically, make sure to differentiate between your mobile button and your desktop button using your classNames. Then, you set a bolean indicator depending on what is in the className. From there, you either display the correct container, or use a CSS class that simply puts display: none (in this case, I used the name remove-display. All that is done with an inline ternary operator.
Whether you are toggling a button, or toggling a whole container (a parent div that when display: none removes all the content inside), this approach works in both scenarios.
I am still confused as to what you are asking but my solution should get you going. You have an onClick function on two divs and the comments above each of those says are misleading. One says close icon and the other says menu icon toggle. I am not sure what we are toggling.
EDIT: Here is a much cleaner way to do it and it involved no className toggling. This will teach you what you are doing so you can actually toggle what you are trying to toggle:
import React, { useState } from 'react'
function header() {
const [isActivate, setActive] = useState(false)
const handleActivate = (e) => {
!isActivate ? setActivate(true) : setActivate(false)
}
return (
<div>
<div>
<h1 style={{cursor: 'pointer'}} onClick={handleActivate}>
{isActivate === true ? 'OPEN' : false}
</h1>
<h1 style={{cursor: 'pointer'}} onClick={handleActivate}>
{!isActivate ? 'CLOSE' : false}
</h1>
</div>
</div>
)
}
export default header
I have to build a blog with next.js, and now I want to display some hyperlinks in the blog via the content management system using rich text.
As seen in the image, the data objects paragraph is showing in the blog and the link (href link) is nog showing.
data objects href not visible
The code looks like this:
const PostDetail = ( { post }) => {
const getContentFragment = (index, text, obj, type) => {
let modifiedText = text;
if (obj) {
if (obj.bold) {
modifiedText = (<b key={index}>{text}</b>);
}
if (obj.italic) {
modifiedText = (<em key={index}>{text}</em>);
}
if (obj.underline) {
modifiedText = (<u key={index}>{text}</u>);
}
}
switch (type) {
case 'heading-one':
return <h1 key={index} className="text-3xl font-semibold mb-4">{modifiedText.map((item, i) => <React.Fragment key={i}>{item}</React.Fragment>)}</h1>;
case 'heading-three':
case 'heading-two':
return <h2 key={index} className="text-2xl font-semibold mb-4">{modifiedText.map((item, i) => <React.Fragment key={i}>{item}</React.Fragment>)}</h2>;
case 'heading-three':
return <h3 key={index} className="text-xl font-semibold mb-4">{modifiedText.map((item, i) => <React.Fragment key={i}>{item}</React.Fragment>)}</h3>;
case 'paragraph':
return <p key={index} className="mb-8">{modifiedText.map((item, i) => <React.Fragment key={i}>{item}</React.Fragment>)}</p>;
case 'heading-four':
return <h4 key={index} className="text-md font-semibold mb-4">{modifiedText.map((item, i) => <React.Fragment key={i}>{item}</React.Fragment>)}</h4>;
case 'link':
return <Link key={index} href={href} className="text-md font-semibold mb-4">{modifiedText.map((item, i) => <React.Fragment key={i}>{item}</React.Fragment>)}</Link>;
case 'script':
return <script key={index} className="text-md font-semibold mb-4">{modifiedText.map((item, i) => <React.Fragment key={i}>{item}</React.Fragment>)}</script>;
case 'image':
return (
<img
key={index}
alt={obj.title}
height={obj.height}
width={obj.width}
src={obj.src}
/>
);
default:
return modifiedText;
}
};
return (
<>
<div className="bg-white shadow-lg rounded-lg lg:p-8 pb-12 mb-8">
<div className="relative overflow-hidden shadow-md mb-6">
<img src={post.featuredImage.url} alt="" className="object-top h-full w-full object-cover shadow-lg rounded-t-lg lg:rounded-lg" />
</div>
<div className="px-4 lg:px-0">
<div className="flex items-center mb-8 w-full">
<div className="hidden md:flex items-center justify-center lg:mb-0 lg:w-auto mr-8 items-center">
<img
alt={post.author.name}
height="30px"
width="30px"
className="align-middle rounded-full"
src={post.author.photo.url}
/>
<p className="inline align-middle text-gray-700 ml-2 font-medium text-lg">{post.author.name}</p>
</div>
<div className="font-medium text-gray-700">
<svg xmlns="http://www.w3.org/2000/svg" className="h-6 w-6 inline mr-2 text-pink-500" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z" />
</svg>
<span className="align-middle">{moment(post.createdAt).format('MMM DD, YYYY')}</span>
</div>
</div>
<h1 className="mb-8 text-3xl font-semibold">{post.title}</h1>
{post.content.raw.children.map((typeObj, index) => {
const children = typeObj.children.map((item, itemindex) => getContentFragment(itemindex, item.text, item));
console.log(post.content.raw)
return getContentFragment(index, children, typeObj, typeObj.type);
})}
</div>
</div>
</>
)
}
export default PostDetail
If I'm not mistaken this is from JavaScript Mastery blog tutorial. I had the exact same problem.
Ended up changing PostDetail.jsx to this:
import React from "react";
import moment from "moment";
import { RichText } from "#graphcms/rich-text-react-renderer";
const PostDetail = ({ post }) => {
return (
<>
<div className="bg-white shadow-lg rounded-lg lg:p-8 pb-12 mb-8">
<div className="relative overflow-hidden shadow-md mb-6">
<img
src={post.featuredImage.url}
alt=""
className="object-top h-full w-full object-cover shadow-lg rounded-t-lg lg:rounded-lg"
/>
</div>
<div className="px-4 lg:px-0">
<div className="flex items-center mb-8 w-full">
<div className="hidden md:flex items-center justify-center lg:mb-0 lg:w-auto mr-8 items-center">
<img
alt={post.author.name}
height="30px"
width="30px"
className="align-middle rounded-full"
src={post.author.photo.url}
/>
<p className="inline align-middle text-gray-700 ml-2 font-medium text-lg">
{post.author.name}
</p>
</div>
<div className="font-medium text-gray-700">
<svg
xmlns="http://www.w3.org/2000/svg"
className="h-6 w-6 inline mr-2 text-pink-500"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="2"
d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z"
/>
</svg>
<span className="align-middle">
{moment(post.createdAt).format("MMM DD, YYYY")}
</span>
</div>
</div>
<h1 className="mb-8 text-3xl font-semibold">{post.title}</h1>
<div className="mb-8">
{post.excerpt && (
<p
className="text-gray-700 text-center text-lg
font-normal px-4 lg:px-20 mb-8"
></p>
)}
<RichText content={post.content.raw.children} />
</div>
</div>
</div>
</>
);
};
export default PostDetail;
You might run into a problem in Hygraph, where you can't put extra line breaks in your posts. Simply press shift + enter (instead of just enter).