continuous speech to text in react - reactjs

I am trying to make a speech to text component. it is working but when i stop talking it stops also , but i want a loop until i press stop button .
const SpeechRecognition =
window.SpeechRecognition || window.webkitSpeechRecognition;
const recognition = new SpeechRecognition();
This is the code i have tried - https://codesandbox.io/s/clever-clarke-4z7eqb?file=/src/App.js

Don't know what you want but here is how to continuously play the text.
import React, { useEffect } from "react";
function App() {
const [voice, setVoice] = React.useState(false);
const speek = () => {
const SpeechRecognition =
window.SpeechRecognition || window.webkitSpeechRecognition;
const recognition = new SpeechRecognition();
if (voice) recognition.start();
else recognition.stop();
recognition.onresult = (event) => {
const transcript = event.results[0][0].transcript;
console.log(transcript);
};
recognition.onspeechend = () => {
recognition.stop();
if (voice === true) speek(voice); // loop
};
recognition.onerror = (event) => {
console.log("error");
};
};
useEffect(() => {
speek(voice);
}, [voice]);
return (
<>
<button onClick={() => setVoice(!voice)}>
{voice ? "stop" : "start"}
</button>
</>
);
}
export default App;
and here is codebase https://codesandbox.io/s/flamboyant-cray-igzrb7?file=/src/App.js:0-886

Related

React Typewriter effect doesn't reset

I have created a typewriting effect with React and it works perfectly fine. However, when I change the language with i18n both texts don't have the same length and it keeps writing until both texts have the same length and then it changes the language and starts the effect again.
How can I reset the input when the language has changed? How can I reset the input when the component has been destroyed?
I have recorded a video
I have the same issue when I change from one page to another, as both pages have different texts and they don't have the same length.
Here code of my component
export const ConsoleText = ({text, complete = false}) => {
const [currentText, setCurrentText] = useState("");
const translatedText = i18n.t(text);
const index = useRef(0);
useEffect(() => {
if (!complete && currentText.length !== translatedText.length) {
const timeOut = setTimeout(() => {
setCurrentText((value) => value + translatedText.charAt(index.current));
index.current++;
}, 20);
return () => {
clearTimeout(timeOut);
}
} else {
setCurrentText(translatedText);
}
}, [translatedText, currentText, complete]);
return (
<p className="console-text">
{currentText}
</p>
);
};
You are telling react to do setCurrentText(translatedText) only when it is complete or when the compared text lengths are equal, so yes it continues to write until this moment.
To reset your text when text changes, try creating another useEffect that will reset your states :
useEffect(() => {
index.current = 0;
setCurrentText('');
}, [text]);
Now, I actually did this exact same feature few days ago, here is my component if it can help you :
import React from 'react';
import DOMPurify from 'dompurify';
import './text-writer.scss';
interface ITextWriterState {
writtenText: string,
index: number;
}
const TextWriter = ({ text, speed }: { text: string, speed: number }) => {
const initialState = { writtenText: '', index: 0 };
const sanitizer = DOMPurify.sanitize;
const [state, setState] = React.useState<ITextWriterState>(initialState);
React.useEffect(() => {
if (state.index < text.length - 1) {
const animKey = setInterval(() => {
setState(state => {
if (state.index > text.length - 1) {
clearInterval(animKey);
return { ...state };
}
return {
writtenText: state.writtenText + text[state.index],
index: state.index + 1
};
});
}, speed);
return () => clearInterval(animKey);
}
}, []);
// Reset the state when the text is changed (Language change)
React.useEffect(() => {
if (text.length > 0) {
setState(initialState);
}
}, [text])
return <div className="text-writer-component"><span className="text" dangerouslySetInnerHTML={{ __html: sanitizer(state.writtenText) }} /></div>
}
export default TextWriter;
The translation is made outside of the component so you can pass any kind of text to the component.

MediaSession Play/Pause button does not work in Chrome

