How to resolve this reference error : Audio is not defined - reactjs

Problem
I'm trying to play some audio files in some specific situations.
e.g)
When users access to login page, the audio plays 'Please enter your phone number'
when an error message alert comes up, audio file is played such as 'your phone number has been already registered'
So far, the audio files are played successfully when you access some pages, but I got the reference error in the image after I added two lines of code below in the root component (app.tsx)
import {kioskAudio} from '../src/common/utils/kioskAudio';
const {playAudio, stopAudio} = kioskAudio();
What I've tried to resolve this issue
First try:
I imported 'kioskAudio' method into KioskAlertError component directly. But I got the same reference error.
Second try:
So I thought, 'Then should I import the 'kioskAudio' method to the root component(app.tsx) and deliver the props(playAudio, stopAudio) to the component like this :
<KioskAlertError playAudio={playAudio} stopAudio={stopAudio} />
But I still got the reference error. How can I resolve this issue?
Source Code
app.tsx
import KioskAlert, {IKioskAlertProps} from './component/KioskAlert';
import KioskAlertError from './component/KioskAlertError';
import {kioskAudio} from '../src/common/utils/kioskAudio';
export default function CustomApp({Component, pageProps}) {
const router = useRouter();
const [shouldStartRender, setShouldStartRender] = useState(false);
const [kioskAlertInfo, setKioskAlertInfo] = useState({isShow: false, onOK: null} as IKioskAlertProps);
const {playAudio, stopAudio} = kioskAudio();
useEffect(() => {
setShouldStartRender(true);
}, [router]);
return (
<>
<KioskAlertContext.Provider
value={{
kioskAlertState: kioskAlertInfo,
openKioskAlert: openKioskAlert,
closeKioskAlert: closeKioskAlert,
}}
>
<SWRConfig
value={{
refreshInterval: 0,
revalidateOnReconnect: true,
revalidateOnFocus: true,
onErrorRetry: (error, key, config, revalidate, {retryCount}) => {
if (error.response?.status === 401) {
localStorage.removeItem('accessToken');
return;
}
if (retryCount >= 5) return;
setTimeout(() => {
revalidate({retryCount});
}, 5000);
},
}}
>
{shouldStartRender ? (
<DomRouter>
<DomRoutes>
<DomRoute path="/home/home.html" element={<Home />} />
<DomRoute path="/home/clause.html" element={<Clause />} />
<DomRoute path="/home/loginPhone.html" element={<LoginPhone />} />
<DomRoute path="/home/loginPin.html" element={<LoginPin />} />
<DomRoute path="/home/signUp-phone.html" element={<SignUpPhone />} />
<DomRoute path="/home/signUp-authCode.html" element={<SignUpAuthCode />} />
<DomRoute path="/home/signUp-pin.html" element={<SignUpPin />} />
<DomRoute path="/home/CheckUserByPin.html" element={<CheckUserByPin />} />
</DomRoutes>
</DomRouter>
) : null}
<KioskAlertError playAudio={playAudio} stopAudio={stopAudio} />
<KioskAlert {...kioskAlertInfo} />
</SWRConfig>
</KioskAlertContext.Provider>
</>
);
}
KioskAudio.ts
export const kioskAudio = () => {
const audio = new Audio();
const playAudio = (folder: string, file: string) => {
stopAudio();
audio.setAttribute('src', `/sounds/${folder}/${file}.mp3`);
audio.play();
};
const stopAudio = () => {
audio.pause();
audio.currentTime = 0;
};
return {
playAudio,
stopAudio,
};
};
KioskAlertError.tsx
const KioskAlertError: React.FC<IKioskAlertErrorProps> = ({playAudio, stopAudio}) => {
const [isShow, setIsShow] = useState(false);
const [content, setContent] = useState('');
useEffect(() => {
document.addEventListener('error', (data: CustomEvent) => {
const message = JSON.parse(data.detail);
const errorMessage = message.message;
setContent(getErrorMessage(message.message));
setIsShow(true);
switch (errorMessage) {
case 'Already Registered':
console.log('Already joined');
playAudio('alert', '2');
break;
case 'Can't find your numbers':
console.log('userNotFound');
playAudio('alert', '1');
break;
}
});
return () => {
document.removeEventListener('error', null);
};
}, []);
const getErrorMessage = (messageCode) => {
return messageCode;
};
return isShow ? (
<Alert
content={content}
okText={'OK'}
onOK={() => setIsShow(false)}
wrapperStyle={defaultWrapperStyle}
alertStyle={defaultAlertStyle}
upperSectionStyle={defaultUpperSectionStyle}
lowerSectionStyle={defaultLowerSectionStyle}
titleStyle={defaultTitleStyle}
contentStyle={defaultContentStyle}
cancelStyle={defaultButtonStyle}
okStyle={defaultButtonStyle}
/>
) : null;
};
export default KioskAlertError;

