React count down timers with setInterval - javascript

First let me show some relevant code before I explain my problem
CountDownTimer component
import { FC, useState, useEffect, useRef } from "react";
interface CountDownTimerProps {
defaultMinutes: number;
defaultSeconds: number;
ticking: boolean;
}
const CountDownTimer: FC<CountDownTimerProps> = (props) => {
const { defaultMinutes, defaultSeconds, ticking } = props;
const [[mins, secs], setTime] = useState([defaultMinutes, defaultSeconds]);
const timerId = useRef<NodeJS.Timeout | null>(null);
const stopTimerInterval = () => timerId.current !== null && clearInterval(timerId.current);
const tick = () => {
if (mins === 0 && secs === 0) {
return;
} else if (secs === 0) {
setTime([mins - 1, 59]);
} else {
setTime([mins, secs - 1]);
}
};
const reset = () => {
stopTimerInterval();
setTime([defaultMinutes, defaultSeconds]);
};
useEffect(() => {
if (!ticking) {
reset();
return;
} else {
timerId.current = setInterval(() => tick(), 1000);
console.log("creating interval");
}
return () => {
stopTimerInterval();
};
});
return (
<p className="text-white text-5xl">{`${mins.toString().padStart(2, "0")}:${secs
.toString()
.padStart(2, "0")}`}</p>
);
};
export default CountDownTimer;
PomodoroClock component
import { FC } from "react";
import { buildStyles, CircularProgressbarWithChildren } from "react-circular-progressbar";
import CountDownTimer from "./CountDownTimer";
import MinusIcon from "./MinusIcon";
import PlusIcon from "./PlusIcon";
interface PomodoroClockProps {
ticking: boolean;
mins: number;
onIncrementClock(): any;
onDecrementClock(): any;
}
const PomodoroClock: FC<PomodoroClockProps> = (props) => {
const { mins, ticking, onDecrementClock, onIncrementClock } = props;
return (
<div className="w-96 h-96 bg-red-400 rounded-full">
<CircularProgressbarWithChildren
value={47}
strokeWidth={4}
styles={buildStyles({
trailColor: "#fff",
pathColor: "#EF4444",
})}
>
<div className="flex items-center gap-4">
<button onClick={onDecrementClock}>
<MinusIcon />
</button>
<CountDownTimer defaultMinutes={mins} defaultSeconds={0} ticking={ticking} />
<button onClick={onIncrementClock}>
<PlusIcon />
</button>
</div>
</CircularProgressbarWithChildren>
</div>
);
};
export default PomodoroClock;
App component
import { FC, useState } from "react";
import PomodoroClock from "./PomodoroClock";
const App: FC = () => {
const [ticking, setTicking] = useState<boolean>(false);
const [mins, setMins] = useState<number>(5);
const toggleTicking = () => setTicking(!ticking);
const toggleButtonText = ticking ? "STOP" : "START";
type updateClockKind = "inc" | "dec";
const updateClockTime = (kind: updateClockKind, by: number) => {
if (kind === "inc" && mins + by < 20) {
setMins(mins + by);
} else if (kind === "dec" && mins - by > 0) {
setMins(mins - by);
} else {
return;
}
};
return (
<div className="flex content-center flex-col items-center gap-8">
<PomodoroClock
mins={mins}
ticking={ticking}
onIncrementClock={() => updateClockTime("inc", 5)}
onDecrementClock={() => updateClockTime("dec", 5)}
/>
<button
className="bg-red-500 py-4 px-6 rounded-lg text-white text-2xl"
onClick={toggleTicking}
>
{toggleButtonText}
</button>
</div>
);
};
export default App;
SO currently, when I press the start button on the clock everything seems to be working correct it counts down and when I press stop it resets. BUT the problem im having is that on every tick of the clock its creating a new interval and when I stop the clock its erroring in the console with thousands of Warning: Maximum update depth exceeded
Any help is appreciated, it is probably just something small but I have been stuck on this for some time. thanks!

Related

What's the simplest way to execute plain Javascript in a React .js file?

