Custom google fonts with NextJs and tailwindCSS - reactjs

I would like to use google fonts in my NextJS app. I use tailwindCSS and I already imported reference link in the _document.js Head section . In the tailwind.config file I defined my fontFamily, but when I try to use the custom class it does not apply the font family to the html element. What am I doing wrong?
My _document.js file:
import Document, { Html, Head, Main, NextScript } from "next/document";
class MyDocument extends Document {
static async getInitialProps(ctx) {
const initialProps = await Document.getInitialProps(ctx);
return { ...initialProps };
}
render() {
return (
<Html>
<Head>
<link
href="https://fonts.googleapis.com/css2?family=Press+Start+2P&display=swap"
rel="stylesheet"
/>
</Head>
<body>
<Main />
<NextScript />
</body>
</Html>
);
}
}
export default MyDocument;
tailwind.config file:
const defaultTheme = require("tailwindcss/defaultTheme");
module.exports = {
content: [
"./pages/**/*.{js,ts,jsx,tsx}",
"./components/**/*.{js,ts,jsx,tsx}",
],
theme: {
extend: {
fontFamily: {
press: ["Press Start 2P", ...defaultTheme.fontFamily.sans],
},
},
},
plugins: [],
};
Text where I want to use the custom font:
<h2 className="font-press text-3xl">
This is a random text with custom google font family Press Start 2P!
</h2>

This is my reference and solution:
_document.js
import Document, { Html, Head, Main, NextScript } from "next/document";
class MyDocument extends Document {
render() {
return (
<Html>
<Head>
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link
rel="preconnect"
href="https://fonts.gstatic.com"
crossOrigin="true"
/>
<link
href="https://fonts.googleapis.com/css2?family=Press+Start+2P&family=Syne+Mono&family=Ubuntu+Mono&display=swap"
rel="stylesheet"
/>
</Head>
<body>
<Main />
<NextScript />
</body>
</Html>
);
}
}
export default MyDocument;
tailwind.config.js
module.exports = {
content: [
"./pages/**/*.{js,ts,jsx,tsx}",
"./components/**/*.{js,ts,jsx,tsx}",
],
theme: {
fontFamily: {
syne_mono: ["Syne Mono", "monospace"],
press: ["Press Start 2P", "cursive"],
ubuntu: ["Ubuntu Mono", "monospace"],
},
extend: {},
},
plugins: [],
};
index.js (home page)
import Head from "next/head";
export default function Home() {
return (
<div>
<Head>
<title>Create Next App</title>
<meta name="description" content="Generated by create next app" />
<link rel="icon" href="/favicon.ico" />
</Head>
<div className="flex items-center justify-center h-screen flex-col gap-5">
<h1 className="text-6xl text-blue-600 p-3">Custom Fonts:</h1>
<h2 className="font-syne_mono text-6xl">Syne Mono, monospace</h2>
<h2 className="font-press text-6xl">Press Start 2P, cursive;</h2>
<h2 className=" font-ubuntu text-6xl">Ubuntu Mono, monospace;</h2>
</div>
</div>
);
}
output:
"next": "12.0.7","react": "17.0.2","tailwindcss": "^3.0.5"

You have to use single quotes around the font name in tailwind.config.js:
// ...
extend: {
fontFamily: {
press: ['"Press Start 2P"', ...defaultTheme.fontFamily.sans],
},
},

Related

Next.js | How to improve seo when doing dynamic routing with ssr

