Print one phrase under another one using speech recognition - reactjs

The recognition should start when I click recHandler button, then the recognized phrase should be printed below. If I click the button again the recognition should be activated and recognized phrase should be printed under the old one, as result I have to have a list of recognized phrases.
The problem is if I run this part of code setMessage([...message, transcript]) inside useEffect() I get infinite loop of re-renders.
If I run it without useEffect(), I get an error: Error: Too many re-renders. React limits the number of renders to prevent an infinite loop.
import React, { useState, useEffect } from 'react'
import React from 'react';
import SpeechRecognition, { useSpeechRecognition } from 'react-speech-recognition';
const Dictaphone = () => {
const {
transcript,
listening,
resetTranscript,
browserSupportsSpeechRecognition
} = useSpeechRecognition();
const [message, setMessage] = useState([])
const item = (
<ul>
{message?.map((n) => (
<li>{n}</li>
))}
</ul>
)
if (!browserSupportsSpeechRecognition) {
return <span>Browser doesn't support speech recognition.</span>;
}
function recHandler(){
SpeechRecognition.startListening()
}
useEffect(()=>{
setMessage([...message, transcript])
})
return (
<div>
<p>Microphone: {listening ? 'on' : 'off'}</p>
<button onClick={recHandler}>Start</button>
<div>{item}</div>
</div>
);
};

Your useEffect cause re-render, you are missing []
useEffect(()=>{
setMessage([...message, transcript])
},[transcript])

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

How to create a useEffect that only updates when firestore updates? [duplicate]

This question already has answers here:
ReactJS and Firebase Quota Reached Very Fast with Small Data
(1 answer)
VERY High number of reads in Firestore database in my React Project
(1 answer)
Firebase Reads Suddenly Spiked with 54k [duplicate]
(1 answer)
Closed last month.
First off, let me say that I probably worded my question terribly... sorry.
I currently have a useEffect in my application that when you load my page it takes the data from my Firestore collection and sets it to an array to map on screen with a component. It works perfectly fine, however after about 10 minutes of running my application I receive the error "#firebase/firestore: Firestore (9.15.0): Uncaught Error in snapshot listener: FirebaseError: [code=resource-exhausted]: Quota exceeded.".
I added a console log and it looks like this is because my useEffect is constantly trying to read the data from the collection in firestore.
My question is, is there a way to only make this useEffect update the data / run when a new collection is added or deleted / modified?
Code:
import React, { useState, useEffect, useRef } from 'react';
import '../index.css';
import './Home.css';
import Note from '../components/Note';
import { useAuth } from '../contexts/AuthContext';
import { db } from '../firebase';
import { ReactComponent as Add } from '../imgs/add.svg';
import { doc, onSnapshot, query, collection } from 'firebase/firestore';
function Home() {
// Firebase states
const { currentUser } = useAuth();
const noteboardCollectionRef = collection(db, `users/${currentUser.uid}/noteboard-app`);
// useStates
const [notes, setNotes] = useState([]);
//useEffect
useEffect(()=>{
const q = query(noteboardCollectionRef)
const noteboardFirebase = onSnapshot(q, (querySnapshot)=>{
let noteArr = []
querySnapshot.forEach((doc)=>{
noteArr.push({...doc.data(), id: doc.id})
});
setNotes(noteArr);
console.log(notes)
})
return noteboardFirebase;
})
// Start of all functions
return (
<>
<div className='home-container'>
<div className='home-header flex'>
<h1 className='font-carter-one'>Noteboard</h1>
<div className='home-header-dark-container'>
<label className='font-carter-one'>Dark Mode</label>
<span className='home-header-dark-mode'>
<input type='checkbox' checked/>
<span className='dark-mode-slider pointer'/>
</span>
</div>
</div>
<div className='home-body flex-center-all'>
<div className='home-new-note flex-center-all flex-column pointer' onClick={()=>{setAddNoteModal(true)}}>
<Add className='pointer' id='new-note'/>
<h2 className='font-carter-one'>Add Note</h2>
</div>
{notes.map(((note, index) => <Note key={index} note={note} />))}
</div>
</div>
</>
)
}
export default Home;
Thank you in advanced!
You are facing this error because you dont have dependencies in the useEffect i.e
useEffect(()=>{...
},[]) // You are missing this []
Because of which the useEffect runs every time the page is rendered , which is causing to make unlimited requests to the server which is leading to quota exceeded error in firebase
If array is null i.e [] then it runs only the first time the page is rendered.
If you want the useEffect to run only when the firestore is changed add dependency of notes i.e [notes].
Now useEffect will run only when there is change in notes !!
Your final code should look like:
useEffect(()=>{
const q = query(noteboardCollectionRef)
const noteboardFirebase = onSnapshot(q, (querySnapshot)=>{
let noteArr = []
querySnapshot.forEach((doc)=>{
noteArr.push({...doc.data(), id: doc.id})
});
setNotes(noteArr);
console.log(notes)
})
return noteboardFirebase;
}[notes]) // <-- add dependency of notes
Add dependency to [] like this:
useEffect(()=>{
doSomething()
},[dependency])
when dependency is changed, doSomething() will run again.
You can see detail in here!
So, as for your question you should edit code to this:
//useEffect
useEffect(()=>{
const q = query(noteboardCollectionRef)
const noteboardFirebase = onSnapshot(q, (querySnapshot)=>{
let noteArr = []
querySnapshot.forEach((doc)=>{
noteArr.push({...doc.data(), id: doc.id})
});
setNotes(noteArr);
console.log(notes)
})
return noteboardFirebase;
},[notes])
You could use onSnapshot() to make this happen something like this ;
import { useEffect, useState } from 'react';
import { firestore } from './firebase';
function MyComponent() {
const [document, setDocument] = useState(null);
useEffect(() => {
const unsubscribe = firestore
.doc('my-collection/my-document')
.onSnapshot((doc) => {
setDocument(doc.data());
});
return () => {
unsubscribe();
};
}, []);
return (
<div>
{document ? (
<div>
<h1>{document.title}</h1>
<p>{document.body}</p>
</div>
) : (
<p>Loading...</p>
)}
</div>
);
}