As you have used the audio variable inside your audio functions, the reference to the variable in function closures get lost between component re-renders. So you need to convert the kisokAudio util into a custom hook which holds the ref between renders & then use useKioskAudio instead of the simple function.
useKioskAudio.ts
import { useRef } from "react";
export const useKioskAudio = () => {
const audio = useRef(new Audio());
const playAudio = (folder: string, file: string) => {
stopAudio();
audio.current.setAttribute('src', `/sounds/${folder}/${file}.mp3`);
audio.current.play();
};
const stopAudio = () => {
audio.current.pause();
audio.current.currentTime = 0;
};
return {
playAudio,
stopAudio,
};
};
and then use it like
const { playAudio, stopAudio } = useKioskAudio();
in your app.tsx component.

Related

Lexical.js CLEAR_EDITOR_COMMAND won't dispatch

I've written simple plugin so i can push text from lexical to server:
const SubmitPlugin = ({ onSubmit }) => {
const [editor] = useLexicalComposerContext();
const onEnter = useCallback(
(event) => {
const { ctrlKey, metaKey } = event;
if (ctrlKey || metaKey) {
event.preventDefault();
onSubmit(
dompyrify.sanitize($generateHtmlFromNodes(editor), {
ALLOWED_ATTR: ['style'],
}),
);
editor.dispatchCommand(CLEAR_EDITOR_COMMAND, undefined);
}
return true;
},
[editor],
);
useEffect(() => {
return mergeRegister(
editor.registerCommand(KEY_ENTER_COMMAND, onEnter, COMMAND_PRIORITY_HIGH),
);
}, [editor, onEnter]);
return null;
};
When i press Ctrl or CMD + Enter it calls onSubmit function. Everything works fine except for editor.dispatchCommand(CLEAR_EDITOR_COMMAND, undefined); part. It doesn't do anything. All i need is to clear editor
It turns out that CLEAR_EDITOR_COMMAND is separated into plugin:
import { ClearEditorPlugin } from '#lexical/react/LexicalClearEditorPlugin';
and
return (
<LexicalComposer initialConfig={initialConfig}>
<RichTextPlugin
contentEditable={<ContentEditable />}
placeholder={<div className="placeholder">Reply...</div>}
/>
<ClearEditorPlugin />
<SubmitPlugin onSubmit={onSubmit} />
</LexicalComposer>
);
Now it works just fine

React Component is rerendering, even though it does not depend on the state

In my React code I have to use a legacy component, which makes a setup api call when it is first rendered. The component has a custom completion/cancelation event which I use to trigger a State update. The current Code looks like this:
export const useOneTimePassword = (
headline = "OTP anfordern",
id = "opt",
type = "sms",
businessProcess = "otp-process"
): UseOneTimePasswordReturn => {
const [otpCode, setOtpCode] = useState<undefined | string>();
const [isOtpCancelled, setIsOtpCancelled] = useState<boolean>(false);
const openOtp = () => {
const otp = document.querySelector(`otp-component#${id}`) as OtpElement;
otp.open();
};
const OtpComponent: FC = () => (
<Otp
headline={headline}
id={id}
type={type}
businessProcess={businessProcess}
setIsOtpCancelled={setIsOtpCancelled}
setOtpCode={setOtpCode}
/>
);
return {
otpCode,
isOtpCancelled,
openOtp,
OtpComponent,
removeOtp: () => {
setOtpCode(undefined);
},
};
};
and for the Component it looks like this:
const Otp: React.FC<OtpProps> = ({
headline,
businessProcess,
type,
id,
setOtpCode,
setIsOtpCancelled,
}) => {
function onOtpResponse(e: CompletedEvent) {
if (e.detail.otpCode) {
setOtpCode(e.detail.otpCode);
setIsOtpCancelled(false);
} else {
setIsOtpCancelled(true);
}
}
const ref = useRef();
useEffect(() => {
//#ts-ignore
if (ref.current) ref.current.addEventListener("completed", onOtpResponse);
}, []);
return (
<otp-component
ref={ref}
headline={headline}
id={id}
type={type}
business-process={businessProcess}
/>
);
};
export default Otp;
What I do not understand is that state changes in otpCode aswell as isOtpCancelled cause a rerender of the OtpComponent