I am writing dynamic routes code in, like the code below. So my question is, this dynamic routes page is not reflected in google at all. I have dynamically set the title of this page, but when I search for that title in google, it doesn't show up. What should I do?
src/pages/salon/[[...salon]].js (abridged edition)
import React from 'react'
// Firebase
import { db } from "../../../firebaseConfig"
import { doc, getDoc, updateDoc, arrayUnion, arrayRemove } from "firebase/firestore"
import { getAuth, onAuthStateChanged } from "firebase/auth"
const Salon = ({ id, tag, data }) => {
return (
<>
<NextSeo
title={data.title}
description={data.description}
url={data.url}
canonical={data.url}
openGraph={{
url: data.url,
title: data.title,
description: data.description,
type: "article",
images: [
{
url: data.mainImages[1],
width: 800,
height: 600,
alt: "Image",
}],
}}
/>
<div>{data.salonName}【{data.salonNameKana}】</div>
</>
)
}
export default Salon
export async function getServerSideProps(context) {
const url = context.params.salon
const salonRef = doc(db, "salons", url[0])
const salonSnap = await getDoc(salonRef)
const data = salonSnap.data()
if (!data) {
return {
redirect: {
destination: '/',
permanent: false,
},
}
}
return {
props: {
id: url[0],
tag: url[1] ? url[1] : '',
data: data
}
};
}
src/pages/salon/[[...salon]].js (full edition)
import React, { useEffect } from "react"
import Link from 'next/link'
import { useRouter } from 'next/router'
import Image from "next/image"
// firebase関連
import { db } from "../../firebaseConfig"
import {
collection,
orderBy,
query,
getDocs,
limit,
where
} from 'firebase/firestore'
// fontawesome
import { FontAwesomeIcon } from '#fortawesome/react-fontawesome'
import { faSearch, faMapMarkedAlt, faSubway } from "#fortawesome/free-solid-svg-icons"
// Redux関連
import { useSelector } from 'react-redux'
import { NextSeo } from "next-seo"
const Index = ({newSalonList}) => {
const router = useRouter()
const area = useSelector((state) => state.searchKey.area)
return (
<div className="Top">
<NextSeo />
{/* 検索 */}
<div className="search">
<div className="reserveText">イメコンサロンを検索する</div>
<div className="area">
<p>{area}</p>
<Link href="area">
<a><button>エリア変更</button></a>
</Link>
</div>
<div className="form">
<form action="/search" method="GET" className="search-box">
<input type="text" name="query" placeholder="サロン名・エリア・メニューなどから検索" />
<button type="submit" value=" 検索"><FontAwesomeIcon icon={faSearch} /></button>
</form>
</div>
<div className="common_relative_image mainImg">
<Image
src='/imecon-personal-color.jpeg'
alt="mainTopImg"
className="mainImg"
layout="fill"
objectFit="contain"
priority
/>
</div>
<div className="searchWay">
<button className="searchWayItem" onClick={(e) =>{
e.preventDefault()
router.push({
pathname: "/area",
// 検索する地域をクリックした後の遷移先のURLを渡す
query: {to:'area-search'}
})
}}>
<div className="areaImage common_relative_image">
<Image
src='/JapanMap.svg'
alt="japanMap"
layout="fill"
objectFit="contain"
/>
</div>
<p><span>エリア</span><br />から探す</p>
</button>
<Link href="station">
<a className="searchWayItem" style={{cursor: "default", background: "#D9D9D9"}} onClick={ (event) => event.preventDefault() }>
<FontAwesomeIcon icon={faSubway} />
<p><span>駅</span>から探す</p>
<small style={{color: "red"}}>coming soon</small>
</a>
</Link>
<Link href="location">
<a className="searchWayItem" style={{cursor: "default", background: "#D9D9D9"}} onClick={ (event) => event.preventDefault() }>
<FontAwesomeIcon icon={faMapMarkedAlt} />
<p><span>現在地</span><br />から探す</p>
<small style={{color: "red"}}>coming soon</small>
</a>
</Link>
</div>
</div>
{/* ブックマークから探す */}
<Link href="/bookmark">
<a className="bookMark">
<svg xmlns="http://www.w3.org/2000/svg" width="24" fill="#FF0100" height="24" viewBox="0 0 24 24"><path d="M12 4.248c-3.148-5.402-12-3.825-12 2.944 0 4.661 5.571 9.427 12 15.808 6.43-6.381 12-11.147 12-15.808 0-6.792-8.875-8.306-12-2.944z"/></svg>
ブックマークから探す
</a>
</Link>
{/* お知らせ */}
<div className="information">
<div className="common_title">| Information</div>
<ul>
<li>
<Link href="/tosalons">
<a>掲載をご希望のサロン様はこちら</a>
</Link>
</li>
</ul>
</div>
{/* 新着サロン */}
<div className="newSalon">
<div className="common_title">| 新着のサロン</div>
<ul>
{newSalonList.map((data) => {
return (
// 新着順のサロンを表示:useEffectで格納したデータ
<li className="newSalonMain" key={data.salonId}>
<Link href={'/salon/' + data.salonId}>
<a className="newSalonMainList">
<div className="newSalonMainListImage common_relative_image">
<Image
src={data.mainImageUrl}
alt="salonMainImage"
layout="fill"
objectFit="contain"
/>
</div>
<div className="newSalonMainListInfo">
<p className="salonName">{data.salonName}{data.salonNameKana}</p>
<div className="salonTraffic">
<div className="common_relative_image salonTrafficImg">
<Image
src='/traffic.svg'
alt="traffic"
layout="fill"
objectFit="contain"
/>
</div>
<span>{data.address}</span>
</div>
<div className="newSalonMainListInfoMenu">
<div className="newSalonMainListInfoMenuTitle">{data.title}</div>
<div className="newSalonMainListInfoMenuPrice">¥{data.price}</div>
</div>
</div>
</a>
</Link>
</li>
)
})}
</ul>
</div>
</div>
)
}
export default Index
export async function getServerSideProps() {
// firestoreから新着順サロンのドキュメントを5つ取得
const salons = query(collection(db, "salons"), where('publish', "==", true), orderBy('createAt', "desc"), limit(5))
const querySnapshot = await getDocs(salons)
// 一旦useEffect内で配列を作り、格納する。後でこの配列をuseStateに格納
let subSalonList = []
// 取得した新着順サロンのドキュメントから情報を取得し、subSalonListに格納
await querySnapshot.forEach((doc) => {
subSalonList = [...subSalonList, {
salonId: doc.id,
salonName: doc._document.data.value.mapValue.fields.salonName.stringValue,
salonNameKana: doc._document.data.value.mapValue.fields.salonNameKana ? '【' + doc._document.data.value.mapValue.fields.salonNameKana.stringValue + '】': '',
address: doc._document.data.value.mapValue.fields.access ? doc._document.data.value.mapValue.fields.access.stringValue: '',
mainImageUrl: doc._document.data.value.mapValue.fields.mainImages.mapValue.fields[1].stringValue,
}]
})
// サブコレクションの情報は↑で一緒に取得できなかったので、↓で格納する。subSalonListをループして、salonIdを元に順番に情報を取得し、subSalonListに格納している。
for (const value of subSalonList) {
try {
// salonIdを元にmenuサブコレクション下のドキュメントでtopがtrueのものを検索&取得
const q = query(collection(db, "salons", value.salonId, "menu"))
const querySnapshot2 = await getDocs(q)
value.title = querySnapshot2.docs[0]._document.data.value.mapValue.fields.title.stringValue
value.price = querySnapshot2.docs[0]._document.data.value.mapValue.fields.price.integerValue.toLocaleString()
value.time = querySnapshot2.docs[0]._document.data.value.mapValue.fields.time.integerValue.toLocaleString()
} catch(err) {
console.log(err)
return 'err'
}
}
return {
props: {
newSalonList: subSalonList,
}
}
}
site meta tag
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1.0,minimum-scale=1.0,maximum-scale=1.0,user-scalable=no"><link rel="icon" href="https://kanunu-beauty.com/favicon.ico">
<link rel="apple-touch-icon" href="https://kanunu-beauty.com/favicon.ico">
<title>Kanunu(カヌヌ)|パーソナルカラー・骨格診断・顔タイプ診断などのイメコンサロンが探せる日本最大級の検索サイト</title>
<meta name="twitter:card" content="summary_large_image">
<meta name="twitter:site" content="#kanunu_official">
<meta name="twitter:creator" content="#kanunu_official">
<meta property="og:image:alt" content="Kanunu_Image">
<meta property="og:image:width" content="800">
<meta property="og:image:height" content="600">
<meta property="og:locale" content="ja_JP"><meta property="og:site_name" content="Kanunu(カヌヌ)"><link rel="icon" href="https://kanunu-beauty.com/favicon.ico"><link rel="apple-touch-icon" href="https://kanunu-beauty.com/favicon.ico" sizes="76x76"><meta name="robots" content="index,follow">
<meta property="og:image" content="https://kanunu-beauty.com/kanunu.png">
<meta name="description" content="パーソナルカラー・骨格診断・顔タイプ診断などのイメコンサロンが探せる日本最大級の検索サイト。24時間いつでもネット検索OK。イメージコンサルティングを検索するならKanunu">
<meta property="og:title" content="Kanunu(カヌヌ)|パーソナルカラー・骨格診断・顔タイプ診断などのイメコンサロンが探せる日本最大級の検索サイト">
<meta property="og:description" content="パーソナルカラー・骨格診断・顔タイプ診断などのイメコンサロンが探せる日本最大級の検索サイト。24時間いつでもネット検索OK。イメージコンサルティングを検索するならKanunu">
<meta property="og:url" content="https://kanunu-beauty.com/">
<meta property="og:type" content="website">
<link rel="canonical" href="https://kanunu-beauty.com/">
<link rel="preload" as="image" imagesrcset="/_next/image?url=%2Fimecon-personal-color.jpeg&w=640&q=75 640w, /_next/image?url=%2Fimecon-personal-color.jpeg&w=750&q=75 750w, /_next/image?url=%2Fimecon-personal-color.jpeg&w=828&q=75 828w, /_next/image?url=%2Fimecon-personal-color.jpeg&w=1080&q=75 1080w, /_next/image?url=%2Fimecon-personal-color.jpeg&w=1200&q=75 1200w, /_next/image?url=%2Fimecon-personal-color.jpeg&w=1920&q=75 1920w, /_next/image?url=%2Fimecon-personal-color.jpeg&w=2048&q=75 2048w, /_next/image?url=%2Fimecon-personal-color.jpeg&w=3840&q=75 3840w" imagesizes="100vw">
<meta name="next-head-count" content="24">
<meta charset="UTF-8">
<meta name="theme-color" content="#ff889e"><script src="https://apis.google.com/_/scs/abc-static/_/js/k=gapi.lb.ja.5EzdJRYYqVI.O/m=gapi_iframes/rt=j/sv=1/d=1/ed=1/rs=AHpOoo_YkgRfk9NA-tU81xmKmFrifcc8Yg/cb=gapi.loaded_0?le=scs" async=""></script><script src="https://partner.googleadservices.com/gampad/cookie.js?domain=kanunu-beauty.com&callback=_gfp_s_&client=ca-pub-3226715501424001&cookie=ID%3Da210217fc7bc19a0-226fbd731ed100ba%3AT%3D1648307385%3ART%3D1648307385%3AS%3DALNI_Mbl4zhbFc5KZH7kiBhF4bogpQHECQ"></script><script src="https://pagead2.googlesyndication.com/pagead/managed/js/adsense/m202205050101/show_ads_impl_fy2019.js?bust=31067451" id="google_shimpl"></script><script async="" src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?client=ca-pub-3226715501424001" crossorigin="anonymous" data-checked-head="true"></script><link rel="preload" href="/_next/static/css/3af99d7a8bc648e8.css" as="style"><link rel="stylesheet" href="/_next/static/css/3af99d7a8bc648e8.css" data-n-g=""><noscript data-n-css=""></noscript><script defer="" nomodule="" src="/_next/static/chunks/polyfills-5cd94c89d3acac5f.js"></script><script src="/_next/static/chunks/webpack-5752944655d749a0.js" defer=""></script><script src="/_next/static/chunks/framework-5f4595e5518b5600.js" defer=""></script><script src="/_next/static/chunks/main-958eae9894028eb3.js" defer=""></script><script src="/_next/static/chunks/pages/_app-7066ec57ee9a283b.js" defer=""></script><script src="/_next/static/chunks/675-a0e7bc4ebaaecba0.js" defer=""></script><script src="/_next/static/chunks/pages/index-b8f101c1b5c4520a.js" defer=""></script><script src="/_next/static/c6OFukToh53vEUvjEggAL/_buildManifest.js" defer=""></script><script src="/_next/static/c6OFukToh53vEUvjEggAL/_ssgManifest.js" defer=""></script><script src="/_next/static/c6OFukToh53vEUvjEggAL/_middlewareManifest.js" defer=""></script>
<meta http-equiv="origin-trial" content="AxujKG9INjsZ8/gUq8+dTruNvk7RjZQ1oFhhgQbcTJKDnZfbzSTE81wvC2Hzaf3TW4avA76LTZEMdiedF1vIbA4AAABueyJvcmlnaW4iOiJodHRwczovL2ltYXNkay5nb29nbGVhcGlzLmNvbTo0NDMiLCJmZWF0dXJlIjoiVHJ1c3RUb2tlbnMiLCJleHBpcnkiOjE2NTI3NzQ0MDAsImlzVGhpcmRQYXJ0eSI6dHJ1ZX0=">
<meta http-equiv="origin-trial" content="Azuce85ORtSnWe1MZDTv68qpaW3iHyfL9YbLRy0cwcCZwVnePnOmkUJlG8HGikmOwhZU22dElCcfrfX2HhrBPAkAAAB7eyJvcmlnaW4iOiJodHRwczovL2RvdWJsZWNsaWNrLm5ldDo0NDMiLCJmZWF0dXJlIjoiVHJ1c3RUb2tlbnMiLCJleHBpcnkiOjE2NTI3NzQ0MDAsImlzU3ViZG9tYWluIjp0cnVlLCJpc1RoaXJkUGFydHkiOnRydWV9">
<meta http-equiv="origin-trial" content="A16nvcdeoOAqrJcmjLRpl1I6f3McDD8EfofAYTt/P/H4/AWwB99nxiPp6kA0fXoiZav908Z8etuL16laFPUdfQsAAACBeyJvcmlnaW4iOiJodHRwczovL2dvb2dsZXRhZ3NlcnZpY2VzLmNvbTo0NDMiLCJmZWF0dXJlIjoiVHJ1c3RUb2tlbnMiLCJleHBpcnkiOjE2NTI3NzQ0MDAsImlzU3ViZG9tYWluIjp0cnVlLCJpc1RoaXJkUGFydHkiOnRydWV9">
<meta http-equiv="origin-trial" content="AxBHdr0J44vFBQtZUqX9sjiqf5yWZ/OcHRcRMN3H9TH+t90V/j3ENW6C8+igBZFXMJ7G3Pr8Dd13632aLng42wgAAACBeyJvcmlnaW4iOiJodHRwczovL2dvb2dsZXN5bmRpY2F0aW9uLmNvbTo0NDMiLCJmZWF0dXJlIjoiVHJ1c3RUb2tlbnMiLCJleHBpcnkiOjE2NTI3NzQ0MDAsImlzU3ViZG9tYWluIjp0cnVlLCJpc1RoaXJkUGFydHkiOnRydWV9">
<meta http-equiv="origin-trial" content="A88BWHFjcawUfKU3lIejLoryXoyjooBXLgWmGh+hNcqMK44cugvsI5YZbNarYvi3roc1fYbHA1AVbhAtuHZflgEAAAB2eyJvcmlnaW4iOiJodHRwczovL2dvb2dsZS5jb206NDQzIiwiZmVhdHVyZSI6IlRydXN0VG9rZW5zIiwiZXhwaXJ5IjoxNjUyNzc0NDAwLCJpc1N1YmRvbWFpbiI6dHJ1ZSwiaXNUaGlyZFBhcnR5Ijp0cnVlfQ==">
<meta http-equiv="origin-trial" content="AzoawhTRDevLR66Y6MROu167EDncFPBvcKOaQispTo9ouEt5LvcBjnRFqiAByRT+2cDHG1Yj4dXwpLeIhc98/gIAAACFeyJvcmlnaW4iOiJodHRwczovL2RvdWJsZWNsaWNrLm5ldDo0NDMiLCJmZWF0dXJlIjoiUHJpdmFjeVNhbmRib3hBZHNBUElzIiwiZXhwaXJ5IjoxNjYxMjk5MTk5LCJpc1N1YmRvbWFpbiI6dHJ1ZSwiaXNUaGlyZFBhcnR5Ijp0cnVlfQ==">
<meta http-equiv="origin-trial" content="A6+nc62kbJgC46ypOwRsNW6RkDn2x7tgRh0wp7jb3DtFF7oEhu1hhm4rdZHZ6zXvnKZLlYcBlQUImC4d3kKihAcAAACLeyJvcmlnaW4iOiJodHRwczovL2dvb2dsZXN5bmRpY2F0aW9uLmNvbTo0NDMiLCJmZWF0dXJlIjoiUHJpdmFjeVNhbmRib3hBZHNBUElzIiwiZXhwaXJ5IjoxNjYxMjk5MTk5LCJpc1N1YmRvbWFpbiI6dHJ1ZSwiaXNUaGlyZFBhcnR5Ijp0cnVlfQ==">
<meta http-equiv="origin-trial" content="A/9La288e7MDEU2ifusFnMg1C2Ij6uoa/Z/ylwJIXSsWfK37oESIPbxbt4IU86OGqDEPnNVruUiMjfKo65H/CQwAAACLeyJvcmlnaW4iOiJodHRwczovL2dvb2dsZXRhZ3NlcnZpY2VzLmNvbTo0NDMiLCJmZWF0dXJlIjoiUHJpdmFjeVNhbmRib3hBZHNBUElzIiwiZXhwaXJ5IjoxNjYxMjk5MTk5LCJpc1N1YmRvbWFpbiI6dHJ1ZSwiaXNUaGlyZFBhcnR5Ijp0cnVlfQ=="><link as="script" rel="prefetch" href="/_next/static/chunks/pages/mypage-141f665c92390596.js"><link as="script" rel="prefetch" href="/_next/static/chunks/733-e7d84bed7ed356fa.js"><link as="script" rel="prefetch" href="/_next/static/chunks/pages/bookmark-61fff4ba51ad8155.js"><link as="script" rel="prefetch" href="/_next/static/chunks/866-c05af8e23c872e2c.js"><link as="script" rel="prefetch" href="/_next/static/chunks/pages/signin-ae60a19128cbc932.js"><link as="script" rel="prefetch" href="/_next/static/chunks/932-f01e4d1c2aeb2e91.js"><link as="script" rel="prefetch" href="/_next/static/chunks/pages/area-bf69ae687aa602d8.js"><link as="script" rel="prefetch" href="/_next/static/chunks/pages/tosalons-79196977b12f1828.js"><link as="script" rel="prefetch" href="/_next/static/chunks/pages/salon/%5B%5B...salon%5D%5D-15ea0d621d053ed1.js"><link rel="preload" href="https://adservice.google.co.jp/adsid/integrator.js?domain=kanunu-beauty.com" as="script"><script type="text/javascript" src="https://adservice.google.co.jp/adsid/integrator.js?domain=kanunu-beauty.com"></script><link rel="preload" href="https://adservice.google.com/adsid/integrator.js?domain=kanunu-beauty.com" as="script"><script type="text/javascript" src="https://adservice.google.com/adsid/integrator.js?domain=kanunu-beauty.com"></script><script src="https://apis.google.com/js/api.js?onload=__iframefcb771037" type="text/javascript" charset="UTF-8" gapi_processed="true"></script><link as="script" rel="prefetch" href="/_next/static/chunks/pages/privacy-policy-244018d019bd23ac.js"><link as="script" rel="prefetch" href="/_next/static/chunks/pages/terms-of-service-6bb94404533a0249.js">
</head>
_app.js
import React, { useEffect } from 'react'
import { useRouter } from 'next/router'
import Script from 'next/script'
import Head from 'next/head'
import * as gtag from '../lib/gtag.js'
import { GA_TRACKING_ID } from '../lib/gtag'
import { Provider } from "react-redux"
import redux from '../components/store/redux.js'
import '../../public/styles/Common.scss'
import '../../public/styles/Top.scss'
import '../../public/styles/index.scss'
import '../../public/styles/Header.scss'
import '../../public/styles/Footer.scss'
import '../../public/styles/ToSalons.scss'
import '../../public/styles/Terms.scss'
import '../../public/styles/Area.scss'
import '../../public/styles/Salon.scss'
import '../../public/styles/Search.scss'
import '../../public/styles/Bookmark.scss'
import '../../public/styles/SignUp.scss'
import '../../public/styles/SignUp.scss'
import '../../public/styles/Mypage.scss'
import '../../public/styles/SignUp.scss'
import '../../public/styles/SignUp.scss'
import "../../public/styles/Pagination.scss"
import Header from '../components/block/Header.js'
import Footer from '../components/block/Footer.js'
import { DefaultSeo } from 'next-seo'
import SEO from '../../next-seo.config'
import { config } from '#fortawesome/fontawesome-svg-core'
import '#fortawesome/fontawesome-svg-core/styles.css'
config.autoAddCss = false
export default function App({ Component, pageProps }) {
const router = useRouter()
useEffect(() => {
const handleComplete = (url) => {
// google analyticsの設定
gtag.pageview(url)
};
router.events.on("routeChangeComplete", handleComplete);
router.events.on("routeChangeError", handleComplete);
return () => {
router.events.off('routeChangeComplete', handleComplete)
}
},[router])
return (
<>
<Head>
<meta name="viewport" content="width=device-width,initial-scale=1.0,minimum-scale=1.0,maximum-scale=1.0,user-scalable=no" />
<link rel="icon" href="https://test.com/favicon.ico" />
<link rel="apple-touch-icon" href="https://test.com/favicon.ico" />
</Head>
<DefaultSeo {...SEO} />
{/* Analytics */}
<Script
async
src={`https://www.googletagmanager.com/gtag/js?id=${GA_TRACKING_ID}`}
/>
<Script
id="analytics"
dangerouslySetInnerHTML={{
__html: `
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
gtag('js', new Date());
gtag('config', '${GA_TRACKING_ID}', {
page_path: window.location.pathname,
});
`,
}}
/>
<Provider store={redux}>
<Header />
<Component {...pageProps} />
<Footer />
</Provider>
</>
)
}
_document.js
import Document, { Html, Head, Main, NextScript } from 'next/document';
class MyDocument extends Document {
static async getInitialProps(ctx) {
const initialProps = await Document.getInitialProps(ctx);
return { ...initialProps };
}
render() {
return (
<Html lang="ja">
<Head>
<meta charSet="UTF-8" />
<meta name="theme-color" content="#ff889e" />
{/* google広告 */}
<script async
src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?client=xxxxxxxxxxx"
crossOrigin="anonymous"></script>
</Head>
<body>
<Main />
<NextScript />
</body>
</Html>
);
}
}
export default MyDocument;