How can I render Childs in react with useState?

I have a basic React code that create an empty list and fill it with data from the db with an useEffect. My problem is that when I try to make a map with the elements and render a new component, if I use an useState I can't do it.
Here is my code:
import "./../../../../assets/styles/logged/pedidos/pedidos.min.css"
import { Context } from "../../../../App";
import { useContext, useEffect, useState } from "react";
import { PedidoCamionInterface } from "../../../../domain/entities/pedido_camion/pedido_camion_interface";
export function Pedidos() {
const providers = useContext(Context);
const [pedidos, setPedidos] = useState<PedidoCamionInterface[]>([]);
useEffect(() => {
//TODO
providers.providers.pedidosDb.getPedidosCamion([1, 2, 3], "").then((pedidos) => {
setPedidos(pedidos);
});
}, []);
return (
<div id="content">
<h1>Pedidos de camiĆ³n</h1>
<div id="pedidos">
{pedidos.map((pedido: PedidoCamionInterface) => { return pedidoDiv(pedido); })}
</div>
</div>
);
}
const pedidoDiv = (pedido: PedidoCamionInterface) => {
const [selected, setSelected] = useState<boolean>(false);
return (
<div className={`pedido ${selected ? "selected" : ""}`} key={pedido.idPedido}>
<span>{pedido.nombrePedido}</span>
<span>{pedido.tienda}</span>
</div>
);
}
This is the error that I get:
Warning: React has detected a change in the order of Hooks called by Pedidos. This will lead to bugs and errors if not fixed. For more information, read the Rules of Hooks: https://reactjs.org/link/rules-of-hooks
Previous render Next render
------------------------------------------------------
1. useContext useContext
2. useState useState
3. useEffect useEffect
4. undefined useState
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Pedidos#http://localhost:5173/src/ui/pages/logged/pedidos/pedidos.tsx?t=1667424544020:22:31
RenderedRoute#http://localhost:5173/node_modules/.vite/deps/react-router-dom.js?v=396bef63:2437:7
Routes#http://localhost:5173/node_modules/.vite/deps/react-router-dom.js?v=396bef63:2746:7
Router#http://localhost:5173/node_modules/.vite/deps/react-router-dom.js?v=396bef63:2697:7
BrowserRouter#http://localhost:5173/node_modules/.vite/deps/react-router-dom.js?v=396bef63:3079:7
App#http://localhost:5173/src/App.tsx?t=1667424544020:30:35 react-dom.development.js:86:29
Thanks for all.
Be able to render the child
I think the problem might lie in the .map() function.
Instead of
{pedidos.map((pedido: PedidoCamionInterface) => { return pedidoDiv(pedido); })}
try
{pedidos.map((pedido: PedidoCamionInterface) => (<PedidoDiv pedido={pedido} />))}
so React knows to render the component.
Also, change
const pedidoDiv = (pedido: PedidoCamionInterface) => {
to
const pedidoDiv = ({ pedido }: PedidoCamionInterface) => {
so the property pedido is destructured.

How to avoid this message warning "Maximum update depth exceeded..." on NextJs

on NextJs i not understand, how useEffect work. What i need to do, to stop of receiving this warning message
"Maximum update depth exceeded":
The Code bellow is the page, that call a component ListContainer, this page add a item to container.
The page JSX:
import { useState } from "react";
import AppLayout from "../components/AppLayout";
import ListContainer from "../components/ListContainer";
export default function componentCreator(){
const [item,setItem] = useState([])
/* add item to container */
function addItem(){
let newItem = item
newItem.push({
produto: 'Skol 350ml',
preco: '1200,00',
quantidade: 'cx c/ 15 unidades'
})
setItem(newItem)
}
return (
<AppLayout>
<ListContainer items={item} setItems={setItem}/>
<div className="productcardbuttonshow" onClick={() => addItem()}>ADICIONAR</div>
</AppLayout>
)
}
Bellow the component that handle the items, remove or add. But it works, but on console trigger warning messages about update.
Component ListContainer.jsx:
import { useState,useEffect } from "react";
export default function ListContainer(props){
const [html,setHTML] = useState(null)
const [item,setItem] = useState(props.items)
/* refresh html container */
useEffect(() => {
const itemHTML = item.map((itemmap,id) => {
return (
<div id={id} onClick={() => delItem(id)} className="itemProposta">
{itemmap.produto} - {itemmap.quantidade} - R$ {itemmap.preco}
</div>
)
})
setHTML(itemHTML)
})
/* remove item from container */
function delItem(id){
let itemlist = props.items
itemlist.splice(id,1)
props.setItems(itemlist)
}
return (
<>
{html}
</>
)
}
You are getting into an infinite loops of renders. This code is responsible:
useEffect(() => {
const itemHTML = item.map((itemmap,id) => {
return (
<div id={id} onClick={() => delItem(id)} className="itemProposta">
{itemmap.produto} - {itemmap.quantidade} - R$ {itemmap.preco}
</div>
)
})
setHTML(itemHTML)
})
This callback inside useEffect will run after every render, because there is no dependency array. That means after every render, setHTML(itemHTML) is called. And even if the constituent objects of the array itemHTML are same, a new reference of the array is created. A new reference is created because .map() returns a new reference of the array. And although render and update works correctly, infinite rendering is happening.
Consider adding a dependency array to useEffect. For example:
useEffect(() => {
/* function body */
},[props.items]);
Now useEffect callback only runs if props.items reference changes.
Side note (unrelated to your question):
In the below code,
function addItem(){
let newItem = item
newItem.push({
produto: 'Skol 350ml',
preco: '1200,00',
quantidade: 'cx c/ 15 unidades'
})
setItem(newItem)
}
You should do let newItem = [...item], otherwise you are not creating a new reference of item array and setItem(newItem) is basically useless in that case.

How to properly avoid error "Too many re-renders in reactjs"

I used the code below to display records from two table successfully and its working fine
Here is my issue:
Now I need to display and hide a loading image or text as records is being loaded. I have added
const [loading, setLoading] = useState(true);
setLoading(false);
let image_loading;
if (loading) {
image_loading = 'Data is being loaded'
}
in the return I have added the code below
<span>{image_loading}</span>
When I now run the script, it displays error:
Error: Too many re-renders. React limits the number of renders to prevent an infinite loop.
The line of code that seems to cause this error is setLoading(false);. I have a reference solution but it has to do with click event.
Here is the code:
import {initializeBlock, useBase, base, useRecords} from '#airtable/blocks/ui';
import React, { Component } from "react";
import ReactDOM from 'react-dom';
function Rec() {
const [loading, setLoading] = useState(true);
const currentDate = new Date();
const base = useBase();
// get content of first table
const tab1 = base.getTableByNameIfExists('myfirst_table');
// grab all the records from that table
const records = useRecords(tab1);
// get content of second table
const tab2 = base.getTableByNameIfExists('mysecond_table');
// grab all the records from that table
const records2 = useRecords(tab2);
if(records2){
setLoading(false);
}
let image_loading;
if (loading) {
image_loading = 'Data is being loaded'
}
return (
<div>
<span>{image_loading}</span>
<div>
<h1> First Records</h1>
{records.map(record => {
return <li key={record.id}>{record.id} </li>
})}
</div>
<div>
<h1> First Records</h1>
{records2.map(record2 => {
return <li key={record2.id}>{record2.id} </li>
})}
</div>
</div>
);
}
export default Rec;
Typically you want to call setState or useState functions inside of a useEffect, with a condition. The issue is here:
if (records2) {
setLoading(false);
}
By the time the code reaches here, records2 does exist, and so it sets the state. Once the state changes, the component re-renders, and this function is run again, and it goes on in an infinite loop.
You can use useEffect to make the running of setLoading conditional on some other variable. Like this:
useEffect(() => {
if (records2){
setLoading(false)
}
}, [records2])
So now setLoading will only run when records2 changes and if it exists.

Resources