I'm building a media controller application that controls a media player running in a remote process, and trying to use the MediaSession API to facilitate media key control. An audio element that is nearly silent is used to establish the media session, and after a few seconds it is paused indefinitely.
This works well in Firefox, but in Chrome-based browsers (desktop and mobile) the Play/Pause button does not change state and ultimately stops working after a few seconds. The Next/Previous track buttons work as expected.
What do I need to do to make the Play/Pause media session controls work in Chrome-based browsers?
React app to reproduce the issue:
import "./styles.css";
import React from "react";
export default function App() {
return (
<div className="App">
<h1>MediaSession demo</h1>
<Player />
</div>
);
}
function Player() {
const playctl = usePlayer();
if (playctl.state === "stopped") {
return <button onClick={playctl.playPause}>Begin</button>;
}
return (
<>
<p>Use media session notification to control player state.</p>
<MediaSession playctl={playctl} />
<p>Player state: {playctl.state}</p>
<p>Track: {playctl.track}</p>
</>
);
}
function usePlayer() {
const [state, setState] = React.useState("stopped");
const [track, setTrack] = React.useState(1);
let playing = state === "playing";
return {
playPause: () => {
playing = !playing;
setState(playing ? "playing" : "paused");
},
nextTrack: () => {
setTrack(track < 5 ? track + 1 : 1);
},
prevTrack: () => {
setTrack(track > 1 ? track - 1 : 5);
},
state,
nextState: playing ? "Pause" : "Play",
playing,
track
};
}
const MediaSession = ({ playctl }) => {
const controls = useMediaControls();
React.useEffect(() => controls.update(playctl), [controls, playctl]);
return controls.audio;
};
function useMediaControls() {
const audiofile = require("./near-silence.mp3");
const hasSession = window.navigator && "mediaSession" in window.navigator;
const ref = React.useRef();
let shouldShow = true;
function showControls(audio) {
shouldShow = false;
audio.volume = 0.00001; // very low volume level
audio.play();
audio.currentTime = 0;
// pause before track ends so controls remain visible
setTimeout(() => audio.pause(), 5000);
}
function updateSession(playctl) {
const session = window.navigator.mediaSession;
session.playbackState = playctl.playing ? "playing" : "paused";
session.setActionHandler("pause", playctl.playPause);
session.setActionHandler("play", playctl.playPause);
session.setActionHandler("nexttrack", playctl.nextTrack);
session.setActionHandler("previoustrack", playctl.prevTrack);
}
function createApi() {
return {
audio: hasSession && <audio ref={ref} src={audiofile} />,
update: (playctl) => {
if (hasSession) {
const audio = ref.current;
shouldShow && audio && showControls(audio);
updateSession(playctl);
}
}
};
}
return React.useState(createApi)[0];
}
Code sandbox: https://codesandbox.io/s/mediasession-demo-r773i

use Effect not being called as expected

I am trying to implement a simple file upload drop zone in React:
import { useState, useEffect } from 'react';
import './App.css';
const App = () => {
const [isDropzoneActive, setIsDropzoneActive] = useState(false);
const [files, setFiles] = useState([]);
const [currentChunkIndex, setCurrentChunkIndex] = useState(null);
const handleDragOver = e => {
e.preventDefault();
setIsDropzoneActive(true);
};
const handleDragLeave = e => {
e.preventDefault();
setIsDropzoneActive(false);
};
// Update the files array
const handleDrop = e => {
e.preventDefault();
setIsDropzoneActive(false);
// Just overwrite for this simple example
setFiles(e.dataTransfer.files);
};
// Monitor the files array
useEffect(() => {
if (files.length > 0) {
console.log('got a file');
setCurrentChunkIndex(0);
}
}, [files]);
// Monitor the chunk index
useEffect(() => {
if (currentChunkIndex !== null) {
readAndUploadCurrentChunk();
}
}, [currentChunkIndex]);
const readAndUploadCurrentChunk = () => {
// Implement later
};
return (
<div
onDragOver={handleDragOver}
onDragLeave={handleDragLeave}
onDrop={handleDrop}
className={"dropzone" + (isDropzoneActive ? " active" : "")}
>
{files.length > 0 ? 'Uploading' : 'Drop your files here'}
</div>
);
}
export default App;
However it seems that the effect that monitors [currentChunkIndex] is not being called correctly. I have attempted to drag files into the drop zone, one by one. [files] effect it called correctly each time but the effect on [currentChunkIndex] doesn't get called. What am I doing wrong here?
currentChunkIndex changes from null to 0, you set it only to 0.
useEffect(() => {
if (files.length > 0) {
console.log('got a file');
setCurrentChunkIndex(files.length);
}
}, [files]);

Using React hook form getValues() within useEffect return function, returns {}