Testing React component that using hook

enter image description here
Find the attached image
I am getting this following error, while running Unit test for my react file.
I couldn't able to set value for a state method.
TestFile.tsx
const [attr, setAttributes] = useState<any>(initialState);
const getDetail = async () => {
if (Id) {
const form: IForm = await getResponse(Id);
console.log("||" + form.attributes.sourceName)
setFormResponseAttributes(form.attributes);
console.log("***" + JSON.stringify(attr));
};
useEffect(() => {
getDetail();
}, []);
return(
{attr.sourceName === 'ECR' ?
<div className='fe_u_padding--right-medium'>
<Button
id='saveDraftButtonId'
text='Save as Draft'
onClick={() => saveForm(true)}
variant='secondary'
className='hide-while-printing'
/>
</div>
: null
);
}
export
TestFile.test.tsx
it('load form response', () => {
const getResponseSpy = jest.spyOn(ResponseApi, 'getResponse');
getResponseSpy.mockImplementation(() => Promise.resolve(testUtils.Response));
const setAppContextSpy = jest.fn();
let setResponseAttributes;
const wrapper = mount(
<BrowserRouter>
<RegisteredApplicationContext.Provider
value={{ appContext: testUtils.registeredApplication, setAppContext: setAppContextSpy }}
>
<FormResponse match={{ params: { formResponseId: 'formResponseId1' } }} />
</RegisteredApplicationContext.Provider>
</BrowserRouter>
);
wrapper.find(Button).find('#saveDraftButtonId').first().simulate('click', {});
wrapper.update();
expect(getFormResponseSpy).toBeCalled();
TestUtils
export const Response: IForm = {
id: uuidv4(),
attributes: {"contextId": "111","sourceName" : "ECR"}
}
I am getting error while reading the SaveDraftButtonId,
Method “simulate” is meant to be run on 1 node. 0 found instead.,
it could able to find that button coz it couldn't set the value for attr state value
Any inputs would be helpful
TIA

Warning: Using UNSAFE_componentWillMount in strict mode is not recommended (upgrade to CRA 4.0.2)

I updated my React application from 16.3+ to React 17 while upgrading to crate-react-app#4.0.2. Everything works as expected, but I see the following in the console:
Warning: Using UNSAFE_componentWillMount in strict mode is not recommended and may indicate bugs in your code. See react-unsafe-component-lifecycles for details.
* Move code with side effects to componentDidMount, and set initial state in the constructor.
Please update the following components: SideEffect(NullComponent)
My App.jsx file:
import React, { useRef, useEffect, useCallback, createRef } from 'react';
import { useDispatch, useSelector, batch } from 'react-redux';
import './App.scss';
import { CountryBox, Error, MasterBox, MetaTags, ModalContainer, ScreenLoader } from '../../components';
import { dataActions, settingsActions, statisticsActions, statisticsUpdatesActions } from '../../store/actions/actions';
import { engineService } from '../../services';
import { coreUtils } from '../../utils';
const App = (props) => {
const dispatch = useDispatch();
// Refs.
const elRefs = useRef([]);
// State variables.
const settingsList = useSelector((state) => state.settings.settingsList);
const loadingList = useSelector((state) => state.settings.loadingList);
const sourcesList = useSelector((state) => state.data.sourcesList);
const countriesList = useSelector((state) => state.data.countriesList);
const { isActive, isRefreshMode, viewType, isDisplayError, activeModalName,
activeModalValue, isReplaceModalMode, isActionLoader } = settingsList;
const { loadingPrecentage, isScreenLoaderComplete } = loadingList;
// Functions to update the state.
const onSetStateCurrentTime = (data) => dispatch(statisticsActions.setStateCurrentTime(data));
const onSetStateSettingsList = (listName, listValues) => dispatch(settingsActions.setStateSettingsList(listName, listValues));
const onSetStateStatisticsField = (fieldName, fieldValue) => dispatch(statisticsActions.setStateStatisticsField(fieldName, fieldValue));
const onSetStateStatisticsList = (statisticsList) => dispatch(statisticsActions.setStateStatisticsList(statisticsList));
const onSetStateStatisticsUpdatesSettingsList = (statisticsUpdatesSettingsList) => dispatch(statisticsUpdatesActions.setStateStatisticsUpdatesSettingsList(statisticsUpdatesSettingsList));
const onSetStateDataCollection = (collectionName, collectionValue) => dispatch(dataActions.setStateDataCollection(collectionName, collectionValue));
const onSetStateInitiateSettings = (data) => {
const { settingsList, loadingList } = data;
batch(() => {
dispatch(settingsActions.setStateSettingsList('settingsList', settingsList));
dispatch(settingsActions.setStateSettingsList('loadingList', loadingList));
});
};
const onSetStateInitiateSources = (data) => {
const { sourcesList, countriesList, countriesNameIdList, statisticsList, settingsList } = data;
batch(() => {
dispatch(dataActions.setStateDataCollection('sourcesList', sourcesList));
dispatch(dataActions.setStateDataCollection('countriesList', countriesList));
dispatch(dataActions.setStateDataCollection('countriesNameIdList', countriesNameIdList));
dispatch(settingsActions.setStateSettingsList('settingsList', settingsList));
dispatch(statisticsActions.setStateStatisticsList(statisticsList));
});
};
const onSetStateUpdateRound = (data) => {
const { countriesList, statisticsList, updateStatisticsUpdatesListResults } = data;
const { statisticsUpdatesList, statisticsUpdatesSettingsList } = updateStatisticsUpdatesListResults;
batch(() => {
dispatch(dataActions.setStateDataCollection('countriesList', countriesList));
dispatch(statisticsActions.setStateStatisticsList(statisticsList));
if (statisticsUpdatesList && statisticsUpdatesList.length > 0) {
dispatch(statisticsUpdatesActions.setStateStatisticsUpdatesList(statisticsUpdatesList));
dispatch(statisticsUpdatesActions.setStateStatisticsUpdatesSettingsList(statisticsUpdatesSettingsList));
}
});
};
const onSetStateActionUpdate = (data) => {
const { countriesList, settingsList } = data;
batch(() => {
dispatch(dataActions.setStateDataCollection('countriesList', countriesList));
dispatch(settingsActions.setStateSettingsList('settingsList', settingsList));
});
};
const onSetStateActionRefresh = (data) => {
const { countriesList, settingsList, statisticsList, updateStatisticsUpdatesListResults } = data;
const { statisticsUpdatesList, statisticsUpdatesSettingsList } = updateStatisticsUpdatesListResults;
batch(() => {
dispatch(dataActions.setStateDataCollection('countriesList', countriesList));
dispatch(settingsActions.setStateSettingsList('settingsList', settingsList));
dispatch(statisticsActions.setStateStatisticsList(statisticsList));
if (statisticsUpdatesList && statisticsUpdatesList.length > 0) {
dispatch(statisticsUpdatesActions.setStateStatisticsUpdatesList(statisticsUpdatesList));
dispatch(statisticsUpdatesActions.setStateStatisticsUpdatesSettingsList(statisticsUpdatesSettingsList));
}
});
};
const onSetStateUpdateCountryVisibility = (data) => {
const { countriesList, countriesNameIdList, statisticsList, statisticsUpdatesList } = data;
batch(() => {
dispatch(dataActions.setStateDataCollection('countriesList', countriesList));
dispatch(dataActions.setStateDataCollection('countriesNameIdList', countriesNameIdList));
dispatch(statisticsActions.setStateStatisticsList(statisticsList));
if (statisticsUpdatesList && statisticsUpdatesList.length > 0) {
dispatch(statisticsUpdatesActions.setStateStatisticsUpdatesList(statisticsUpdatesList));
}
});
};
// Run the engine.
useEffect(() => {
engineService.runEngine({
mode: props.mode,
onSetStateCurrentTime: onSetStateCurrentTime,
onSetStateSettingsList: onSetStateSettingsList,
onSetStateStatisticsField: onSetStateStatisticsField,
onSetStateStatisticsList: onSetStateStatisticsList,
onSetStateStatisticsUpdatesSettingsList: onSetStateStatisticsUpdatesSettingsList,
onSetStateInitiateSettings: onSetStateInitiateSettings,
onSetStateInitiateSources: onSetStateInitiateSources,
onSetStateUpdateRound: onSetStateUpdateRound,
onSetStateDataCollection: onSetStateDataCollection,
onSetStateActionUpdate: onSetStateActionUpdate,
onSetStateActionRefresh: onSetStateActionRefresh,
onSetStateUpdateCountryVisibility: onSetStateUpdateCountryVisibility
});
return () => {
engineService.clearSources();
};
}, []);
// Set loader for each master action.
useEffect(() => {
engineService.updateActionLoader(false);
}, [countriesList]);
// After exit from any modal - Scroll back to the element's vertical position.
const scrollToCountry = useCallback((data) => {
const { action, value } = data;
if (action === 'modal' && !value && activeModalValue && !isReplaceModalMode && activeModalName !== 'country') {
setTimeout(() => {
const offsetTop = elRefs.current.find(c => c.current?.dataset?.countryId === activeModalValue).current.offsetTop;
if (offsetTop > window.innerHeight) {
window.scrollTo(0, offsetTop);
}
}, 10);
}
}, [elRefs, activeModalValue, isReplaceModalMode]);
// Update action on master modal click.
const handleActionClick = useCallback((e) => {
if (!isActionLoader) {
const data = {
action: coreUtils.getAttributeName(e, 'data-action'),
value: coreUtils.getAttributeName(e, 'name'),
id: coreUtils.getAttributeName(e, 'data-country-id')
};
scrollToCountry(data);
engineService.runMasterActionClick(data);
}
}, [elRefs, activeModalValue, isReplaceModalMode]);
// Update action on relevant modal change.
const handleModalActionChange = useCallback((e) => {
engineService.runModalActionUpdate({
modalName: coreUtils.getAttributeName(e, 'data-modal-name'),
action: coreUtils.getAttributeName(e, 'data-action'),
value: coreUtils.getValue(e)
});
}, []);
// Validate all OK to show the data and generate the countries.
const isInitiateComplete = !isDisplayError && countriesList && countriesList.length > 0 && loadingPrecentage === 100;
const renderCountries = useCallback(() => {
const countriesDOM = [];
const refsList = [];
for (let i = 0; i < countriesList.length; i++) {
const country = countriesList[i];
const ref = elRefs.current[i] || createRef();
refsList.push(ref);
countriesDOM.push(
(<CountryBox
key={country.id}
{...country} // React memo works only with separated properties.
isRefreshMode={isRefreshMode}
sourcesList={sourcesList}
onActionClick={handleActionClick}
ref={ref}
/>));
}
elRefs.current = refsList;
return countriesDOM;
}, [countriesList]);
return (
<div className="main">
{MetaTags}
{!isScreenLoaderComplete &&
<ScreenLoader
isActive={isActive}
loadingList={loadingList}
isDisplayError={isDisplayError}
/>
}
{isDisplayError &&
<Error
isDisplayError={isDisplayError}
/>
}
{activeModalName &&
<ModalContainer
onActionClick={handleActionClick}
onActionChange={handleModalActionChange}
/>
}
{isInitiateComplete &&
<div className="page">
<div className="main-container">
<div className={`container ${viewType} f32 f32-extra locations`}>
<MasterBox
onActionClick={handleActionClick}
/>
{renderCountries()}
</div>
</div>
</div>
}
</div>
);
};
export default App;
How can I fix this problem?
OK, I solved it.
The issue was with one of the components named MetaTags:
MetaTags.jsx
import React from 'react';
import { Helmet } from 'react-helmet';
import { timeUtils } from '../../../utils';
const MetaTags =
(<Helmet>
<title data-rh="true">World Covid 19 Data | Covid 19 World Data | {timeUtils.getTitleDate()}</title>
</Helmet>);
export default MetaTags;
The react-helmet package is outdated, and I needed to install 'react-helmet-async' instead, and change the code to:
initiate.jsx
app = (
<HelmetProvider>
<Suspense fallback={null}>
<Provider store={createStore(rootReducer, composeEnhancers(applyMiddleware(thunk)))}>
<Helmet>
<title data-rh="true">Dynamic title {timeUtils.getTitleDate()}</title>
</Helmet>
<BrowserRouter>
{component}
</BrowserRouter>
</Provider>
</Suspense>
</HelmetProvider>
);
This solved my issue and the warning was gone.

How can I update component whenever typing in input tag in React?

What the below code does is to get data from API, and then render it on the page. searchChange function takes a value from the input tag, and setValue for query state. My api endpoint takes argument to filter the API such as http://127.0.0.1:8000/api/deals/?q=${query}.
I'm very confused how I can update the DealList component with the API updated with query state whenever typing something in the input tag. I'm thinking of that I need to something in searchChange function, but not sure what to do there.
index.js
const useFetch = (url, query, defaultResponse) => {
const [result, setResult] = useState(defaultResponse);
const getDataFromAPI = async url => {
try {
const data = await axios.get(url);
setResult({
isLoading: false,
data
});
} catch (e) {}
};
useEffect(() => {
if (query.length > 0) {
getDataFromAPI(`${url}?q=${query}`);
} else {
getDataFromAPI(url);
}
}, []);
return result;
};
const Index = ({ data }) => {
const query = useInput("");
const apiEndpoint = "http://127.0.0.1:8000/api/deals/";
const dealFetchResponse = useFetch(apiEndpoint, query, {
isLoading: true,
data: null
});
const searchChange = e => {
query.onChange(e);
query.setValue(e.target.value);
};
return (
<Layout>
<Head title="Home" />
<Navigation />
<Container>
<Headline>
<h1>The best lease deal finder</h1>
<h4>See all the lease deals here</h4>
</Headline>
<InputContainer>
<input value={query.value} onChange={searchChange} />
</InputContainer>
{!dealFetchResponse.data || dealFetchResponse.isLoading ? (
<Spinner />
) : (
<DealList dealList={dealFetchResponse.data.data.results} />
)}
</Container>
</Layout>
);
};
export default Index;
The biggest challenge in something like this is detecting when a user has stopped typing.. If someone is searching for 'Milk' - when do you actually fire off the API request? How do you know they aren't searching for 'Milk Duds'? (This is hypothetical, and to demonstrate the 'hard' part in search bars/APIs due to their async nature)..
This is typically solved by debouncing, which has been proven to work, but is not very solid.
In this example, you can search Github repos...but even in this example, there are unnecessary requests being sent - this is simply to be used as a demonstration. This example will need some fine tuning..
const GithubSearcher = () => {
const [repos, setRepos] = React.useState();
const getGithubRepo = q => {
fetch("https://api.github.com/search/repositories?q=" + q)
.then(res => {
return res.json();
})
.then(json => {
let formattedJson = json.items.map(itm => {
return itm.name;
})
setRepos(formattedJson);
});
}
const handleOnChange = event => {
let qry = event.target.value;
if(qry) {
setTimeout(() => {
getGithubRepo(qry);
}, 500);
} else {
setRepos("");
}
};
return (
<div>
<p>Search Github</p>
<input onChange={event => handleOnChange(event)} type="text" />
<pre>
{repos ? "Repo Names:" + JSON.stringify(repos, null, 2) : ""}
</pre>
</div>
);
};
ReactDOM.render(<GithubSearcher />, document.body);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.6/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.6/umd/react-dom.production.min.js"></script>

Resources