I have a project, and this project contains several interfaces, and among these interfaces there is an interface for uploading an image, and the problem is in the deletion icon. When you click on it, a modal appears, but the element is deleted before the modal appears.
How can i solve the problem?
this file display a list of instructions that contains upload Image
import '../../../styles/input/index.scss';
import '../../../styles/dropzone/index.scss';
import { Button, Col, message, Modal, Row, Spin, Upload, UploadFile } from 'antd';
import { FunctionComponent, useCallback, useRef, useState } from 'react';
import { motion, useAnimation } from 'framer-motion';
import { defaultTranstion } from '../../../constants/framer';
import { Controller } from 'react-hook-form';
import FromElemnetWrapper from '../form-element-wrapper';
import { Delete, UploadCloud } from 'react-feather';
import { getBase64 } from '../../../utils/get-base64';
import _ from 'lodash';
import config from '../../../api/nuclearMedicineApi/config';
import { FormattedMessage } from 'react-intl';
import BasicModal from '../modal';
import { UploadOutlined } from '#ant-design/icons';
import axios from 'axios';
import { IFormError } from '../general-form-containner';
interface DropzoneProps {
name: string;
control: any;
rules?: any;
label: string;
disabled?: boolean;
multiple?: boolean;
accept?: string;
refType?: number;
defaultFileList?: any;
onRemove?: any;
customRequest?: (option: any) => void;
onProgress?: any;
}
const Dropzone: FunctionComponent<DropzoneProps> = ({
name,
control,
rules,
label,
disabled,
multiple,
accept,
refType,
defaultFileList,
onRemove,
customRequest,
onProgress
}) => {
const focusController = useAnimation();
const errorController = useAnimation();
const [previewVisible, setpreviewVisible] = useState(false);
const [previewImage, setpreviewImage] = useState('');
const handleCancel = () => setpreviewVisible(false);
const handlePreview = async (file: any) => {
if (!file.url && !file.preview) {
file.preview = await getBase64(file.originFileObj);
}
setpreviewImage(file?.preview ?? file.url);
setpreviewVisible(true);
};
const [isModalOpen, setIsModalOpen] = useState(false);
const [errors, setErrors] = useState<IFormError[]>([]);
const [visibleModal, setVisibleModal] = useState(false);
const [removePromise, setRemovePromise] = useState();
const [deleteVisible, setdeleteVisible] = useState<boolean>(false);
const onDeleteHandle = () => {
setdeleteVisible(false);
};
const deletehandleCancel = () => {
setdeleteVisible(false);
};
let resolvePromiseRef = useRef<((value: boolean) => void) | undefined>();
// let resolvePromiseRef = useRef<HTMLInputElement | null>(null)
const handleRemove = useCallback(() =>{
const promise = new Promise(resolve => {
resolvePromiseRef.current = resolve
});
setVisibleModal(true);
return promise;
}, [])
const handleOkModalRemove = useCallback(() => {
if (resolvePromiseRef.current) {
resolvePromiseRef.current(true)
}
}, [removePromise]);
const handleCancelModalRemove = useCallback(() => {
if (resolvePromiseRef.current) {
resolvePromiseRef.current(false);
setVisibleModal(false)
}
}, [removePromise]);
return (
<>
<FromElemnetWrapper
focusController={focusController}
errorController={errorController}
label={label}
required={rules.required?.value}
>
<Controller
control={control}
name={name}
rules={rules}
render={({
field: { onChange, onBlur, value, name, ref },
fieldState: { invalid, error },
}) => {
if (invalid) {
errorController.start({ scale: 80 });
} else {
errorController.start(
{ scale: 0 },
{ ease: defaultTranstion.ease.reverse() },
);
}
return (
<div
onBlur={() => {
onBlur();
focusController.start({ scale: 0 });
}}
onFocus={() => {
focusController.start({ scale: 80 });
}}
className='relative'
>
<div className='upload-container'>
<form
className='dropzone needsclick'
id='demo-upload'
action='/upload'
>
{/* <> */}
<Upload
action={`${config.baseUrl}api/services/app/Attachment/Upload`}
headers={config.headers}
ref={ref}
multiple={multiple}
disabled={disabled}
data={{ RefType: refType }}
listType='picture'
fileList={value}
id={name}
accept={accept}
onPreview={handlePreview}
onRemove={handleRemove}
iconRender={
() => {
return <Spin style={{ marginBottom: '12px', paddingBottom: '12px' }}></Spin>
}
}
progress={{
strokeWidth: 3,
strokeColor: {
"0%": "#f0f",
"100%": "#ff0"
},
style: { top: 12 }
}}
beforeUpload={
(file) => {
console.log({ file });
return true
}
}
// onProgress= {(event: any) => (event.loaded / event.total) * 100}
// onChange={(e) =>
// onChange(e.fileList)
// }
// onChange={(response) => {
// console.log('response: ', response);
// if (response.file.status !== 'uploading') {
// console.log(response.file, response.fileList);
// }
// if (response.file.status === 'done') {
// message.success(`${response.file.name}
// file uploaded successfully`);
// } else if (response.file.status === 'error') {
// message.error(`${response.file.name}
// file upload failed.`);
// }
// else if (response.file.status === 'removed') {
// message.error(`${response.file.name}
// file upload removed.`);
// }
// }}
>
<div className='upload-button'>
<div className='wrapper'>
<motion.div
className='fas fa-angle-double-up'
whileHover={{
y: [
0, -2, 2,
0,
],
transition: {
duration: 1.5,
ease: 'easeInOut',
yoyo: Infinity,
},
}}
>
<UploadCloud
style={{
margin: '.2rem',
display:
'inline-block',
}}
color='white'
size={20}
/>
Upload
</motion.div>
</div>
</div>
</Upload>
{/*
<Modal
title="Are you sure?"
visible={visibleModal}
onOk={handleOkModalRemove}
onCancel={handleCancelModalRemove}
/> */}
<BasicModal
header={
<>
<FormattedMessage id={'confirmdeletion'} />
</>
}
headerType='error'
content={
<>
<Row>
<Col span={8} offset={4}>
<Button
type='primary'
className='savebtn'
onClick={onDeleteHandle}
style={{
cursor:
Object.keys(errors).length !==
0
? 'not-allowed'
: 'pointer',
}}
>
<FormattedMessage id={'affirmation'} />
</Button>
</Col>
<Col span={8} offset={4}>
<Button
type='default'
className='savebtn'
onClick={deletehandleCancel}
style={{
cursor:
Object.keys(errors).length !==
0
? 'not-allowed'
: 'pointer',
}}
>
<FormattedMessage id={'cancel'} />
</Button>
</Col>
</Row>
</>
}
isOpen={visibleModal}
footer={false}
width='35vw'
handleCancel={handleCancelModalRemove}
handleOk={handleOkModalRemove}
/>
{/* {_.isEmpty(value) && (
<div className='dz-message needsclick'>
<FormattedMessage id='dropfileshere' />
</div>
)} */}
<BasicModal
isOpen={previewVisible}
header={<FormattedMessage id="Preview image" />}
footer={false}
handleCancel={handleCancel}
content={<img
alt='example'
style={{ width: '100%' }}
src={previewImage}
/>}
/>
{/* </> */}
</form>
</div>
{invalid && (
<p className='form-element-error'>
{error?.message}
</p>
)}
</div>
);
}}
/>
</FromElemnetWrapper>
</>
);
};
export default Dropzone;
Why it doesn't work?
https://ant.design/components/upload
onRemove - A callback function, will be executed when removing file button is clicked, remove event will be prevented when return value is false or a Promise which resolve(false) or reject
You have to return a promise which resolves to false or return false
How to solve it?
You have to return a promise so first of all, you need a ref or a state to store resolve function of that promise so you can call it in modal
const resolvePromiseRef = useRef<((value: boolean) => void) | undefined>();
Then you will need to assign the resolve function to the ref and return the promise
Now onRemove will wait for your promise to resolve
const handleRemove = () =>{
const promise = new Promise(resolve => {
resolvePromiseRef.current = resolve
});
setVisibleModal(true);
return promise;
}
Now your functions handlers
const handleOkModalRemove = useCallback(() => {
if (resolvePromiseRef.current) {
resolvePromiseRef.current(true)
}
}, [removePromise]);
const handleCancelModalRemove = useCallback(() => {
if (resolvePromiseRef.current) {
resolvePromiseRef.current(false)
}
}, [removePromise]);
You can also use the state, but I recommend ref because it doesn't rerender the component when changed.
Related
I have a project, and this project contains several interfaces, and among these interfaces, I have an interface for uploading an image.
After uploading the image, it is possible for the user to delete it, and a form appears to confirm the deletion process.
But I didn't know how to write the delete function, and for this reason I got this error:
No overload matches this call.
Overload 1 of 2, '(props: UploadProps<any> & { children?: ReactNode; } & RefAttributes<any>): ReactElement<any, string | JSXElementConstructor<any>>', gave the following error.
and this error:
Type '() => Promise<unknown>' is not assignable to type '(file: UploadFile<any>) => boolean | void | Promise<boolean | void>'
How can i solve the problem?
import '../../../styles/input/index.scss';
import '../../../styles/dropzone/index.scss';
import { Modal, Upload, UploadFile } from 'antd';
import { FunctionComponent, useState } from 'react';
import { motion, useAnimation } from 'framer-motion';
import { defaultTranstion } from '../../../constants/framer';
import { Controller } from 'react-hook-form';
import FromElemnetWrapper from '../form-element-wrapper';
import { UploadCloud } from 'react-feather';
import { getBase64 } from '../../../utils/get-base64';
import _ from 'lodash';
import config from '../../../api/nuclearMedicineApi/config';
import { FormattedMessage, useIntl } from 'react-intl';
import BasicModal from '../modal';
import { InboxOutlined } from '#ant-design/icons';
interface DropzoneProps {
name: string;
control: any;
rules?: any;
label: string;
disabled?: boolean;
multiple?: boolean;
accept?: string;
refType?: number;
}
const Dropzone: FunctionComponent<DropzoneProps> = ({
name,
control,
rules,
label,
disabled,
multiple,
accept,
refType,
}) => {
const focusController = useAnimation();
const errorController = useAnimation();
const [previewVisible, setpreviewVisible] = useState(false);
const [previewImage, setpreviewImage] = useState('');
const handleCancel = () => setpreviewVisible(false);
const handlePreview = async (file: any) => {
if (!file.url && !file.preview) {
file.preview = await getBase64(file.originFileObj);
}
setpreviewImage(file?.preview ?? file.url);
setpreviewVisible(true);
};
const [file, setFile] = useState<UploadFile[]>()
const { formatMessage } = useIntl();
// const onGalleryFileRemove = (_file: UploadFile<any>) => { ... }
const onGalleryFileRemove = (_file: UploadFile<any>) => {
const { confirm } = Modal
return new Promise((resolve, reject) => {
confirm({
title: formatMessage({ id: 'confirmationDeletePicture' }),
onOk: () => {
resolve(true)
// < !----onRemoveFunctionality here---->
},
onCancel: () => {
reject(true)
}
})
})
}
return (
<>
<FromElemnetWrapper
focusController={focusController}
errorController={errorController}
label={label}
required={rules.required?.value}
>
<Controller
control={control}
name={name}
rules={rules}
render={({
field: { onChange, onBlur, value, name, ref },
fieldState: { invalid, error },
}) => {
if (invalid) {
errorController.start({ scale: 80 });
} else {
errorController.start(
{ scale: 0 },
{ ease: defaultTranstion.ease.reverse() },
);
}
return (
<div
onBlur={() => {
onBlur();
focusController.start({ scale: 0 });
}}
onFocus={() => {
focusController.start({ scale: 80 });
}}
className='relative'
>
<div
className='upload-container'
>
<form
className='dropzone needsclick'
id='demo-upload'
action='/upload'
>
<Upload
action={`${config.baseUrl}api/services/app/Attachment/Upload`}
headers={config.headers}
ref={ref}
multiple={multiple}
disabled={disabled}
data={{ RefType: refType }}
listType='picture'
fileList={value}
id={name}
accept={accept}
onPreview={handlePreview}
onChange={(e) => {
onChange(e.fileList),
setFile(e.fileList)
}
}
onRemove={() => onGalleryFileRemove(file)}
>
<div>
<motion.div
className='fas fa-angle-double-up'
whileHover={{
y: [
0, -2, 2,
0,
],
transition: {
duration: 1.5,
ease: 'easeInOut',
yoyo: Infinity,
},
}}
>
<InboxOutlined
style={{ marginRight: '27rem', marginTop: '5rem', fontSize: '4rem', color: '#179c8b' }}
/>
</motion.div>
</div>
</Upload>
{_.isEmpty(value) && (
<div className='dz-message needsclick'>
<FormattedMessage id='dropfileshere' />
</div>
)}
<BasicModal
isOpen={previewVisible}
header={<FormattedMessage id="preview-image" />}
footer={false}
handleCancel={handleCancel}
content={<img
alt='example'
style={{ width: '100%' }}
src={previewImage}
/>}
/>
</form>
</div>
{invalid && (
<p className='form-element-error'>
{error?.message}
</p>
)}
</div>
);
}}
/>
</FromElemnetWrapper>
</>
);
};
export default Dropzone;
The typing error tells you that you've provided an invalid callback to the onRemove method.
<Upload onRemove={() => onGalleryFileRemove(file)} />
Your call above is problematic because it uses file that is a state on your app, which is an array of files. Moreover your returned promise has an unknown return type. But the onRemove method only accepts a single file and expects a promise to be resolved with a boolean value. You can do this:
const onGalleryFileRemove = (_file: UploadFile<any>) => {
const { confirm } = Modal
// Ensure promise returns a boolean
return new Promise<boolean>((resolve, reject) => {
// Other logic here
});
};
<Upload onRemove={(file) => onGalleryFileRemove(file)} />
...but you can simply bind your onGalleryFileRemove directly?
<Upload onRemove={onGalleryFileRemove} />
That should solve most of your problem as your function type already matches the expected type of onRemove.
import { FC } from 'react';
import { useDropzone } from 'react-dropzone';
import { FileIcon } from 'assets/icons';
import Typography from '../Typography';
import { TMultipleDropzoneProps } from './types';
import styles from './MultipleDropzone.module.scss';
const MultipleDropzone: FC<TMultipleDropzoneProps> = ({ title, onDrop }) => {
const { getRootProps, getInputProps, open, isDragAccept, isFocused, isDragReject } = useDropzone({
accept: { 'image/*': ['.jpeg', '.png'], 'video/mp4': ['.mp4', '.MP4'] },
onDrop,
noClick: true,
noKeyboard: true,
maxFiles: 3,
});
const accept = isDragAccept ? 1 : 0;
const focused = isFocused ? 1 : 0;
const rejected = isDragReject ? 1 : 0;
// This is used for warning in console for camel-case attributes to the DOM element and to make it boolean
return (
<div className={styles.wrapper}>
<div
onClick={open}
className={styles.container}
{...getRootProps({ accept, focused, rejected })}
>
<input {...getInputProps({})} />
{rejected === 1 && (
<div className={styles.error}>
Some files were rejected because they did not meet the requirements.
</div>
)}
<div className={styles.container__content}>
<Typography>{title}</Typography>
</div>
<button onClick={open} className={styles.icon}>
<FileIcon />
</button>
</div>
</div>
);
};
export default MultipleDropzone;
the type file:
export type TMultipleDropzoneProps = {
title: string;
onDrop: (e: any, a: any) => void;
isFileUploaded: boolean;
maxFiles?: number;
};
the modal I am using it in:
import { useContext, useState } from 'react';
import { ModalContext } from 'context/Modal';
import { FileExtended } from 'types/global/file';
import { useAppDispatch, useAppSelector } from 'hooks';
import { NewPostTextArea, MultipleDropzone, Typography, Button } from 'components';
import { createActivityPost } from 'store/slices/activitiesSlice/activitiesThunks';
import { CloseCircleIcon } from 'assets/icons';
import { TImages } from './types';
import styles from './NewPost.module.scss';
const NewPost = () => {
const { closeModal } = useContext(ModalContext);
const [images, setImages] = useState<TImages[]>([]);
const [description, setDescription] = useState<string>('');
const dispatch = useAppDispatch();
const { userData } = useAppSelector((state) => state.auth);
const createPost = () => {
const post = {
user_id: userData?.id as number,
description: description,
content_url: 'https://cdn.pixabay.com/photo/2015/04/23/22/00/tree-736885__340.jpg',
content_type: 'string',
};
closeModal();
if (description.trim()) {
dispatch(createActivityPost(post));
}
};
const isFileUploaded = images.length > 0;
const onDrop = (acceptedFiles: FileExtended[], maxFiles: number) => {
acceptedFiles.forEach((file) => {
const reader = new FileReader();
reader.onload = (e) => {
setImages((prev) => [
...prev,
{
id: Date.now(),
type: file.type,
src: e.target && e.target.result,
file: file,
description: file.name,
},
]);
};
reader.readAsDataURL(file);
});
let modifiedAcceptedFiles = acceptedFiles;
if (acceptedFiles.length > maxFiles) {
// If the number of files exceeds the maxFiles limit,
// slice the extra files off the array and show an error message
modifiedAcceptedFiles = acceptedFiles.slice(0, maxFiles);
}
};
const removeImage = (id: number) => {
setImages(images.filter((image) => image.id !== id));
};
const imageFiles = images.map((image) => {
return (
<div key={image.id} className={styles.container__main__case__box}>
<CloseCircleIcon
onClick={() => removeImage(image.id)}
className={styles.container__main__case__box_close}
/>
{image.type.includes('video') ? (
<video src={image.src as string} autoPlay loop />
) : (
<img src={image.src as string} alt={image.description} />
)}
</div>
);
});
return (
<div className={styles.container}>
<Typography className={styles.container__head}>New Post</Typography>
<div className={styles.container__description}>
<NewPostTextArea value={description} setValue={setDescription} />
</div>
<div className={styles.container__main}>
<Typography className={styles.container__main__head}>{images.length} items</Typography>
<div className={styles.container__main__case}>{imageFiles}</div>
</div>
<MultipleDropzone
onDrop={onDrop}
title='Please attach your files here (Max 3)'
isFileUploaded={isFileUploaded}
/>
<div className={styles.container__footer}>
<Button className={styles.container__footer_close} onClick={closeModal}>
Close
</Button>
<Button type='submit' className={styles.container__footer_submit} onClick={createPost}>
Create
</Button>
</div>
</div>
);
};
export default NewPost;
I tried adding maxFiles to every single component, I also tried adding it to the onDrop component. New to React ( 1 week) and I am slowly losing my sanity. I will never forgive Zuckerberg for this apparition he has brought upon coderkin. Even chatGPT could not help my case.
I am trying to implement Formik's Field component and Material-Ui Autocomplete for Multiple values with checkboxs.
Every time I try to select a value from the dropdown list it closes the popup and again I have to open it for next value selection.
I am using setFieldValue() method to update the field value because of the Autocomplete's onChange behaviour.
import React, { useState, useEffect } from "react";
import { ErrorMessage, useField, Field, useFormikContext } from "formik";
import styles from "./Form.module.scss";
import { makeStyles, TextField, Checkbox } from "#material-ui/core";
import { Autocomplete } from "#material-ui/lab";
import service from "http-service";
import CheckBoxOutlineBlankIcon from "#material-ui/icons/CheckBoxOutlineBlank";
import CheckBoxIcon from "#material-ui/icons/CheckBox";
// Interface
interface IProps {
name: string;
rest?: any;
className?: string;
marginBottom?: string;
label?: string;
options?: any[];
disabled?: boolean;
tabIndex?: number;
multiple?: boolean;
returnArray?: boolean;
referentialIdKey?: string;
referentialNameKey?: string;
serviceConfig?: string;
query?: string;
processResponse?: Function;
}
// Custom Style
const useStyles = makeStyles((theme) => ({
root: {
"& .MuiAutocomplete-inputRoot": {
padding: "6px",
},
},
}));
const AsyncSearchMultiSelect: React.FC<IProps> = (props) => {
const {
name,
className,
disabled,
options,
marginBottom,
label,
tabIndex,
multiple,
returnArray,
referentialIdKey,
referentialNameKey,
serviceConfig,
query,
processResponse,
...rest
} = props;
const { setFieldValue } = useFormikContext();
const [field] = useField(name);
const classes = useStyles();
// States :
const [asyncOptions, setAsyncOptions] = useState<any[]>([]);
const [fetchedData, setFetchedData] = useState<any>();
// all
const [selectedOpt, setSelectedOpt] = useState<any>([]);
// API Call for Dropdown Options :
useEffect(() => {
if (!serviceConfig) return;
service[serviceConfig]
.getDataByQuery(query || "")
.then(({ data }: { data: any[] }) => {
let dataSet = processResponse ? processResponse(data) : data;
if (dataSet.data) {
dataSet = dataSet.data;
}
setFetchedData(dataSet);
let tempOptions = dataSet.map((item: any) => {
return {
name: referentialNameKey ? item[referentialNameKey] : item["name"],
value: referentialIdKey ? item[referentialIdKey] : item["id"],
} as never;
});
tempOptions.unshift({ name: "All", value: "all" });
console.log("tempOptions >>>> ", tempOptions);
setAsyncOptions(tempOptions);
})
.catch((err: any) => console.log("Error! : ", err));
}, []);
if (field.value.length > 0 && asyncOptions !== []) {
let fullObjValues = asyncOptions.filter((option) =>
field.value.includes(option.value)
);
console.log("fullObjValues >>> ", fullObjValues);
if (fullObjValues.length > 0) {
setFieldValue(name, fullObjValues);
}
}
const handleChange = (event: any, newValue: any) => {
console.log("AsyncSearchableEvent (value) >>> ", newValue);
setFieldValue(name, newValue);
};
const getOptionLabelCustom = (option: any) => {
if (typeof option != "object") {
let optionTitle: any = asyncOptions.find(
(element: { value: any }) => element?.value == option
);
return optionTitle?.name;
} else {
return option?.name;
}
};
const icon = <CheckBoxOutlineBlankIcon fontSize="small" />;
const checkedIcon = <CheckBoxIcon fontSize="small" />;
const getComponent = () => {
return (
<Autocomplete
// {...field}
disableCloseOnSelect
multiple
options={options ? options : asyncOptions}
value={field.value}
limitTags={2}
getOptionLabel={(option) => getOptionLabelCustom(option)}
onChange={(event, value) => handleChange(event, value)}
renderOption={(option, { selected }) => {
// 'all' option
const selectOptIndex = selectedOpt.findIndex(
(opt: any) => opt.name.toLowerCase() === "all"
);
if (selectOptIndex > -1) {
selected = true;
}
//
return (
<React.Fragment>
<Checkbox
icon={icon}
checkedIcon={checkedIcon}
style={{ marginRight: 8 }}
checked={selected}
/>
{option.name}
</React.Fragment>
);
}}
renderInput={(params) => (
<TextField
{...params}
label={label}
classes={classes}
fullWidth
variant="outlined"
/>
)}
/>
);
};
return (
<div
className={styles.element_wrapper}
style={{ marginBottom: marginBottom }}
>
<Field
{...field}
id={name}
name={name}
disabled={disabled}
component={getComponent}
autoComplete="off"
/>
<div className={styles.error}>
<ErrorMessage name={name} />
</div>
</div>
);
};
export default AsyncSearchMultiSelect; ```
try to controll it with open :
const [open, setOpen] = React.useState(false);
<Autocomplete
disabled={disabled}
id={name}
name={name}
sx={{width: "100%"}}
open={open}
onOpen={() => {
setOpen(true);
}}
onClose={() => {
setOpen(false);
}}
This seems like it should be easy but why isn't a button callback function with a setState call not triggering a refresh of the data item? Actually it's just the computeSMA button that isn't changing the sma when the button is selected. The other two callbacks to set inputs work. The fetchData updates the charts so i can't figure this out!! Must be too tired ...
import React, { useState, useEffect } from "react"
import { useRecoilState } from "recoil";
import { closingDataAtom, metaDataAtom, tickerAtom, timeSeriesAtom , smaAtom} from '../../utils/atoms'
import { Container } from '#material-ui/core'
import '../../utils/Home.css'
import { VictoryChart, VictoryBar, VictoryTheme, VictoryVoronoiContainer, VictoryLine, VictoryBrushContainer, VictoryZoomContainer } from 'victory';
import { Chart, Axis, Tooltip, Line, Point } from "bizcharts";
import {XYPlot, LineSeries} from 'react-vis';
const APIKEY = 'demo'
const Home = () => {
const [apikey, setApiKey] = useState(APIKEY)
const [ticker, setTicker] = useRecoilState(tickerAtom);
const [metadata, setMetaData] = useRecoilState(metaDataAtom)
const [closingdata, setClosingData] = useRecoilState(closingDataAtom)
const [dates, setDates] = useRecoilState(timeSeriesAtom)
const [sma, setSMA] = useRecoilState(smaAtom)
const TIME_RESOLUTION = 'Daily'
var requestUrl
if (TIME_RESOLUTION === 'Daily') {
requestUrl = "https://www.alphavantage.co/query?function=TIME_SERIES_DAILY_ADJUSTED&symbol=" + ticker + "&outputsize=full&apikey=" + apikey
} else {
requestUrl = "https://www.alphavantage.co/query?function=TIME_SERIES_WEEKLY_ADJUSTED&symbol=" + ticker + "&outputsize=full&apikey=" + apikey;
}
const fetchData = async () => {
fetch(requestUrl)
.then(response => response.json())
.then(data => {
var closing_price = []
var metadata = []
var dat = []
Object.keys(data['Time Series (Daily)']).forEach((dateKey) => {
closing_price.push(data['Time Series (Daily)'][dateKey]['5. adjusted close'])
dat.push({ 'date': new Date(dateKey) })
})
Object.keys(data['Meta Data']).forEach((metaKey) => {
metadata.push(data['Meta Data'][metaKey])
})
setDates(dat.reverse())
setClosingData(closing_price.reverse())
setMetaData(metadata)
})
.catch(e => {
});
};
const handleSMACompute = (e) => {
var sm = ['2', '3', '4']
setSMA(sm) <====== REACT IS NOT "REACTING"
}
const handleTickerInput = (e) => {
setTicker(e.target.value)
}
const handleAPIInput = (e) => {
setApiKey(e.target.value)
}
return (
<>
<Container className="container" maxWidth="sm">
<div>
<label>Ticker:</label> {ticker}
<input type="text" name="ticker" onChange={handleTickerInput} />
</div>
<div>
<label>APIKEY:</label> {apikey}
<input type="text" name="apikey" onChange={handleAPIInput} />
</div>
<button onClick={fetchData}>
Click it
</button>
<Container className="container" maxWidth="sm">
<ul>{metadata}</ul>
</Container>
<button OnClick={handleSMACompute}> Generate SMA </button>
<Container className="container" maxWidth="sm">
<ul>The value is {sma}</ul>
</Container><div>
</div>
<VictoryChart
theme={VictoryTheme.material}
domainPadding={10}
>
<VictoryBar
style={{ data: { fill: "#c43a31" } }}
data={closingdata}
/>
</VictoryChart>
<div>
<VictoryChart
theme={VictoryTheme.material}
>
<VictoryLine
style={{
data: { stroke: "#c43a31" },
parent: { border: "1px solid #ccc" }
}}
animate={{
duration: 20,
onLoad: { duration: 20 }
}}
containerComponent={<VictoryZoomContainer zoomDomain={{x: [5, 35], y: [0, 100]}}/>}
categories={{
y: dates
}}
data={closingdata}
/>
</VictoryChart>
</div>
</Container>
</>
);
}```
It seems to have been the button setup. I changed to this and it works....??ggrrrr
Click it
</button>
<Container className="container" maxWidth="sm">
<li>{metadata}</li>
</Container>
<button onClick={computeSMA}>
Click it
</button>
<Container className="container" maxWidth="sm">
<li>{sma}</li>
</Container>
In your first code, you used OnClick as the event name. Should be onClick. It is a react syntax and it is case sensitive.
Hello everyone :D I need your advise/tip. Right now I have a APIDataTable component. It has its rows, columns and etc. This component is responsible to show/build data table on frontend with search bar in it above the table. I have an search bar, which is not functional right now. I want it to search data from data table. What should I start from? How can i make it perform search in Table. Thank you for any advise and tip <3
import React, { useEffect, useState } from "react";
import { plainToClassFromExist } from "class-transformer";
import { Pagination } from "../../models/Pagination";
import {
DataTable,
DataTableHead,
DataTableHeadCell,
DataTableBody,
DataTableRow,
DataTableCell,
} from "../DataTable";
import { request } from "../../api";
import "./index.css";
import { MenuSurface } from "../MenuSurface";
import { IconButton } from "../IconButton";
import { Checkbox } from "../Checkbox";
import { Dialog } from "../Dialog";
import { GridCell, GridRow } from "../Grid";
import { Button } from "../Button";
export class Column<T> {
label: string;
width?: number;
filter?: JSX.Element;
render: (row: T) => JSX.Element | string;
constructor(column: Partial<Column<T>>) {
Object.assign(this, column);
}
}
type APIDataTableProps<T> = {
apiPath?: string;
params?: string;
columns?: Column<T>[];
type: Function;
onRowClick?: (row: T) => void;
};
export const APIDataTable = <T extends object>({
apiPath,
params,
columns,
type,
onRowClick,
}: APIDataTableProps<T>) => {
const [data, setData] = useState<Pagination<T>>(null);
const [page, setPage] = useState(1);
const [pageSize, setPageSize] = useState(15);
const [isLoading, setIsLoading] = useState(false);
const [isDialogOpen, setDialogOpen] = useState(false);
const [isMenuSurFaceOpen, setMenuSurfaceOpen] = useState(false);
const [hiddenColumns, setHiddenColumns] = useState<number[]>(
JSON.parse(localStorage.getItem(`hiddenColumns-${apiPath + params}`)) || []
);
const fetchData = async () => {
const urlSearchParams = new URLSearchParams(params);
urlSearchParams.set("page", page.toString());
urlSearchParams.set("page_size", pageSize.toString());
const url = `${apiPath}?${urlSearchParams}`;
const response = await request(url);
const data = plainToClassFromExist(new Pagination<T>(type), response, {
excludeExtraneousValues: true,
});
setData(data);
setIsLoading(false);
};
useEffect(() => {
if (!!apiPath) {
setIsLoading(true);
fetchData();
}
}, [page, pageSize]);
const headCells = columns
.filter((e, i) => !hiddenColumns?.includes(i))
.map((column) => (
<DataTableHeadCell key={column.label} width={column.width}>
{column.label}
</DataTableHeadCell>
));
const rows = data?.results?.map((row, index) => (
<DataTableRow
key={"row-" + index}
onClick={() => !!onRowClick && onRowClick(row)}
>
{columns
.filter((e, i) => !hiddenColumns?.includes(i))
.map((column) => {
return (
<DataTableCell key={column.label} width={column.width}>
<div className="data-table-cell-text">{column.render(row)}</div>
</DataTableCell>
);
})}
</DataTableRow>
));
let uncheckedCheckboxes = hiddenColumns;
const onCheckboxChange = (index: number, value: boolean) => {
if (!value) {
uncheckedCheckboxes.push(index);
//setHiddenColumns(uncheckedCheckboxes);
} else {
const array = [...uncheckedCheckboxes];
array.splice(array.indexOf(index), 1);
uncheckedCheckboxes = array;
}
};
const [isOpen, setIsOpen] = useState(false);
return (
<div className="data-table-container">
<div className="search-test">
<div className="mdc-menu-surface--anchor">
<label
className="mdc-text-field mdc-text-field--filled mdc-text-field--no-label mdc-text-field--with-leading-icon mdc-text-field--with-trailing-icon"
htmlFor="input"
id="search-menu-surface"
>
<IconButton density={-1} icon="search" />
<input
className="mdc-text-field__input "
type="text"
placeholder="Поиск"
id="searchinput"
/>
<IconButton
density={-1}
icon="arrow_drop_down"
onClick={() => {
setMenuSurfaceOpen(true);
}}
/>
</label>
<MenuSurface
isOpen={isMenuSurFaceOpen}
onClose={() => setMenuSurfaceOpen(false)}
fullwidth
>
<div className="data-table-filters-container">
{columns.map(
(column) =>
!!column.filter && (
<div className="data-table-filter">
<div className="data-table-filter-label mdc-typography--subtitle1">
{column.label}
</div>
<div className="data-table-column-filter">
{column.filter}
</div>
</div>
// <GridRow>
// <GridCell span={3}>{column.label}</GridCell>
// <GridCell span={3}>{column.filter}</GridCell>
// </GridRow>
)
)}
{/* <GridCell span={2}> */}
{/* <Button label="Поиск" raised /> */}
{/* <Button
label="Отмена"
raised
onClick={() => {
setIsOpen(false);
}}
/> */}
{/* </GridCell> */}
</div>
</MenuSurface>
</div>
<IconButton
onClick={() => {
setDialogOpen(true);
}}
density={-1}
icon="settings"
/>
<Dialog
isOpen={isDialogOpen}
onOkClick={() => {
localStorage.setItem(
`hiddenColumns-${apiPath + params}`,
JSON.stringify(uncheckedCheckboxes)
);
setDialogOpen(false);
setHiddenColumns(uncheckedCheckboxes);
}}
onCloseClick={() => setDialogOpen(false)}
>
<div
style={{
display: "flex",
flexDirection: "column",
}}
>
{columns.map((column, index) => (
<Checkbox
label={column.label}
onChange={(value) => onCheckboxChange(index, value)}
defaultChecked={!uncheckedCheckboxes.includes(index)}
/>
))}
</div>
</Dialog>
</div>
<DataTable
pagination={true}
count={data?.count}
rowsNumber={data?.results.length}
page={page}
next={data?.next}
previous={data?.previous}
isLoading={isLoading}
onNextClick={() => setPage(page + 1)}
onPreviosClick={() => setPage(page - 1)}
>
<DataTableHead>{headCells}</DataTableHead>
<DataTableBody>{rows}</DataTableBody>
</DataTable>
</div>
);
};
I'm guessing that you want to search bar to effectively filter out rows that don't match. in this case what you want to do is add a filter to the search text (naturally you'll add a state for the search value, but it looks like you'll have that handled.
You'll add your filter here const rows = data?.results?.filter(...).map
You filter function will look something like this
const rows = data?.results.filter((row) => {
// In my own code if I have other filters I just make them return false
// if they don't match
if (
searchText &&
!(
// exact match example
row.field === searchText ||
// case-insensitive example
row.otherField?.toLowerCase().includes(searchText)
// can continue with '||' and matching any other field you want to search by
)
)
return false;
return true;
}).map(...)