Inertia Link component misbehavior

I am using Inertia to create React & Laravel website. The Inertia Link component pops up a stack of the page visited instead of rendering the page as expected. What could be the issue?
Initially it worked just fine. Here's my Header component.
import React, { useState } from "react";
import { Link } from "#inertiajs/inertia-react";
import MenuIcon from "#mui/icons-material/Menu";
import CloseIcon from "#mui/icons-material/Close";
import logo from "../../../../../public/images/zawadiz.png";
import "./Header.css";
const Header = () => {
const [showMobileMenu, setShowMobile] = useState(false);
return (
<header>
<div className="container">
<Link href="/" className="brand">
<img src={logo} alt="" className="logo" width={130} />
</Link>
<div
className="menu"
style={{
right: showMobileMenu ? 0 : "-100%",
transition: "all .5s ease",
}}
>
<Link href="/">Home</Link>
<Link href="/about">About</Link>
<Link href="/services">Services</Link>
<Link href="/products">Products</Link>
<Link href="/contact">Contact</Link>
</div>
<div className="menu-icon">
{showMobileMenu ? (
<CloseIcon
onClick={() => setShowMobile(!showMobileMenu)}
/>
) : (
<MenuIcon
onClick={() => setShowMobile(!showMobileMenu)}
/>
)}
</div>
</div>
</header>
);
};
export default Header;
app.blade.php
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0" />
<link rel="icon" type="image/svg+xml" href="../../public/images/favicon.png" />
<link href="{{ mix('/css/app.css') }}" rel="stylesheet" />
<script src="{{ mix('/js/app.js') }}" defer></script>
#inertiaHead
</head>
<body>
#inertia
</body>
</html>
Please see the screenshot below for the behavior experienced.
Thanks in advance.

