Error Hydration failed with react-speech-recognition - reactjs

I wanted to try speech recognition in NextJS 13. I installed react-speech-recognition and copy/pasted the provided example. But I am getting Error: Hydration failed because the initial UI does not match what was rendered on the server.
I tried to rollback react to v18.1, removed .next folder but it didn't help. I scrolled NextJS documentation about React Hydration Error, but I don't call windows and don't put div tag in p.
Any ideas what can be the issue?
Code:
'use client'
import 'regenerator-runtime/runtime'
import React from 'react'
import SpeechRecognition, {
useSpeechRecognition,
} from 'react-speech-recognition'
export default function page() {
const {
transcript,
listening,
resetTranscript,
browserSupportsSpeechRecognition,
} = useSpeechRecognition()
if (!browserSupportsSpeechRecognition) {
return <span>Browser doesn't support speech recognition.</span>
}
return (
<div>
<p>Microphone: {listening ? 'on' : 'off'}</p>
<button onClick={SpeechRecognition.startListening}>Start</button>
<button onClick={SpeechRecognition.stopListening}>Stop</button>
<button onClick={resetTranscript}>Reset</button>
<p>{transcript}</p>
</div>
)
}

The hydration error is caused by these lines:
if (!browserSupportsSpeechRecognition) {
return <span>Browser doesn't support speech recognition.</span>
}
Because you are using the 'use client' directive, this component behaves as traditional page components on previous Next.js versions (The page is pre-rendered and then sent to the client to be hydrated). The library you are using checks if webkitSpeechRecognition or SpeechRecognition exists in the window object in order to set the browserSupportsSpeechRecognition boolean, but window is not available server-side (it is undefined). The condition above evaluates to true thus creating the mismatch between what was rendered on the server and on the client-side's first render (You can view the page's source and you will notice that the not supported text was rendered on the server).
You can solve the issue using useState and useEffect hooks, taking advantage of the fact that useEffect only runs on the client-side:
'use client'
import React, { useState, useEffect } from 'react'
import 'regenerator-runtime/runtime'
import SpeechRecognition, {
useSpeechRecognition
} from 'react-speech-recognition'
const Page = () => {
const [speechRecognitionSupported, setSpeechRecognitionSupported] =
useState(null) // null or boolean
const {
transcript,
listening,
resetTranscript,
browserSupportsSpeechRecognition
} = useSpeechRecognition()
useEffect(() => {
// sets to true or false after component has been mounted
setSpeechRecognitionSupported(browserSupportsSpeechRecognition)
}, [browserSupportsSpeechRecognition])
if (speechRecognitionSupported === null) return null // return null on first render, can be a loading indicator
if (!speechRecognitionSupported) {
return <span>Browser does not support speech recognition.</span>
}
return (
<div>
<p>Microphone: {listening ? 'on' : 'off'}</p>
<button onClick={SpeechRecognition.startListening}>Start</button>
<button onClick={SpeechRecognition.stopListening}>Stop</button>
<button onClick={resetTranscript}>Reset</button>
<p>{transcript}</p>
</div>
)
}
export default Page

I had the same problem, but I think checking for server rendering (when the window object is undefined) is a little bit less messy solution:
const isServer = typeof window === "undefined";
if (!browserSupportsSpeechRecognition && !isServer) {
return <div>Your browser does not support speech recognition.</div>;
}
Works well!

Related

loading components twice, probably because of useEffect wrong set-up