I'm using react-hook-form library with a multi-step-form
I tried getValues() in useEffect to update a state while changing tab ( without submit ) and it returned {}
useEffect(() => {
return () => {
const values = getValues();
setCount(values.count);
};
}, []);
It worked in next js dev, but returns {} in production
codesandbox Link : https://codesandbox.io/s/quirky-colden-tc5ft?file=/src/App.js
Details:
The form requirement is to switch between tabs and change different parameters
and finally display results in a results tab. user can toggle between any tab and check back result tab anytime.
Implementation Example :
I used context provider and custom hook to wrap setting data state.
const SomeContext = createContext();
const useSome = () => {
return useContext(SomeContext);
};
const SomeProvider = ({ children }) => {
const [count, setCount] = useState(0);
const values = {
setCount,
count
};
return <SomeContext.Provider value={values}>{children}</SomeContext.Provider>;
};
Wrote form component like this ( each tab is a form ) and wrote the logic to update state upon componentWillUnmount.
as i found it working in next dev, i deployed it
const FormComponent = () => {
const { count, setCount } = useSome();
const { register, getValues } = useForm({
defaultValues: { count }
});
useEffect(() => {
return () => {
const values = getValues(); // returns {} in production
setCount(values.count);
};
}, []);
return (
<form>
<input type="number" name={count} ref={register} />
</form>
);
};
const DisplayComponent = () => {
const { count } = useSome();
return <div>{count}</div>;
};
Finally a tab switching component & tab switch logic within ( simplified below )
const App = () => {
const [edit, setEdit] = useState(true);
return (
<SomeProvider>
<div
onClick={() => {
setEdit(!edit);
}}
>
Click to {edit ? "Display" : "Edit"}
</div>
{edit ? <FormComponent /> : <DisplayComponent />}
</SomeProvider>
);
}

When to use hooks? is worth it that example?

I have write a hook to check if browser is IE, so that I can reutilize the logic instead of write it in each component..
const useIsIE = () => {
const [isIE, setIsIE] = useState(false);
useEffect(() => {
const ua = navigator.userAgent;
const isIe = ua.indexOf("MSIE ") > -1 || ua.indexOf("Trident/") > -1;
setIsIE(isIe);
}, []);
return isIE;
}
export default useIsIE;
Is it worth it to use that hook?
Im not sure if is good idea because that way, Im storing a state and a effect for each hook call (bad performane?) when I can simply use a function like that:
export default () => ua.indexOf("MSIE ") > -1 || ua.indexOf("Trident/") > -1;
What do you think? is worth it use that hook or not?
If not, when should I use hooks and when not?
ty
No. Not worth using the hook.
You'd need to use a hook when you need to tab into React's underlying state or lifecycle mechanisms.
Your browser will probably NEVER change during a session so just creating a simple utility function/module would suffice.
I would recommend to set your browser checks in constants and not functions, your browser will never change.
...
export const isChrome = /Chrome/.test(userAgent) && /Google Inc/.test(navigator.vendor);
export const isIOSChrome = /CriOS/.test(userAgent);
export const isMac = (navigator.platform.toUpperCase().indexOf('MAC') >= 0);
export const isIOS = /iphone|ipad|ipod/.test(userAgent.toLowerCase());
...
This is a simple hook that checks if a element has been scrolled a certain amount of pixels
const useTop = (scrollable) => {
const [show, set] = useState(false);
useEffect(() => {
const scroll = () => {
const { scrollTop } = scrollable;
set(scrollTop >= 50);
};
const throttledScroll = throttle(scroll, 200);
scrollable.addEventListener('scroll', throttledScroll, false);
return () => {
scrollable.removeEventListener('scroll', throttledScroll, false);
};
}, [show]);
return show;
};
Then you can use it in a 'To Top' button to make it visible
...
import { tween } from 'shifty';
import useTop from '../../hooks/useTop';
// scrollRef is your scrollable container ref (getElementById)
const Top = ({ scrollRef }) => {
const t = scrollRef ? useTop(scrollRef) : false;
return (
<div
className={`to-top ${t ? 'show' : ''}`}
onClick={() => {
const { scrollTop } = scrollRef;
tween({
from: { x: scrollTop },
to: { x: 0 },
duration: 800,
easing: 'easeInOutQuart',
step: (state) => {
scrollRef.scrollTop = state.x;
},
});
}}
role="button"
>
<span><ChevronUp size={18} /></span>
</div>
);
};

Resources