ReactJS - Uncaught RangeError: Maximum call stack size exceeded

I am trying to make a contact-manager-app from a YouTube video:
https://www.youtube.com/watch?v=0riHps91AzE&lc=Ugybk5M3ofjHsO8uHjd4AaABAg.9WHwkOL6qXV9WJu89p6VTV
Every time, I enter the inputs and click Add, the following error pops-up:
the screen-shot of the main page
I also get "6 moderate severity vulnerabilities" while downloading uuidv4. ( Put just in case, if it might help )
Also got "Module not found: Error: Can't resolve 'util' in 'C:\Users\loki\OneDrive\Desktop\ReactJS-YouTube\contact-app\node_modules\uuidv4\build\lib"
Here are all my files:
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#000000" />
<meta
name="description"
content="Web site created using create-react-app"
/>
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/semantic-ui/2.4.1/semantic.min.css" integrity="sha512-8bHTC73gkZ7rZ7vpqUQThUDhqcNFyYi2xgDgPDHc+GXVGHXq+xPjynxIopALmOPqzo9JZj0k6OqqewdGO3EsrQ==" crossorigin="anonymous" referrerpolicy="no-referrer" />
<title>React App</title>
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div>
</body>
</html>
App.js
import React, { useState, useEffect } from "react";
import { uuid } from "uuidv4";
import "./App.css";
import Header from "./Header";
import AddContact from "./AddContact";
import ContactList from "./ContactList";
function App() {
const LOCAL_STORAGE_KEY = "contacts";
const [contacts, setContacts] = useState([]);
const addContactHandler = (contact) => {
console.log(contact);
setContacts([...contacts, { id: uuid(), ...contact }]);
};
const removeContactHandler = (id) => {
const newContactList = contacts.filter((contact) => {
return contact.id !== id;
});
setContacts(newContactList);
};
useEffect(() => {
const retriveContacts = JSON.parse(localStorage.getItem(LOCAL_STORAGE_KEY));
if (retriveContacts) setContacts(retriveContacts);
}, []);
useEffect(() => {
localStorage.setItem(LOCAL_STORAGE_KEY, JSON.stringify(contacts));
}, [contacts]);
return (
<div className="ui container">
<Header />
<AddContact addContactHandler={addContactHandler} />
<ContactList contacts={contacts} getContactId={removeContactHandler} />
</div>
);
}
export default App;
ContactList.js
import React from "react";
import ContactCard from "./ContactCard";
const ContactList = (props) => {
console.log(props);
const deleteContactHandler = (id) => {
props.getContactId(id);
};
const renderContactList = props.contacts.map((contact) => {
return(
<ContactCard contact={contact} clickHandler = { deleteContactHandler } key = { contact.id}/>
);
})
return(
<div className="ui celled list">
{renderContactList}
</div>
);
}
export default ContactList;
ContactCard.js
import React from "react";
import user from "../images/user.jpg";
const CardContact = (props) => {
const {id, name, email} = props.contact;
return(
<div className="item">
<img className="ui avatar image" src={user} alt="user" />
<div className="content">
<div className="header">{name}</div>
<div>{email}</div>
</div>
<i className="trash alternate outline icon"
style={{color:"red",marginTop:"7px"}}
onClick={() => props.clickHandler(id)}>
</i>
</div>
);
};
export default CardContact;
AddContact.js
import React from "react";
class AddContact extends React.Component {
state = {
name: "",
email: "",
};
add = (e) => {
e.preventDefault();
if (this.state.name === "" || this.state.email === "") {
alert("ALl the fields are mandatory!");
return;
}
this.props.addContactHandler(this.state);
this.setState({ name: "", email: "" });
};
render() {
return (
<div className="ui main">
<h2>Add Contact</h2>
<form className="ui form" onSubmit={this.add}>
<div className="field">
<label>Name</label>
<input
type="text"
name="name"
placeholder="Name"
value={this.state.name}
onChange={(e) => this.setState({ name: e.target.value })}
/>
</div>
<div className="field">
<label>Email</label>
<input
type="text"
name="email"
placeholder="Email"
value={this.state.email}
onChange={(e) => this.setState({ email: e.target.value })}
/>
</div>
<button className="ui button blue">Add</button>
</form>
</div>
);
}
}
export default AddContact;
From the uuidv4 npm page:
Most of the functionality of uuidv4 module is already included in uuid since version 8.3.0, so most of the functions of uuidv4 module have already been marked as deprecated.
So, importing uuidv4 module in your App.js is causing this error.
You can upgrade to the latest version of uuid library to get rid of this error.
Run these commands in the terminal in your project directory.
npm uninstall uuidv4
npm install uuid
And now, in App.js import uuid module instead of uuidv4
import { v4 as uuid } from 'uuid';
And now, you can use uuid() function to create UUIDs
To check more about uuid, you can see there documentation: https://github.com/uuidjs/uuid#quickstart
In your App.js:
On each useEffect (page renders) you are calling setContract , on useEffect below you are watching that contract as dependency , so it's rendering when contract is changing
useEffect(() => {
const retriveContacts = JSON.parse(localStorage.getItem(LOCAL_STORAGE_KEY));
if (retriveContacts) setContacts(retriveContacts);
}, []);
useEffect(() => {
localStorage.setItem(LOCAL_STORAGE_KEY, JSON.stringify(contacts));
}, [contacts]);