I have built a ToDo React App (https://codesandbox.io/s/distracted-easley-zjdrkv) that does the following:
User write down an item in the input bar
User hit "enter"
Item is saved into the list below (local storage, will update later)
There is some logic to parse the text and identify tags (basically if the text goes "#tom:buy milk" --> tag=tom, text=buy milk)
The problem I am facing are:
useEffect runs twice at load, and I don't understand why
After the first item gets saved, if I try saving a second item, the app crashes. Not sure why, but I feel it has to do with the point above...and maybe the event listener "onKeyDown"
App
import { useState, useEffect } from 'react'
import './assets/style.css';
import data from '../data/data.json'
import InputBar from "./components/InputBar/InputBar"
import NavBar from "./components/NavBar/NavBar"
import TabItem from "./components/Tab/TabItem"
function App() {
const [dataLoaded, setDataLoaded] = useState(
() => JSON.parse(localStorage.getItem("toDos")) || data
)
useEffect(() => {
localStorage.setItem("toDos", JSON.stringify(dataLoaded))
console.log('update')
}, [dataLoaded])
function deleteItem(id){
console.log(id)
setDataLoaded(oldData=>{
return {
...oldData,
"items":oldData.items.filter(el => el.id !== id)
}
})
}
return (
<div className='container'>
<NavBar/>
<InputBar
setNewList = {setDataLoaded}
/>
{
//Items
dataLoaded.items.map(el=>{
console.log(el)
return <TabItem item={el} key={el.id} delete={deleteItem}/>
})
}
</div>
)
}
export default App
InputBar
import { useState, useEffect } from 'react'
import { nanoid } from 'nanoid'
import '../../assets/style.css';
export default function InputBar(props){
const timeElapsed = Date.now();
const today = new Date(timeElapsed);
function processInput(s) {
let m = s.match(/^(#.+?:)?(.+)/)
if (m) {
return {
tags: m[1] ? m[1].slice(1, -1).split('#') : ['default'],
text: m[2],
created: today.toDateString(),
id:nanoid()
}
}
}
function handleKeyDown(e) {
console.log(e.target.value)
console.log(document.querySelector(".main-input-div input").value)
if(e.keyCode==13){
props.setNewList(oldData =>{
return {
...oldData,
"items" : [processInput(e.target.value), ...oldData.items]
}
}
)
e.target.value=""
}
}
return(
<div className="main-input-div">
<input type="text" onKeyDown={(e) => handleKeyDown(e)}/>
</div>
)
}
Tab
import { useState } from 'react'
import "./tab-item.css"
import { FontAwesomeIcon } from '#fortawesome/react-fontawesome'
import { faTrash } from "#fortawesome/free-solid-svg-icons";
export default function TabItem(props) {
return (
<div className="tab-item">
<div className="tab-item-text">{props.item.text}</div>
<div className="tab-item-actions">
<FontAwesomeIcon icon={faTrash} onClick={()=>props.delete(props.item.id)}/>
</div>
<div className="tab-item-details">
<div className="tab-item-details-tags">
{
props.item.tags.map(el=><div className="tab-item-details-tags-tag">{el}</div>)
}
</div>
</div>
<div className="tab-item-date">{props.item.created}</div>
</div>
)
}
The above answer is almoost correct. I am adding more info to the same concepts.
useEffect running twice:
This is most common ask in recent times. It's because the effect runs twice only in development mode & this behavior is introduced in React 18.0 & above.
The objective is to let the developer see & warn of any bugs that may appear due to a lack of cleanup code when a component unmounts. React is basically trying to show you the complete component mounting-unmounting cycle. Note that this behavior is not applicable in the production environment.
Please check https://beta-reactjs-org-git-effects-fbopensource.vercel.app/learn/synchronizing-with-effects#step-3-add-cleanup-if-needed for a detailed explanation.
App crashes on second time: It's probably because you are trying to update the input value from event.target.value if you want to have control over the input value, your input should be a controlled component meaning, your react code should handle the onChange of input and store it in a state and pass that state as value to the input element & in your onKeyDown handler, reset the value state. That should fix the crash.
export default function InputBar(props){
const [inputVal, setInputVal] = useState("");
function handleKeyDown(e) {
console.log(e.target.value)
console.log(document.querySelector(".main-input-div input").value)
if(e.keyCode==13){
props.setNewList(oldData =>{
return {
...oldData,
"items" : [processInput(e.target.value), ...oldData.items]
}
}
)
setInputVal("")
}
}
return(
<div className="main-input-div">
<input
type="text"
value={inputVal}
onChange={(e) => {setInputVal(e.target.value)}}
onKeyDown={(e) => handleKeyDown(e)}
/>
</div>
)
}
Hope this helps. Cheers!
Your app is using strict mode, which in a development mode renders components twice to help detect bugs (https://reactjs.org/docs/strict-mode.html#detecting-unexpected-side-effects).
root.render(
<StrictMode>
<App />
</StrictMode>
);
As for the crash, I think it's happening due to props.setNewList being an asynchronous call and the resetting of e.target.value - something like this seemed to fix it for me:
function handleKeyDown(e) {
console.log(e.target.value)
console.log(document.querySelector(".main-input-div input").value)
if(e.keyCode==13){
const inputVal = e.target.value;
props.setNewList(oldData =>{
return {
...oldData,
"items" : [processInput(inputVal), ...oldData.items]
}
}
)
e.target.value=""
}
}
I will add, that using document.querySelector to get values isn't typical usage of react, and you might want to look into linking the input's value to a react useState hook.
https://reactjs.org/docs/forms.html#controlled-components

Why is react skeleton not rendering?

I have a component Recommended that makes a service call to firebase and renders the returned data. During the loading delay at the database call, I want to render a react skeleton, as follows:
Recommended.js
import { useState, useEffect } from "react";
import Skeleton from "react-loading-skeleton";
import { getVenues } from "../services/firebase";
import VenueCard from "./VenueCard";
const Reccomended = () => {
const [venues, setVenues] = useState([]);
useEffect(() => {
async function getAllVenues() {
const response = await getVenues();
await setVenues(response);
}
getAllVenues();
}, []);
venues[0] ? console.log(true) : console.log(false)
return (
<div>
{!venues[0] ? (
<>
<Skeleton />
</>
) : (
<>
<p className="recommended">Recommended for Josh</p>
<VenueCard venues={venues} />
</>
)}
</div>
);
};
export default Reccomended;
However, the skeleton is not rending during loading. The returning data is saved to the state variable venues, and I'm using the 'truthiness' as a conditional for the render. I tested this by logging the following:
venues[0] ? console.log(true) : console.log(false)
In the browser it initially logged false, followed quickly by true
So given this, I don't understand why the skeleton isn't loading - any suggestions?
I've also passed parameters into <Skeleton/> which didn't change anything.
You must include the CSS styles, or you won't see anything. Just add
import "react-loading-skeleton/dist/skeleton.css";
with the rest of the imports.
This is documented in the package readme in the react-loading-skeleton basic Usage section

How to set window resize event listener value to React State?

This issue is very simple but I probably overlook very little point. Window screen size is listening by PostLayout component. When window width is less than 768px, I expect that isDesktopSize is false. I tried everything like using arrow function in setIsDesktopSize, using text inside of true or false for state value, using callback method etc... but it's not working.
PostLayout shared below:
import React, {useState,useEffect, useCallback} from 'react'
import LeftSideNavbar from './LeftSideNavbar'
import TopNavbar from './TopNavbar'
export default function PostLayout({children}) {
const [isDesktopSize, setIsDesktopSize] = useState(true)
let autoResize = () => {
console.log("Desktop: " + isDesktopSize);
console.log(window.innerWidth);
if(window.innerWidth < 768 ){
setIsDesktopSize(false)
}else{
setIsDesktopSize(true)
}
}
useEffect(() => {
window.addEventListener('resize', autoResize)
autoResize();
}, [])
return (
<>
<TopNavbar isDesktopSize={isDesktopSize}/>
<main>
<LeftSideNavbar/>
{children}
</main>
</>
)
}
console log is shared below:
Desktop: true
627
This could probably be extracted into a custom hook. There's a few things you'd want to address:
Right now you default the state to true, but when the component loads, that may not be correct. This is probably why you see an incorrect console log on the first execution of the effect. Calculating the initial state to be accurate could save you some jank/double rendering.
You aren't disconnecting the resize listener when the component unmounts, which could result in an error attempting to set state on the component after it has unmounted.
Here's an example of a custom hook that addresses those:
function testIsDesktop() {
if (typeof window === 'undefined') {
return true;
}
return window.innerWidth >= 768;
}
function useIsDesktopSize() {
// Initialize the desktop size to an accurate value on initial state set
const [isDesktopSize, setIsDesktopSize] = useState(testIsDesktop);
useEffect(() => {
if (typeof window === 'undefined') {
return;
}
function autoResize() {
setIsDesktopSize(testIsDesktop());
}
window.addEventListener('resize', autoResize);
// This is likely unnecessary, as the initial state should capture
// the size, however if a resize occurs between initial state set by
// React and before the event listener is attached, this
// will just make sure it captures that.
autoResize();
// Return a function to disconnect the event listener
return () => window.removeEventListener('resize', autoResize);
}, [])
return isDesktopSize;
}
Then to use this, your other component would look like this (assuming your custom hook is just in this same file -- though it may be useful to extract it to a separate file and import it):
import React, { useState } from 'react'
import LeftSideNavbar from './LeftSideNavbar'
import TopNavbar from './TopNavbar'
export default function PostLayout({children}) {
const isDesktopSize = useIsDesktopSize();
return (
<>
<TopNavbar isDesktopSize={isDesktopSize}/>
<main>
<LeftSideNavbar/>
{children}
</main>
</>
)
}
EDIT: I modified this slightly so it should theoretically work with a server-side renderer, which will assume a desktop size.
Try this, you are setting isDesktopSizze to 'mobile', which is === true
const [isDesktopSize, setIsDesktopSize] = useState(true)
let autoResize = () => {
console.log("Desktop: " + isDesktopSize);
console.log(window.innerWidth);
if(window.innerWidth < 768 ){
setIsDesktopSize(true)
}else{
setIsDesktopSize(false)
}
}
I didn't find such a package on npm and I thought it would be nice to create one: https://www.npmjs.com/package/use-device-detect. I think it will help someone :)

Cannot read property 'company' of null

console.log(profile); works well and it shows this.
but when I use console.log(profile.company); to get the company name.
it show me the Cannot read property 'company' of null error message.
how to solve this err? Any help is highly appreciated.
here is the code
import React,{Fragment,useEffect}from 'react'
import PropTypes from 'prop-types'
import Loading from "../layout/Loading.js"
import {connect} from "react-redux"
import {getProfileById} from "../../redux/profile/profileAction.js"
import { Link } from 'react-router-dom'
import ProfileTop from "./ProfileTop.js"
import ProfileAbout from "./ProfileAbout.js"
import ProfileExperience from "./ProfileExperience.js"
import ProfileEducation from "./ProfileEducation.js"
import ProfileGithub from "./ProfileGithub.js"
const Profile = ({getProfileById,match,profileData,loginData}) => {
const {loading,profile}=profileData
console.log(profile); //works
console.log(profile.company); //error occurred
useEffect(()=>{
getProfileById(match.params.userId)
},[getProfileById,match.params.userId])
return (
<div style={{marginTop:"100px"}}>
{
profile ===null||loading? (<Loading/>):
(<Fragment>
<Link to="/profiles" className="btn btn-light"> Back to profiles</Link>
{
(loginData.isAuthenticated && loginData.loading===false&&loginData.user_data.userid===match.params.userId) ?
(<Link to="/edit-profile" className="btn btn-dark">Edit profile</Link>):null
}
<div className="profile-grid my-1">
<ProfileTop profile={profile}></ProfileTop>
<ProfileAbout profile={profile}></ProfileAbout>
<ProfileExperience profile={profile}></ProfileExperience>
<ProfileEducation profile={profile}></ProfileEducation>
<ProfileGithub profile={profile}></ProfileGithub>
{
profile.github_user_name===""? null:<ProfileGithub profile={profile}></ProfileGithub>
}
</div>
</Fragment>)
}
</div>
)
}
Profile.propTypes = {
profileData:PropTypes.object.isRequired,
}
const mapStateToProps=(state)=>{
return{
profileData:state.profileData,
loginData:state.loginData
}
}
export default connect(mapStateToProps,{getProfileById})(Profile)
Cannot read property 'company' of null error message
It is clear that the 'company' object is null (might be for the initial render)
and you are getting the error because you are accessing the property on the null object.
In the case of TypeScript you can use,
profile?.company.
It is called optional chaining
https://www.typescriptlang.org/docs/handbook/release-notes/typescript-3-7.html#optional-chaining
but in the normal JS, we have to use if statement and check for null case.
For the above example your template is completely depends on the profile.
So that initially itself check the value of profile, if the value is undefined/null then return an empty template.
if(!profile){
return null;
}
return <Your template>
As mentioned in the comments, browser consoles will not print the content of an object right away. To do so you would need to do something such as JSON.stringify(profile).
Also, it's perfectly fine to console.log on functional components in my opinion, this will let you know when components render (but don't get too caught up in why components render so often, renders are usually not expensive).
If you are using a recent version of Create React App, you can try optional chaining (?.):
console.log(profile);
console.log(profile?.company); // equivalent to (profile && profile.company)
// Or if you need to know the content exactly in the moment:
console.log(JSON.stringify(profile));

Creating Toast Message without button pressed in react js

**Hi Everyone am new to react js can anyone please guide me on how to display Toast Message without using button, i want to show error message on page load using Toast Notification can anyone help me or suggest me on how to do it
If it's just a simple, one time toast message, you could use the React Hooks API.
Some pseudocode, not considering animations and styles:
import React, { useEffect, useState } from 'react'
function ToastMessage({ message, duration = 2000 }) {
const [ showToast, setShowToast ] = useState(false)
// the empty array as the second argument
// ensures it is fired only after component mount.
useEffect(() => {
setShowToast(true)
// Hides the message after 2 default seconds (configurable)
setTimeout(() => setShowToast(false), duration)
}, [])
return (
showToast &&
<div>{props.message}</div> || null
)
}
// Then in your page...
// Assuming you have your error coming from somewhere in your props
function Page(props) {
const [ hasError, setHasError ] = useState(props.error !== '')
return (
<>
<Header />
<PageContent />
<Footer />
{ hasError &&
<ToastMessage
message={props.error}
duration={1000} />
}
</>
)
}

Resources