What's the simplest way to execute plain Javascript in a React 16 .js file?
Below is a render block from a React file (call it myfile.js; entire file is below).
How can I get, i.e.
console.log;
or i.e.,
var timestamp = Math.round((new Date()).getTime() / 1000);
(or other plain Javascript) to execute?
Do I need to use const?
Is it better to use the bulk of Javascript outside of the render block?
render () {
const { columns, children, singleColumn, isModalOpen } = this.props;
const { renderComposePanel } = this.state;
if (singleColumn) {
return (
<div className='columns-area__panels'>
<div className='columns-area__panels__pane columns-area__panels__pane--compositional'>
<div className='columns-area__panels__pane__inner'>
{renderComposePanel && <ComposePanel />}
</div>
</div>
<div className='columns-area__panels__main'>
<div className='tabs-bar__wrapper'><div id='tabs-bar__portal' />
console.log('Hello World''); // execute plain JS here
var timestamp = Math.round((new Date()).getTime() / 1000);
</div>
<div className='columns-area columns-area--mobile'>{children}</div>
</div>
<div className='columns-area__panels__pane columns-area__panels__pane--start columns-area__panels__pane--navigational'>
<div className='columns-area__panels__pane__inner'>
<NavigationPanel />
</div>
</div>
</div>
);
}
return (
<div className={`columns-area ${ isModalOpen ? 'unscrollable' : '' }`} ref={this.setRef}>
{columns.map(column => {
const params = column.get('params', null) === null ? null : column.get('params').toJS();
const other = params && params.other ? params.other : {};
return (
<BundleContainer key={column.get('uuid')} fetchComponent={componentMap[column.get('id')]} loading={this.renderLoading(column.get('id'))} error={this.renderError}>
{SpecificComponent => <SpecificComponent columnId={column.get('uuid')} params={params} multiColumn {...other} />}
</BundleContainer>
);
})}
{React.Children.map(children, child => React.cloneElement(child, { multiColumn: true }))}
</div>
);
}
}
Entire myfile.js :
import React from 'react';
import PropTypes from 'prop-types';
import ImmutablePropTypes from 'react-immutable-proptypes';
import ImmutablePureComponent from 'react-immutable-pure-component';
import BundleContainer from '../containers/bundle_container';
import ColumnLoading from './column_loading';
import DrawerLoading from './drawer_loading';
import BundleColumnError from './bundle_column_error';
import {
Compose,
Notifications,
HomeTimeline,
CommunityTimeline,
PublicTimeline,
HashtagTimeline,
DirectTimeline,
FavouritedStatuses,
BookmarkedStatuses,
ListTimeline,
Directory,
} from '../../ui/util/async-components';
import ComposePanel from './compose_panel';
import NavigationPanel from './navigation_panel';
import { supportsPassiveEvents } from 'detect-passive-events';
import { scrollRight } from '../../../scroll';
const componentMap = {
'COMPOSE': Compose,
'HOME': HomeTimeline,
'NOTIFICATIONS': Notifications,
'PUBLIC': PublicTimeline,
'REMOTE': PublicTimeline,
'COMMUNITY': CommunityTimeline,
'HASHTAG': HashtagTimeline,
'DIRECT': DirectTimeline,
'FAVOURITES': FavouritedStatuses,
'BOOKMARKS': BookmarkedStatuses,
'LIST': ListTimeline,
'DIRECTORY': Directory,
};
export default class ColumnsArea extends ImmutablePureComponent {
static contextTypes = {
router: PropTypes.object.isRequired,
};
static propTypes = {
columns: ImmutablePropTypes.list.isRequired,
isModalOpen: PropTypes.bool.isRequired,
singleColumn: PropTypes.bool,
children: PropTypes.node,
};
// Corresponds to (max-width: $no-gap-breakpoint + 285px - 1px) in SCSS
mediaQuery = 'matchMedia' in window && window.matchMedia('(max-width: 1174px)');
state = {
renderComposePanel: !(this.mediaQuery && this.mediaQuery.matches),
}
componentDidMount() {
if (!this.props.singleColumn) {
this.node.addEventListener('wheel', this.handleWheel, supportsPassiveEvents ? { passive: true } : false);
}
if (this.mediaQuery) {
if (this.mediaQuery.addEventListener) {
this.mediaQuery.addEventListener('change', this.handleLayoutChange);
} else {
this.mediaQuery.addListener(this.handleLayoutChange);
}
this.setState({ renderComposePanel: !this.mediaQuery.matches });
}
this.isRtlLayout = document.getElementsByTagName('body')[0].classList.contains('rtl');
}
componentWillUpdate(nextProps) {
if (this.props.singleColumn !== nextProps.singleColumn && nextProps.singleColumn) {
this.node.removeEventListener('wheel', this.handleWheel);
}
}
componentDidUpdate(prevProps) {
if (this.props.singleColumn !== prevProps.singleColumn && !this.props.singleColumn) {
this.node.addEventListener('wheel', this.handleWheel, supportsPassiveEvents ? { passive: true } : false);
}
}
componentWillUnmount () {
if (!this.props.singleColumn) {
this.node.removeEventListener('wheel', this.handleWheel);
}
if (this.mediaQuery) {
if (this.mediaQuery.removeEventListener) {
this.mediaQuery.removeEventListener('change', this.handleLayoutChange);
} else {
this.mediaQuery.removeListener(this.handleLayouteChange);
}
}
}
handleChildrenContentChange() {
if (!this.props.singleColumn) {
const modifier = this.isRtlLayout ? -1 : 1;
this._interruptScrollAnimation = scrollRight(this.node, (this.node.scrollWidth - window.innerWidth) * modifier);
}
}
handleLayoutChange = (e) => {
this.setState({ renderComposePanel: !e.matches });
}
handleWheel = () => {
if (typeof this._interruptScrollAnimation !== 'function') {
return;
}
this._interruptScrollAnimation();
}
setRef = (node) => {
this.node = node;
}
renderLoading = columnId => () => {
return columnId === 'COMPOSE' ? <DrawerLoading /> : <ColumnLoading multiColumn />;
}
renderError = (props) => {
return <BundleColumnError multiColumn errorType='network' {...props} />;
}
render () {
const { columns, children, singleColumn, isModalOpen } = this.props;
const { renderComposePanel } = this.state;
if (singleColumn) {
return (
<div className='columns-area__panels'>
<div className='columns-area__panels__pane columns-area__panels__pane--compositional'>
<div className='columns-area__panels__pane__inner'>
{renderComposePanel && <ComposePanel />}
</div>
</div>
<div className='columns-area__panels__main'>
<div className='tabs-bar__wrapper'><div id='tabs-bar__portal' /></div>
<div className='columns-area columns-area--mobile'>{children}</div>
</div>
<div className='columns-area__panels__pane columns-area__panels__pane--start columns-area__panels__pane--navigational'>
<div className='columns-area__panels__pane__inner'>
<NavigationPanel />
</div>
</div>
</div>
);
}
return (
<div className={`columns-area ${ isModalOpen ? 'unscrollable' : '' }`} ref={this.setRef}>
{columns.map(column => {
const params = column.get('params', null) === null ? null : column.get('params').toJS();
const other = params && params.other ? params.other : {};
return (
<BundleContainer key={column.get('uuid')} fetchComponent={componentMap[column.get('id')]} loading={this.renderLoading(column.get('id'))} error={this.renderError}>
{SpecificComponent => <SpecificComponent columnId={column.get('uuid')} params={params} multiColumn {...other} />}
</BundleContainer>
);
})}
{React.Children.map(children, child => React.cloneElement(child, { multiColumn: true }))}
</div>
);
}
}
This is what works in a simple React App. I moved the plain Javascript out of the render and return blocks to the top of the file (under the import calls), and then used 1) either const for the variable timestampone. Or, 2) for a Javascript variable with HTML, dangerouslySetInnerHTML, which is OK for me, as no one will have access to insert HTML other than me.
App.js:
import * as React from 'react';
const timestampone = <span> {Math.round((new Date()).getTime() / 1000)} </span>;
var timestamptwo = Math.round((new Date()).getTime() / 1000);
if (timestamptwo >=1675893600 && timestamptwo <= 1675897200 ) {
var timedate = '<div class="wrapper">Between Timestamps</div>';
}
function App() {
return (
<div className="App">
timestampone: {timestampone}
<div dangerouslySetInnerHTML={{__html: timedate}} />
</div>
);
}
export default App;
In your situation, there's two simple ways to do it:
By creating your function immediately inside your render block, then executing the function within the HTML using {} to wrap variable:
render () {
const runJavascript = () => {
console.log('Hello World''); // execute plain JS here
var timestamp = Math.round((new Date()).getTime() / 1000);
};
if (singleColumn) {
return (
<div>{runJavascript()}</div>
);
}
return <div>hello world</div>;
}
Additionally, you can create your runJavascript function outside of your render block along-side any constructor, componentDidMount or componentDidUnmount blocks you may have:
runJavascript = () => {
console.log('Hello World''); // execute plain JS here
var timestamp = Math.round((new Date()).getTime() / 1000);
};
render () {
if (singleColumn) {
return (
<div>{this.runJavascript()}</div>
);
}
return <div>hello world</div>;
}

A component for drag and drop files in React returns error about maximum update depth exceeded

There is a component for uploading files with drag and drop, here is its code:
import clsx from 'clsx';
import React, { DragEvent, useEffect, useState } from 'react';
import { Link } from 'react-router-dom';
import { Delete, DocumentDownloadIcon } from '#oam/shared/assets';
import { Button, ButtonType } from '../button/button';
export interface UploadFieldProps extends React.InputHTMLAttributes<HTMLInputElement> {
label?: string;
mainErrorMessage?: string;
subErrorMessage?: string;
onFilesChange?: (files: FileList | File[] | null) => void;
multiple?: boolean;
inputId?: string;
defaultFiles?: DefaultFiles[];
route?: string;
showDelete?: boolean;
maxSize?: number;
deleteHandler?: (id: string, files?: File[]) => void;
showDownloadIcon?: boolean;
}
export interface DefaultFiles {
fileId: string;
name: string;
}
const getBorderClasses = (disabled: boolean): string =>
clsx({
'absolute right-0 float-right pr-2 -z-10 -top-5': true,
'cursor-pointer': !disabled
});
const getInputClasses = (className: string, disabled: boolean): string =>
clsx({
'block w-full pt-4 pb-5 text-sm p-2 mt-0 bg-transparent border-dashed border border-oam-gray-500 appearance-none':
true,
[className]: !!className,
'cursor-pointer hover:border-oam-blue-light': !disabled
});
const MAX_SIZE = 5242880;
const WORD_FORMAT =
'application/msword,application/vnd.openxmlformats-officedocument.wordprocessingml.document';
const EXCEL_FORMAT = 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet';
interface IFile extends File {
fileId?: string;
}
export function UploadField({
className = '',
inputId = 'upload-input',
disabled = false,
label = 'Drag files here or ...',
mainErrorMessage,
subErrorMessage,
multiple = false,
onFilesChange,
defaultFiles = [],
route,
showDelete = true,
maxSize = MAX_SIZE,
deleteHandler,
showDownloadIcon,
...props
}: UploadFieldProps) {
const [errorMessage, setErrorMessage] = useState('');
const [files, setFiles] = useState<FileList | File[] | null | Array<File>>(
defaultFiles as unknown as File[]
);
const deleteUploadedFile = (file: IFile) => {
if (files && !disabled) {
const arrayFiles = Array.from(files).filter((f) => f.name !== file.name);
if (file.fileId) {
deleteHandler?.(file.fileId, arrayFiles);
}
setFiles(arrayFiles);
}
};
useEffect(() => {
setFiles(defaultFiles as unknown as File[]);
}, [defaultFiles]);
const createDataTransferFiles = (filesChanged: FileList) => {
const dataTransfer = new DataTransfer();
setErrorMessage('');
Array.from(filesChanged).forEach((file) => {
if (file.size <= maxSize) {
if (dataTransfer.files.length !== 0) {
for (const fileData of Array.from(dataTransfer.files)) {
if (fileData.name !== file.name) {
dataTransfer.items.add(file);
break;
}
}
} else {
dataTransfer.items.add(file);
}
} else {
setErrorMessage(`${file.name} exceeds size limit. 5mb max file size supported.`);
}
});
setFiles([...Array.from(dataTransfer.files), ...(files as [])]);
return dataTransfer.files;
};
const stopDragAndDropDefaultEvent = (e: DragEvent<HTMLDivElement>) => {
e.preventDefault();
e.stopPropagation();
};
const onDragAndDrop = (e: DragEvent<HTMLDivElement>) => {
if (disabled) {
return;
}
stopDragAndDropDefaultEvent(e);
const files = e.dataTransfer.files;
if (files && files.length) {
const fileList = createDataTransferFiles(e.dataTransfer.files);
onFilesChange && onFilesChange(fileList);
}
};
const onChange = (event: React.ChangeEvent<HTMLInputElement>) => {
if (event.target.files) {
const fileList = createDataTransferFiles(event.target.files);
onFilesChange && onFilesChange(fileList);
}
};
return (
<div
className='relative z-0 w-full'
onDragOver={stopDragAndDropDefaultEvent}
onDragEnter={stopDragAndDropDefaultEvent}
onDrop={onDragAndDrop}
>
<input
id={inputId}
accept={`image/*,.pdf,${WORD_FORMAT},${EXCEL_FORMAT},application/JSON`}
type='file'
data-testid='upload-input'
className='hidden'
multiple={multiple}
disabled={disabled}
onChange={onChange}
{...props}
/>
{label &&
<label htmlFor='upload-input' className={getInputClasses(className, disabled)}>
{label}
</label>
}
<div className={getBorderClasses(disabled)}>
<div className='flex items-center'>
<Button
disabled={disabled}
data-testid='add-file'
title='Upload file'
buttonType={ButtonType.SECONDARY}
/>
</div>
</div>
{files &&
<div className='pt-2'>
{Array.from([...(files as unknown as File[])]).map((file, index) => {
return (
<div className='flex items-center content-center p-2 mb-3' key={index}>
{file?.name &&
<>
{defaultFiles[index]?.fileId ?
<Link to={`${route + defaultFiles[index].fileId}`} className='flex font-bold'>
{file?.name}
{showDownloadIcon &&
<div className='pl-8 pr-2'>
<DocumentDownloadIcon data-testid='download-file' />
</div>
}
</Link>
:
<span className='pr-8 font-bold'>{file?.name}</span>
}
{showDelete && !disabled &&
<Delete
data-testid='delete-file'
onClick={() => deleteUploadedFile(file)}
className='justify-end w-4 h-4 cursor-pointer text-oam-gray-600'
/>
}
</>
}
</div>
);
})}
</div>
}
{(!!mainErrorMessage || !!errorMessage) &&
<div className='flex flex-row ml-2 text-sm text-status-red' id='error'>
<span className='pr-2 font-bold'>{mainErrorMessage || errorMessage}</span>
<span>{subErrorMessage}</span>
</div>
}
</div>
);
}
export default UploadField;
And on a page of the application I'm using the above component, inside a Controller of react-hook-form, there is a React-Query hook for uploading the file which works fine, the only issue is that in console there is this error message that runs forever, like 100 times a second, the counter keeps growing very fast.
Here is the page where I'm using the above component:
import { yupResolver } from '#hookform/resolvers/yup';
import { useCallback, useEffect, useState } from 'react';
import { Controller, useForm } from 'react-hook-form';
import { object, mixed } from 'yup';
import { useToggle } from '#oam/shared/hooks';
import { UploadField } from '#oam/shared/ui-components';
import { ILicense } from '../../constants/types';
import { useUploadLicense } from '../../hooks';
export const DEFAULT_FILE = object({
attachments: mixed().nullable().notRequired()
});
export function License() {
const { uploadFiles } = useUploadLicense();
const defaultValues = DEFAULT_FILE.cast({
attachments: []
});
const form = useForm({
resolver: yupResolver(DEFAULT_FILE),
defaultValues
});
const [uploadedFile, setUploadedFile] = useState<FileList | File[] | null>(null);
const [parsedFile, setParsedFile] = useState<ILicense>();
const [validLicense, toggleValidLicense] = useToggle(false);
const [invalidLicense, toggleInvalidLicense] = useToggle(false);
const [invalidFormat, toggleInvalidFormat] = useToggle(false);
const parseFile = useCallback(async () => {
if (invalidFormat) {
toggleInvalidFormat();
}
if (uploadedFile?.length) {
if (uploadedFile[0].type !== 'application/json') {
toggleInvalidFormat();
} else {
const uploadedFileText = await uploadedFile[0].text().then((text) => {
return text;
});
const parsed = JSON.parse(uploadedFileText);
setParsedFile(parsed);
form.handleSubmit(async (data) => {
const files = data.attachments;
const filesResponse = await uploadFiles(
{ files },
{
onSuccess: () => toggleValidLicense(),
onError: () => toggleInvalidLicense()
}
).catch((e) => form.setError('attachments', { message: e }));
form.setValue('attachments', filesResponse?.data);
})();
}
}
}, [
form,
invalidFormat,
toggleInvalidFormat,
toggleInvalidLicense,
toggleValidLicense,
uploadFiles,
uploadedFile
]);
useEffect(() => {
if (uploadedFile?.length) {
parseFile();
}
}, [parseFile, uploadedFile]);
return (
<div>
<Controller
control={form.control}
name='attachments'
render={() =>
<UploadField
onFilesChange={(files) => {
form.setValue('attachments', files);
setUploadedFile(files);
if (validLicense && files?.length) {
toggleValidLicense();
}
if (invalidLicense && files?.length) {
toggleInvalidLicense();
}
if (invalidFormat && files?.length) {
toggleInvalidFormat();
}
}}
/>
}
/>
</div>
);
}
export default License;
And the React-Query hook but I don't think it has a connection to the bug:
import { useMutation, useQueryClient } from 'react-query';
import { IUploadFieldRequest } from '../constants/types';
import { licenseServiceAPI } from '../services';
export const URI = () => '/licenses/upload';
export const useUploadLicense = () => {
const queryClient = useQueryClient();
const [apiService] = licenseServiceAPI<FormData>({
headers: {
'Content-Type': 'multipart/form-data'
}
});
const {
data: response,
mutateAsync,
isLoading,
isSuccess
} = useMutation(
(formData: IUploadFieldRequest) => {
const form = new FormData();
Array.from(formData.files)?.forEach((file) => {
form.append('license-file', file);
});
return apiService.post(URI(), form);
},
{
onError: () => {
return queryClient.invalidateQueries('licenseUpload');
}
}
);
return {
data: response?.data,
uploadFiles: mutateAsync,
isLoading,
isSuccess
};
};
export default useUploadLicense;
The erros that keeps appearing:
Warning: Maximum update depth exceeded. This can happen when a component calls setState inside useEffect, but useEffect either doesn't have a dependency array, or one of the dependencies changes on every render.
Any ideas how to fix this?

Adjust user to point in slider

I try to build sliders with different categories that each user has his point.
The informant comes from the json server
What I need I do not succeed in having the customer choose a user that is numbered and the dot will be colored in the slider How do I do that?
In addition he has the option to delete and return the point.
I was able to delete the points by deleting them in the object. But I could not return, is there a possibility to return?
Broker.jsx
import React, { useEffect, useState } from 'react';
import './style.css';
import Combo from '../components/Combo/Combo';
import Sliders from '../components/Sliders/Sliders';
const GetUsersDataFromManipulation = (users, field) => {
const state = users.reduce((store, user) => {
const userId = user.user
const currentManipulationUserData = user.profileManipulation[field]
if (currentManipulationUserData.length === 0) {
return store
}
store[userId] = currentManipulationUserData[0].bid
return store;
}, {})
return state;
};
function Broker({ manipulations }) {
const users = manipulations[2].users
const [hiddenUser, setHiddenUser] = useState(() => {
const visible = {};
for (let user of users) {
visible[user.user] = true;
}
return visible;
})
const GetUsersBid = (profileManipulation) => {
const data = GetUsersDataFromManipulation(users, `${profileManipulation}`); if (!Object.keys(data).length) {
return null
}
return data;
};
const gender = GetUsersBid('gender');
const age = GetUsersBid('age');
const marital = GetUsersBid('marital');
const children = GetUsersBid('children');
const education = GetUsersBid('education');
const interests = GetUsersBid('interests');
const dynamicInterests = GetUsersBid('dynamicInterests');
const showUser = (user_id) => {
const new_hidden = { ...hiddenUser }
new_hidden[user_id] = true;
setHiddenUser(new_hidden);
}
const hideUser = (user_id) => {
const new_hidden = { ...hiddenUser }
console.log(user_id)
new_hidden[user_id] = false;
setHiddenUser(new_hidden);
}
const [userInformation, setUserInformation] = useState([
{ name: 'gender', bids: gender },
{ name: 'age', bids: age },
{ name: 'marital', bids: marital },
{ name: 'children', bids: children },
{ name: 'education', bids: education },
{ name: 'interests', bids: interests },
{ name: 'dynamicInterests ', bids: dynamicInterests },
]);
useEffect(() => {
const curret_User_Info = [...userInformation]
for (let user of Object.keys(hiddenUser)) {
for (let i = 0; i < curret_User_Info.length; i++) {
if (curret_User_Info[i].bids !== null) {
if (hiddenUser[user] === false) {
delete curret_User_Info[i].bids[user]
}
else {
//What am I returning here? So that the bids will return?
}
}
}
}
setUserInformation(curret_User_Info)
}, [hiddenUser])
return (
<div>
<div className="button" >
{userInformation && <Combo users={users} showUser={showUser} hideUser={hideUser} userInformation={userInformation} />}
</div>
<div className='slid'>
{userInformation.map(sliderDetails => {
return (
<div className={sliderDetails.name} key={sliderDetails.name} >
{sliderDetails.bids && (<Sliders className="sliders" hiddenUserChange={hiddenUser} name={sliderDetails.name} userBids={sliderDetails.bids} setUserInformation={setUserInformation} userInformation={userInformation} />)}
</div>
)
})}
</div>
</div>
);
}
export default Broker;
ComboBox.jsx
import React, { useEffect, useRef, useState } from 'react';
import ComboBox from 'react-responsive-combo-box';
import { Button } from '#mui/material';
import 'react-responsive-combo-box/dist/index.css';
import "./style.css"
function Combo({ users, showUser, hideUser, userInformation }) {
const [selectedOption, setSelectedOption] = useState();
const [choosing, setChoosing] = useState();
useEffect(() => {
}, [users])
const onShow = () => {
showUser(users[selectedOption - 1].user)
}
const onHide = () => {
hideUser(users[selectedOption - 1].user)
}
const colorChange = (numOption) => {
const id = users[numOption - 1].user
}
return (
<div className="combo_box">
<ComboBox
onSelect={(option) => { setSelectedOption(option); colorChange(option) }}
options={[...Array.from({ length: users.length }, (_, i) => i + 1)]}
/>
<div className='button' >
<Button style={{ "marginRight": 20 }} variant="contained" onClick={onShow}>Show</Button>
<Button variant="contained" onClick={onHide}>Hide</Button>
</div>
</div>
);
}
export default Combo;
Sliders.jsx
import React, { useEffect, useState } from 'react'
import "./style.css"
import 'rc-slider/assets/index.css';
import Slider from 'rc-slider';
const Sliders = ({ hiddenUserChange, name, userBids, setUserInformation, userInformation }) => {
const [bids, setBids] = useState()
useEffect(() => {
setBids(Object.values(userBids))
}, [hiddenUserChange, userBids])
const updateFieldChanged = (newValue, e) => {//OnChanged Slider
setUserInformation(state => {
return state.map(manipulation => {
if (manipulation.name === name) {
Object.entries(manipulation.bids).forEach(([userId, bidValue], index) => {
manipulation.bids[userId] = newValue[index]
console.log(manipulation.bids[userId])
})
}
return manipulation
})
});
}
const handleChange = (event, newValue) => {
setBids(event)
};
return (
<>
<h1 className='headers'>{name}</h1>
{
<Slider
style={{ "marginRight": "20rem", "width": "30rem", "left": "20%" }}
range={true}
trackStyle={[{ backgroundColor: '#3f51b5' }]}
max={100}
RcSlider={true}
railStyle={{ backgroundColor: '#3f51b5' }}
activeDotStyle={{ left: 'unset' }}
ariaLabelForHandle={Object.keys(hiddenUserChange)}
tabIndex={(Object.keys(userBids))}
ariaLabelledByForHandle={bids}
value={(bids)}
onChange={handleChange}
onAfterChange={updateFieldChanged}
tipProps
tipFormatter
/>
}
</>
)
}
export default Sliders
enter image description here
Thank you all!

React js : Audio element's src is updating on setState but the audioRef is not

please help me! I'm using context API to pass the tracks to MucsicPlayer. but whenever I set setTracks state audio element src updates but audioRef.current doesn't update. I inspected it and saw that audioRef.current = <audio preload="auto" src(unknown)>. so ref src does not update. what should I do.
import React, { useState, useEffect, useRef, useContext } from 'react'
import { TrackContext } from '../../music/TrackContext'
const MusicPlayer = () => {
const [tracks, setTracks] = useContext(TrackContext)
console.log(tracks)
// states
const [trackIndex, setTrackIndex] = useState(0)
console.log(trackIndex)
const [trackProgress, setTrackProgress] = useState(0)
const [isPlaying, setIsPlaying] = useState(false)
// eslint-disable-next-line
const [volume, setVolume] = useState(1)
const { title, artist, image, audioSrc } = tracks[trackIndex]
//refs
const audio = new Audio(audioSrc)
const audioRef = useRef(audio)
const intervalRef = useRef()
const isReady = useRef(false)
console.log(audioRef.current)
const { duration } = audioRef.current
const toPrevTrack = () => {
if (trackIndex - 1 < 0) {
setTrackIndex(tracks.length - 1)
} else {
setTrackIndex(trackIndex - 1)
}
}
const toNextTrack = () => {
if (trackIndex < tracks.length - 1) {
setTrackIndex(trackIndex + 1)
} else {
setTrackIndex(0)
}
}
const startTimer = () => {
clearInterval(intervalRef.current)
intervalRef.current = setInterval(() => {
if (audioRef.current.ended) {
toNextTrack()
} else {
setTrackProgress(audioRef.current.currentTime);
}
}, [1000])
}
useEffect(() => {
if (isPlaying) {
audioRef.current.play()
startTimer();
} else {
clearInterval(intervalRef.current)
audioRef.current.pause()
}
// eslint-disable-next-line
}, [isPlaying])
useEffect(() => {
return () => {
audioRef.current.pause()
clearInterval(intervalRef.current)
}
}, [])
useEffect(() => {
audioRef.current.play()
audioRef.current = new Audio(audioSrc)
setTrackProgress(audioRef.current.currentTime)
if (isReady.current) {
audioRef.current.play()
setIsPlaying(true)
startTimer()
} else {
isReady.current = true
}
// eslint-disable-next-line
}, [trackIndex])
const onScrub = (value) => {
clearInterval(intervalRef.current)
audioRef.current.currentTime = value
setTrackProgress(audioRef.current.currentTime)
}
const onScrubEnd = () => {
if (!isPlaying) {
setIsPlaying(true);
}
startTimer();
}
const onScrubVolume = (value) => {
audioRef.current.volume = value
setVolume(audioRef.current.value)
}
function formatMinutes(sec) {
return new Date(sec * 1000).toUTCString().split(" ")[4].substr(3, 8)
}
const currentPercentage = duration ? `${(trackProgress / duration) * 100}%` : '0%';
const trackStyling = `-webkit-gradient(linear, 0% 0%, 100% 0%, color-stop(${currentPercentage}, #fff), color-stop(${currentPercentage}, #777))`;
return (
<div className="player">
<div className="left-block">
<div className="art">
<img src={image} alt="" />
</div>
<div className="song-details">
<div className="song-name">{title}</div>
<div className="artist-name">
{artist}
</div>
</div>
</div>
<div className="center-block">
<div className="song-progress">
<div>{formatMinutes(audioRef.current.currentTime)}</div>
<input
value={trackProgress}
step="1"
min="1"
max={duration ? duration : `${duration}`}
onChange={(e) => onScrub(e.target.value)}
onMouseUp={onScrubEnd}
onKeyUp={onScrubEnd}
style={{ background: trackStyling }}
type="range" />
<div>{duration ? formatMinutes(duration) : "00:00"}</div>
</div>
</div>
</div>
</div>
</div>
)
}
export default MusicPlayer
useRef will not get re-initialized on every render.
So it will stay the same as it was initialized the very first time.
So whenever you are switching your track you have to update the audioRef too.
Change your toPrevTrack and toNextTrack
const toPrevTrack = () => {
const prevIndex = trackIndex - 1 < 0 ? tracks.length - 1 : trackIndex - 1;
const { audioSrc } = tracks[prevIndex]
audioRef.current = new Audio(audioSrc);
}
const toNextTrack = () => {
const nextIndex = trackIndex < tracks.length - 1 ? trackIndex + 1 : 0;
const { audioSrc } = tracks[nextIndex]
audioRef.current = new Audio(audioSrc);
}

How to trigger event when timer count downs to 0

I have a parent component which has timer component inside it. Timer starts at 15 minutes and count downs till 0. When my timer shows time as 0 I want to trigger a submit button event, submit button is inside Quiz Component (Quiz Component is also a child component of Parent Component). I found probably I can use MutationObserver when p tag changes. I am not sure whether it's the correct and only approach or there is better way to achieve this.
Parent Component:
import React, { Component } from 'react';
import '../css/App.css'
import Quiz from './Quiz';
import Timer from './Timer';
import { connect } from 'react-redux';
import { ActionTypes } from '../redux/constants/actionTypes';
import { saveQuizAll, getQuizIndex } from '../commonjs/common.js';
const mapStateToProps = state => { return { ...state.quiz, ...state.quizAll } };
const mapDispatchToProps = dispatch => ({
onQuizLoad: payload => dispatch({ type: ActionTypes.QuizLoad, payload }),
onQuizChange: payload => dispatch({ type: ActionTypes.QuizAnswerAll, payload }),
onPagerUpdate: payload => dispatch({ type: ActionTypes.PagerUpdate, payload })
});
class QuizContainer extends Component {
state = {
quizes: [
{ id: 'data/class1.json', name: 'Class 1' },
{ id: 'data/class2.json', name: 'Class 2' },
{ id: 'data/class3.json', name: 'Class 3' },
{ id: 'data/class4.json', name: 'Class 4' },
],
quizId: 'data/class1.json'
};
pager = {
index: 0,
size: 1,
count: 1
}
componentDidMount() {
console.log('componentDidMount');
this.load(this.state.quizId);
}
load(quizId, isValReload) {
console.log('In load');
let url = quizId || this.props.quizId;
if (isValReload) {
let quiz = this.props.quizAll.find(a => url.indexOf(`${a.id}.`) !== -1);
console.log('In load quiz : ', quiz);
this.pager.count = quiz.questions.length / this.pager.size;
this.props.onQuizLoad(quiz);
this.props.onPagerUpdate(this.pager);
}
else {
fetch(`../${url}`).then(res => res.json()).then(res => {
let quiz = res;
quiz.questions.forEach(q => {
q.options.forEach(o => o.selected = false);
});
quiz.config = Object.assign(this.props.quiz.config || {}, quiz.config);
this.pager.count = quiz.questions.length / this.pager.size;
this.props.onQuizLoad(quiz);
this.props.onPagerUpdate(this.pager);
});
}
}
//This event implements restriction to change class without finishing curretnly selectd class
onClassClick = (e) => {
let qus = this.props.quiz.questions;
// console.log(qus);
let isNotAllAns = qus.some((q, i) => {
var isNot = false;
if (q.answerType.id !== 3 && q.answerType.id !== 4) {
isNot = (q.options.find((o) => o.selected === true)) === undefined;
}
else {
// console.log('q', q);
isNot = ((q.answers === "" || q.answers.length === 0));
}
return isNot;
});
if (isNotAllAns) {
alert('Please complete the quiz.');
e.stopPropagation();
}
}
/*
saveQuizAll(_quizAll, _quiz) {
let allQuiz = [];
// , _quizAll, _quiz;
// if (true) {
// _quiz = this.quiz;
// _quizAll = this.quizAll;
// }
console.log(this, _quiz, _quizAll);
if (_quiz.questions.length !== 0) {
if (_quizAll.length !== undefined) {
console.log('Not Initial Setup Splice', _quiz.id);
allQuiz = _quizAll;
const qIndex = this.getQuizIndex(_quiz.id.toString());
if (qIndex > -1) {
allQuiz.splice(qIndex, 1, _quiz);
}
else {
allQuiz.splice(_quizAll.length, 0, _quiz);
// allQuiz.splice(this.props.quizAll.length-1, 0, this.props.quizAll, this.props.quiz);
}
}
else {
allQuiz[0] = _quiz;
}
return allQuiz;
// if (true) {
// this.onQuizChange(allQuiz);
// }
}
}
*/
onChange = (e) => {
// console.log(this.props.quizAll, this.props.quizAll.length);
let allQuiz = [];
allQuiz = saveQuizAll(this.props.quizAll, this.props.quiz);
//below code converted into saveQuizAll funstion
/*
if (this.props.quizAll.length !== undefined) {
console.log('Not Initial Setup Splice', this.props.quiz.id);
allQuiz = this.props.quizAll;
const qIndex = this.getQuizIndex(this.props.quiz.id.toString());
if (qIndex > -1) {
allQuiz.splice(qIndex, 1, this.props.quiz);
}
else {
allQuiz.splice(this.props.quizAll.length, 0, this.props.quiz);
// allQuiz.splice(this.props.quizAll.length-1, 0, this.props.quizAll, this.props.quiz);
}
}
else {
allQuiz[0] = this.props.quiz;
}
*/
// console.log('allQuiz Out - ', allQuiz);
this.props.onQuizChange(allQuiz);
console.log('Check QuizAll - ', this.props.quizAll);
const aQuiz = JSON.parse(JSON.stringify(this.props.quizAll));
this.setState({ quizId: e.target.value });
if (aQuiz.length !== undefined && getQuizIndex(this.props.quizAll, e.target.value) > -1) {
// console.log(aQuiz.findIndex(a => e.target.value.indexOf(`${a.id}.`) !== -1));
this.load(e.target.value, true);
}
else {
this.setState({ quizId: e.target.value });
this.load(e.target.value, false);
}
}
// getQuizIndex(qID) {
// return this.props.quizAll.findIndex(a => (qID.indexOf(`${a.id}.`) !== -1 || qID.indexOf(`${a.id}`) !== -1));
// }
render() {
return (
<div className="container">
<header className="p-2">
<div className="row">
<div className="col-6">
<h3>DADt Application</h3>
</div>
<div className="col-6 text-right">
<label className="mr-1">Select Quiz:</label>
<select onChange={this.onChange} onClick={this.onClassClick}>
{this.state.quizes.map(q => <option key={q.id} value={q.id}>{q.name}</option>)}
</select>
</div>
</div>
</header>
<Timer duration={900}/>
<Quiz quiz={this.state.quiz} quizId={this.state.quizId} saveAll={saveQuizAll} mode={this.state.mode} />
</div>
);
}
}
export default connect(mapStateToProps, mapDispatchToProps)(QuizContainer);
Here is my Timer Component
import React, { Component } from 'react'
class Timer extends Component {
constructor(props) {
super(props);
this.state = {
seconds: 0
};
}
tick() {
this.setState((prevState) => ({
seconds: prevState.seconds + 1
}));
}
componentDidMount() {
this.interval = setInterval(() => this.tick(), 1000);
}
componentWillUnmount() {
clearInterval(this.interval);
}
render() {
const { duration } = this.props;
let timeLeft = duration - this.state.seconds;
timeLeft = Number(timeLeft);
let minutes = Math.floor(timeLeft % 3600 / 60);
let seconds = Math.floor(timeLeft % 3600 % 60);
let minutesDisplay = minutes > 0 ? minutes + (minutes === 1 ? " : " : " : ") : "";
let secondsDisplay = seconds > 0 ? seconds + (seconds === 1 ? "" : "") : "";
return <p className="badge badge-success">Time Left: {minutesDisplay}{secondsDisplay}</p>;
}
}
export default Timer;
Quiz Component:
import React, { Component } from 'react';
import { ActionTypes } from '../redux/constants/actionTypes';
import Review from './Review';
import Questions from './Questions';
import Result from './Result';
import { connect } from 'react-redux';
// import { saveQuizAll } from '../commonjs/common.js';
const mapStateToProps = state => { return { ...state.quiz, ...state.mode, ...state.pager, ...state.quizAll } };
const mapDispatchToProps = dispatch => ({
onSubmit: payload => dispatch({ type: ActionTypes.QuizSubmit, payload }),
onQuizChange: payload => dispatch({ type: ActionTypes.QuizAnswerAll, payload }),
onPagerUpdate: payload => dispatch({ type: ActionTypes.PagerUpdate, payload })
});
class Quiz extends Component {
move = (e) => {
let id = e.target.id;
let index = 0;
if (id === 'first')
index = 0;
else if (id === 'prev')
index = this.props.pager.index - 1;
else if (id === 'next') {
index = this.props.pager.index + 1;
}
else if (id === 'last')
index = this.props.pager.count - 1;
else
index = parseInt(e.target.id, 10);
if (index >= 0 && index < this.props.pager.count) {
let pager = {
index: index,
size: 1,
count: this.props.pager.count
};
this.props.onPagerUpdate(pager);
}
}
saveStore(e) {
let allQuiz = [];
console.log(this, e);
allQuiz = this.props.saveAll(e.props.quizAll, e.props.quiz);
console.log(allQuiz);
this.props.onQuizChange(allQuiz);
}
setMode = (e) => this.props.onSubmit(e.target.id);
// setMode(e) {
// console.log('in mode',e);this.props.onSubmit(e.target.id);
// }
renderMode() {
console.log('Inside here', this.props.mode);
if (this.props.mode === 'quiz') {
return (<Questions move={this.move} />)
} else if (this.props.mode === 'review') {
return (<Review quiz={this.props.quiz} move={this.move} />)
} else {
console.log('Before Results');
const divSel = document.querySelector('div.col-6.text-right');
// console.log('divSel', divSel);
if (divSel) {
divSel.style.display = "none";
}
return (<Result questions={this.props.quizAll || []} />)
}
}
render() {
return (
<div>
{this.renderMode()}
{(this.props.mode !== 'submit') &&
<div>
<hr />
<button id="quiz" className="btn btn-primary" onClick={this.setMode}>Quiz</button>
<button id="review" className="btn btn-primary" onClick={this.setMode}>Review</button>
<button id="submit" className="btn btn-primary" onClick={(e) => {this.setMode(e); this.saveStore(this)}}>Submit Quiz</button >
</div >}
</div>
)
}
}
export default connect(mapStateToProps, mapDispatchToProps)(Quiz);
I think you can have two approaches.
1. The "react" way
In the Parent component:
// ...
constructor(props) {
// ...
this.state = {
timeExpired: false
};
}
const onTimeExpired = () => {
this.setState({timeExpired: true});
}
// ...
render() {
return (
<div className="container">
{ // ... }
<Timer duration={900} onTimeExpired={onTimeExpired}/>
<Quiz quiz={this.state.quiz} quizId={this.state.quizId} saveAll={saveQuizAll} mode={this.state.mode} triggerSubmit={this.state.timeExpired} />
</div>
);
}
In the Timer component:
// ...
componentDidUpdate() {
if (this.state.seconds === this.props.duration) {
this.props.onTimeExpired();
}
}
// ...
In the Quiz component:
// ...
componentDidUpdate() {
if (this.props.triggerSubmit) {
// Do whatever you do on submit
}
}
// ...
2. The "quick and dirty" way:
In the Timer component
// ...
componentDidUpdate() {
if (this.state.seconds === this.props.duration) {
const quizForm = document.getElementById('quizFormId');
quizForm && quizForm.submit();
}
}
// ...
Provide a prop method onTimeFinished in your Timer component. Then in your render function you can add
{ !(this.props.duration-this.state.seconds) && this.props.onTimeFinished() }
Reference: React Conditional Rendering
try this:
Parent Component:
// state
state = {
triggerSubmit: false
}
// functions
doSubmit = () => {
this.setState({ triggerSubmit: true });
}
resetSubmit = () => {
this.setState({ triggerSubmit: false });
}
// jsx
<Timer duration={900} doSubmit={this.doSubmit} />
<Quiz
quiz={this.state.quiz}
quizId={this.state.quizId}
saveAll={saveQuizAll}
mode={this.state.mode}
resetSubmit={this.resetSubmit}
triggerSubmit={this.state.triggerSubmit} />
Timer Component:
// function
doSubmit = (timeLeft) => {
if (timeLeft === 0) {
this.props.doSubmit();
}
}
// jsx
<p className="badge badge-success"
onChange={() => {this.doSubmit(timeLeft)}>
Time Left: {minutesDisplay}{secondsDisplay}
</p>
Quiz Component:
// state
state = {
triggerSubmit: this.props.triggerSubmit
}
// function
triggerSubmit = () => {
if (this.state.triggerSubmit) {
your trigger submit code here...
this.props.resetSubmit();
}
}

Categories