Can return directly a component from a handling event function in Reactjs?

I have a form to add a category. I expect that when clicking on the add button, there is a notification showing in the corner screen if the input is empty. My idea is returning a Çomponent( or function) contains a toast in handleAddNewCategory() but it's not showing. I know that the library react-bootstrap can do showing toast like this
handleAddNewCategory(){
if(condition)
return toastr.info("message",...);
}
but I don't want to use it for now. Can anyone give me a solution to solve this with using only bootstrap. I'm totally new to Reactjs.
This is some minimal file:
import React, { Component } from "react";
import AddNewCategory from "./AddNewCategory";
import Notification from "./Notification";
class CategoryList extends Component {
constructor(props) {
super(props);
this.state = {
categoryNameInput: "",
categoryDescriptionInput: "",
};
}
...
handleAddNewCategory = () => {
const { categoryNameInput, categoryDescriptionInput } = this.state;
if (categoryNameInput.trim() === "") {
return <Notification message="Please enter category name" />;
}
if (categoryDescriptionInput.trim() === "") {
return <Notification message="Please enter category description" />;
}
};
render() {
return (
<div className="container">
...
<AddNewCategory handleAddNewCategory={this.handleAddNewCategory} />
...
</div>
);
}
}
export default CategoryList;
Notification component
import React, { Component } from "react";
class Notification extends Component {
render() {
const myStyle = {
zIndex: "1001",
position: "absolute",
top: "10px",
right: "10px",
};
return (
<div className="toast col-2" style={myStyle} data-autohide="false">
<div className="toast-header">
<strong className="mr-auto text-primary">Notice</strong>
</div>
<div className="toast-body">{this.props.message}</div>
</div>
);
}
}
export default Notification;
index.html
<!DOCTYPE html>
<html lang="en">
<head>
...
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.0/css/bootstrap.min.css" integrity="sha384-9aIt2nRpC12Uk9gS9baDl411NQApFmC26EwAOH8WgZl5MYYxFfc+NcPb1dKGj7Sk" crossorigin="anonymous">
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div>
<script src="https://code.jquery.com/jquery-3.5.1.slim.min.js" integrity="sha384-DfXdz2htPH0lsSSs5nCTpuj/zy4C+OGpamoFVy38MVBnE+IbbVYUew+OrCXaRkfj" crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/popper.js#1.16.0/dist/umd/popper.min.js" integrity="sha384-Q6E9RHvbIyZFJoft+2mJbHaEWldlvI9IOYy5n3zV9zzTtmI3UksdQRVvoxMfooAo" crossorigin="anonymous"></script>
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.5.0/js/bootstrap.min.js" integrity="sha384-OgVRvuATP1z7JjHLkuOU7Xw704+h835Lr+6QL9UvYjZE3Ipu6Tp75j7Bh/kR0JKI" crossorigin="anonymous"></script>
<script>
$(document).ready(function() {
$('.toast').toast('show');
});
</script>
</body>
</html>
don't use jquery to show the toast it's not the best practice and instead of conditionally rendering the component what you can do is. just render it ones and then controlled its visibility.
follow the below code:-
Notification component
import React, { Component } from "react";
class Notification extends Component {
render() {
const myStyle = {
zIndex: "1001",
position: "absolute",
top: "10px",
right: "10px",
};
return (
<div hidden={this.props.showToast} className="toast col-2" style={myStyle} data-autohide="false">
<div className="toast-header">
<strong className="mr-auto text-primary">Notice</strong>
</div>
<div className="toast-body">{this.props.message}</div>
</div>
);
}
}
export default Notification;
Notification component usage :-
class CategoryList extends Component {
constructor(props) {
super(props);
this.state = {
categoryNameInput: "",
categoryDescriptionInput: "",
toastMessage:'',
showToast:false
};
}
...
handleAddNewCategory = () => {
const { categoryNameInput, categoryDescriptionInput } = this.state;
if (categoryNameInput.trim() === "") {
this.setState({
toastMessage:'Please enter category name',
showToast:true
})
}
if (categoryDescriptionInput.trim() === "") {
this.setState({
toastMessage:'Please enter category name',
showToast:true
})
}
};
render() {
return (
<div className="container">
<Notification message={this.state.toastMessage} showToast={this.state.showToast}/>;
...
<AddNewCategory handleAddNewCategory={this.handleAddNewCategory} />
...
</div>
);
}
}
export default CategoryList;
here what i do is i initialise the boolean in state and enable it so that toast will be visible and i pass it directly throw props so that whenever your message and boolean changes it automatically reflects in your notification controller.
and to remove the toast just set {showToast} to false whenever you want. and remove the jquery part from your code

Problem with jquery(react-owl-carousel) in gatsby/react

please help. I was trying to use react-owl-carousel in a gatsby site but I have run into a problem. The error says:
Here's my gatsby-node.js
exports.onCreateWebpackConfig = ({ plugins, actions }) => {
actions.setWebpackConfig({
plugins: [
plugins.define({
$: 'jquery',
jQuery: 'jquery',
'window.jQuery': 'jquery',
}),
],
})
}
try this in your gatsby-node.js
exports.onCreateWebpackConfig = ({ actions }) => {
const { setWebpackConfig } = actions
setWebpackConfig({
externals: {
jquery: 'jQuery', // important: 'Q' capitalized
},
})
}
and in html.js
import React from 'react'
import PropTypes from 'prop-types'
export default function HTML(props) {
return (
<html {...props.htmlAttributes}>
<head>
<script
src="https://code.jquery.com/jquery-3.3.1.min.js"
integrity="sha256-FgpCb/KJQlLNfOu91ta32o/NMZxltwRo8QtmkMRdAu8="
crossOrigin="anonymous"
/>
<meta charSet="utf-8" />
<meta httpEquiv="x-ua-compatible" content="ie=edge" />
<meta
name="viewport"
content="width=device-width, initial-scale=1, shrink-to-fit=no"
/>
{props.headComponents}
</head>
<body {...props.bodyAttributes}>
{props.preBodyComponents}
<div
key={`body`}
id="___gatsby"
dangerouslySetInnerHTML={{ __html: props.body }}
/>
{props.postBodyComponents}
</body>
</html>
)
}
HTML.propTypes = {
htmlAttributes: PropTypes.object,
headComponents: PropTypes.array,
bodyAttributes: PropTypes.object,
preBodyComponents: PropTypes.array,
body: PropTypes.string,
postBodyComponents: PropTypes.array,
}
put html.js in the src folder

Resources