Create custom Search bar in react to search through Firebase document - reactjs

I want to create a custom search bar to query my Firestore document retrieve collection based on user input.
I know there are better options to do this like Algolia, Typesense etc.
But I have issues with Firebase upgrading my account, and I have contacted the Firebase team.
DrinkSearch.tsx
const DrinkSearch: React.FC = () => {
const [searchTerm, setSearchTerm] = useState("");
const [drinkSnap, setDrinkSnap] = useState<
QueryDocumentSnapshot<DocumentData>[]
>([]);
const [isLoading, setIsLoading] = useState(false);
const drinkRef = collection(firebaseFirestore, "products");
const drinkQuery = query(drinkRef, where("drinkName", "==", searchTerm));
const snapshots = getDocs(drinkQuery);
let docsIsEmpty!: boolean;
const getProductOnChange = () => {
setIsLoading(true);
snapshots
.then((docsSnapshot) => {
setIsLoading(false);
setDrinkSnap(docsSnapshot?.docs);
docsIsEmpty = docsSnapshot?.empty;
console.log(docsSnapshot?.docs);
})
.catch((error: FirestoreError) => {
setIsLoading(false);
console.log(error.message);
});
};
const handleChange = (e: ChangeEvent<HTMLInputElement>) => {
setSearchTerm(e.currentTarget.value);
getProductOnChange();
};
useEffect(() => {
console.log(searchTerm);
}, [searchTerm]);
return (
<Box>
<InputGroup size="lg">
<InputLeftElement pointerEvents="none">
<RiSearch2Line color="#CBD5E0" size="20px" />
</InputLeftElement>
<Input
onChange={handleChange}
type="text"
_focus={{
boxShadow: shadowSm,
}}
fontSize="14px"
placeholder="Search for drinks"
/>
</InputGroup>
<Box
padding={5}
bgColor="white"
height="40px"
borderBottomRadius={"8px"}
border={"1px solid #EDF2F7"}
>
{docsIsEmpty && <Text>Drink not found.</Text>}
{isLoading && (
<Flex height="100%">
<Spinner size={"sm"} colorScheme={"primary.500"} />
</Flex>
)}
{drinkSnap &&
drinkSnap?.map((drinkSnap) => {
const drinks = drinkSnap?.data();
return (
<HStack
cursor={"pointer"}
justify={"space-between"}
padding={"5px"}
_hover={{
bgColor: "#EDF2F7",
}}
key={drinkSnap?.id}
>
<Text fontWeight={"semibold"}>{drinks?.drinkName}</Text>
<Badge fontSize={"12px"}>{drinks?.category}</Badge>
</HStack>
);
})}
</Box>
</Box>
);
};
export default DrinkSearch;
Result: When I start typing for example black label is the name of a drink, nothing happens i.e the [] is empty. When I remove 'l'. it remains black labe, it returns the array with the collection.
What I want: On typing, return all collections that match what is typed.

Related

useEffect doesn't re-render on state change or infinite looping issue

I have a component which contains a form and a list. When user adds an item to the list though the form, the item should display immediately in the list. I try to use useEffect to fetch data, useEffect without dependency causes an infinite request loop. I added empty array as dependency to prevent looping but in this case new item which is added doesn't display in the list until refreshing the page. How can I solve this issue? (I use antd and antd-form-builder to create the component)
here is my code:
function FieldSetting() {
const [form] = Form.useForm()
const [typeValue, setTypeValue] = useState()
const meta = {
fields: [{ key: "pathname", onChange: (e) => setTypeValue(e.target.value) }],
}
const [data, setData] = useState([])
async function onFinish() {
try {
await axios.post("api", { typeValue, typeId })
form.resetFields()
} catch (e) {
console.log(e)
}
}
useEffect(() => {
const getData = async () => {
const response = await fetch(`api?id=${typeId}`)
const newData = await response.json()
setData(newData)
}
getData()
}, [])
return (
<Container>
<Form form={form} layout="inline" className="form-field" onFinish={onFinish}>
<FormBuilder form={form} meta={meta} />
<Form.Item>
<Button type="primary" htmlType="submit">
Add
</Button>
</Form.Item>
</Form>
<div
id="scrollableDiv"
style={{
height: 665,
overflow: "auto",
padding: "0 16px",
border: "1px solid rgba(140, 140, 140, 0.35)",
}}
>
<List
itemLayout="horizontal"
dataSource={data}
renderItem={(item) => (
<List.Item
actions={[
<a key="list-edit">edit</a>,
<a onClick={() => axios.delete(`http://gage.axaneh.com/api/Gages/SettingProduct/RemoveProductSetting/${item.id}`, item)} key="list-delete">
delete
</a>,
]}
>
<List.Item.Meta title={item.typeValue} />
</List.Item>
)}
/>
</div>
</Container>
)
}
export default FieldSetting
Just add a state that will refretch (trigger useEffect) after you have submitted the form. Be aware that it will refetch all the data from the API. This might bring scalability issues when the data grows.
function FieldSetting() {
const [form] = Form.useForm()
const [refetch, setRefetch] = useState(false) // <----- add this state
const [typeValue, setTypeValue] = useState()
const meta = {
fields: [{ key: "pathname", onChange: (e) => setTypeValue(e.target.value) }],
}
const [data, setData] = useState([])
async function onFinish() {
try {
await axios.post("api", { typeValue, typeId })
form.resetFields()
setRefetch(!refetch) // <----- set the refetch to change the state
} catch (e) {
console.log(e)
}
}
useEffect(() => {
const getData = async () => {
const response = await fetch(`api?id=${typeId}`)
const newData = await response.json()
setData(newData)
}
getData()
}, [refetch]) // <----- add the refetch here to trigger the effect
return (
<Container>
<Form form={form} layout="inline" className="form-field" onFinish={onFinish}>
<FormBuilder form={form} meta={meta}
/>
<Form.Item>
<Button type="primary" htmlType="submit">
Add
</Button>
</Form.Item>
</Form>
<div
id="scrollableDiv"
style={{
height: 665,
overflow: "auto",
padding: "0 16px",
border: "1px solid rgba(140, 140, 140, 0.35)",
}}
>
<List
itemLayout="horizontal"
dataSource={data}
renderItem={(item) => (
<List.Item
actions={[
<a key="list-edit">edit</a>,
<a onClick={() => axios.delete(`http://gage.axaneh.com/api/Gages/SettingProduct/RemoveProductSetting/${item.id}`, item)} key="list-delete">
delete
</a>,
]}
>
<List.Item.Meta title={item.typeValue} />
</List.Item>
)}
/>
</div>
</Container>
)
}
export default FieldSetting```
Whenever you manipulate your array just add a dummy state and change it
add this state
const [extra, setExtra] = useState(0)
when you change the state of your array like add or remove just add this line below
setExtra(extra+1)
what happens is that adding or removing data in an array don't count as a state change in react as per my understanding it need to be something different like true to false or in this case 0 to 1

onSubmit validation toggles between true and false in the validationResults when it shouldn't

It seems to record the entry fields correctly but when submitting, it returns true and then false upon attempting to submit again.
Screenshot showcasing the issue.
I changed the onSubmit to
({ value }) => console.log('Submit', value)
and got the following results, and it seems that when it works as expected it actually returns the value from the form fields but when it fails validation no value from the fields are passed.
Is there something wrong with the way I'm handling states or is this an issue with the form component?
const ResponsiveGrid: VFC<Props> = ({children, areas, ...props}) => {
const arrangement = React.useContext(ResponsiveContext) as "xsmall" | "small" | "medium" | "middle";
const row = React.useContext(ResponsiveContext) as "xsmall" | "small" | "medium" | "middle";
const column = React.useContext(ResponsiveContext) as "xsmall" | "small" | "medium" | "middle";
return (
<Box align={"center"}>
<Grid areas={areas[arrangement]} rows={LeadGeneratorGridRowSettings[row]}
columns={LeadGeneratorGridColSettings[column]} {...props}>
{children}
</Grid>
</Box>
);
};
export default function CardDealer() {
const [userInfo, setUserInfo] = React.useState<UserInfo>({env: "101", money: "$9", first: "",last: "",phone: "",email: "",wagers: "",zip: ""});
const [valid, setValid] = React.useState(false);
let environments = ["101", "102"];
let wagers = ["$9", "$99", "999", "$9,999", "99,999", "999,999"]
async function changeHandler(key: string, value: string) {
await setUserInfo(
{
...userInfo,
[key]: value
}
)
}
function submitHandler() {
if (valid)
console.log(userInfo)
else
console.log("It isn't real")
}
const listMapper = fieldNames.map((field, index) =>
<FormField key={index} label={field.label} name={field.name} placeholder={"type here"}
onChange={(event) => changeHandler(event.target.name, event.target.value)}
validate={[{regexp: field.validation.regexp, message: field.validation.message}, field.validation.func]}/>
)
return (
<ResponsiveContext.Consumer>
{size => (
<ResponsiveGrid responsive={true} gap="medium" areas={ResponsiveGridArrangementSettings}>
<Box gridArea="one">
<Heading level={"4"} alignSelf={"center"}>
Environment
</Heading>
<Select options={environments} value={userInfo.env}
onChange={({option}) => changeHandler("env", option)}/>
</Box>
<Box gridArea="two">
<Heading level={"4"} alignSelf={"center"}>
Lead Bucket
</Heading>
<Select options={wagers} value={userInfo.bucket}
onChange={({option}) => changeHandler("bucket", option)}/>
</Box>
<Box gridArea="three">
<Form value={userInfo} validate={"submit"} onSubmit={() => submitHandler}
onValidate={(validationResults) => {
console.log('validationResults = ', validationResults);
console.log('userInfo = ', userInfo)
setValid(validationResults.valid);
}}>
{listMapper}
<Box alignContent={"center"}>
<Button alignSelf={"center"} type={"submit"} primary
style={{"padding": "15px", borderRadius: "10px"}}>
Generate Leads
</Button>
</Box>
</Form>
</Box>
</ResponsiveGrid>
)}
</ResponsiveContext.Consumer>
)
}

Parsing in props value from parent to component and display

Still new to ReactJS.
I have 3 JS pages: Home, Create, Edit.
From Home, you can navigate to the Create and Edit pages.
You can access the Create page anytime, but you need to call an API to populate some data before you can access the Edit page.
All 3 pages are using the same component, FormEntry. As its name, it generates basically a form input. Within this component, there are 2 functions: Search and AddEdit. Home is using the former, Create and Edit are using the later.
The flow is as such where when you click on the Create button, this will direct you to the Create page. The Create page will then display the form.
However, if you click on the Search button, this will call an API and generate data in a table. Each table row is clickable and clicking on them will direct you to the Edit page. For reusability, I parse in some values using props that, in theory, should populate the form fields based on which row I clicked on.
The issue I'm having is that though the value gets parsed in, the form field is not displaying the correct data. To be specific, the data from the responseData I parsed into the component is not displaying. And even if it does display, it's returning as 'undefined'.
What am I doing wrong?
Home.js
function HomePage() {
const [responseData, setData] = useState([]);
const navigateData = useNavigate();
function navigateToEdit(event){
navigateData({insert URL here}+event.id);
}
function getSearchData2(allData){
( allData.propRefNum !== "" ||
allData.client !== "" ||
allData.appSys !== "" ||
allData.status !== "" ? AxiosCall.searchProposal(JSON.stringify(allData)): AxiosCall.getProposals()
)
.then(
(result) => {
setData(result.data);
}
);
}
return (
<>
<div style={{ height: 400, width: '100%' }}>
<div style={{ display: 'flex', height: '100%' }}>
<div style={{ flexGrow: 1 }}>
<DataGrid onRowClick={navigateToEdit} rows={dataRowObjs} columns={dataColObjs} headerAlign="center" disableColumnFilter />
</div>
</div>
</div>
</>
);
}
export default HomePage;
Edit.js
function EditPage() {
const [responseData, setData] = useState([]);
const { id } = useParams();
useEffect(() => {
const apiData = {
id: id
}
AxiosCall.getProposal(JSON.stringify(apiData))
.then(
(result) => {
setData(result.data);
}
);
},[]);
function getEditData(allData){
fetch({insert URL here}).then(
(result) => {
setData(result);
});
}
return <FormEntry.AddEditFormEntry title="Edit Proposal" defaultDataValue={responseData} responseInputData={getEditData} />
}
export default EditPage;
FormEntry component; AddEditForm
function AddEditFormEntry(props){
const propRefNumRef = useRef();
const descRef = useRef();
const clientRef = useRef();
const appSysRef = useRef();
const statusRef = useRef();
const remarkRef = useRef();
const vendorRef = useRef();
const { register, formState: { errors }, handleSubmit } = useForm();
function submitData(data){
//event.preventDefault();
const propRefNum = propRefNumRef.current.value;
const desc = descRef.current.value;
const client = clientRef.current.value;
const appSys = appSysRef.current.value;
const status = statusRef.current.value;
const remark = remarkRef.current.value;
const vendor = vendorRef.current.value;
const allData = {
propRefNum: propRefNum,
desc: desc,
client: client,
appSys: appSys,
status: status,
remark: remark,
vendor: vendor,
}
props.responseInputData(allData);
}
let defaultRefNum = props.defaultDataValue?.refNum; - **Note A: this is the line in question. When I console.log this variable, it displays the data correctly**
return(
<>
<form className="formEntry" onSubmit={handleSubmit(submitData)}>
<div style={{ display: 'flex'}} >
<div align="left" >
<RouterLink to={insert URL here} >
<IconButton aria-label="search" color="primary" >
<SkipPreviousIcon />
</IconButton>
</RouterLink >
</div>
<h1 style={{ flexGrow: 1, marginTop: -4 }} >{props.title}</h1>
<div align="right">
<IconButton aria-label="search" color="primary" type="submit" >
<SaveIcon />
</IconButton>
<IconButton aria-label="search" color="primary" type="submit" >
<SaveIcon />
</IconButton>
</div>
</div>
<br/>
<Stack justifyContent="center" direction="row" spacing={2} >
<Stack justifyContent="center" direction="column" spacing={2} >
<FieldEntry.TextEntry required="true" label="Proposal Reference Number" type="text" id="input_propRefNum" name="propRefNum" propsRef={propRefNumRef} value={defaultRefNum} />
**Referring to Note A above, I want to populate this field above. I am getting 'undefined', if not blank. If I am using the Create function, blank/undefined is expected. The Edit function is supposed to populate something here **
<FieldEntry.TextEntry label="Description" type="text" id="input_desc" name="desc" propsRef={descRef} value={props.defaultDataValue?.description} />
<FieldEntry.TextEntry required="true" label="Client" type="text" id="input_client" name="client" propsRef={clientRef} />
<FieldEntry.TextEntry required="true" label="Application System" type="text" id="input_appSys" name="appSys" propsRef={appSysRef} value={props.defaultDataValue?.appSystem} />
</Stack>
</Stack>
<br/>
</form>
</>
)
}
Note: I've removed a number of codes that does not pertain to the matter, to keep the sample code small. Rest assured that aside from my issue, everything is working as expected
I think a simpler example(which I just found out will have the same issue) would be
const [defaultRefNum, setRefNum] = useState("");
const [counter, setCounter] = useState(0);
let testValue = props.defaultDataValue?.refNum
useEffect(() => {
console.log("testValue2:",testValue)
if (props.defaultDataValue != null){
console.log("is not Null")
setCounter(c => c+1);
}
else{
console.log("is Null")
}
},[testValue]);
console.log("counter:",counter)
and
<FieldEntry.TextEntry value={counter} />
console.log output - counter: 2
Value in TextEntry: 0
I would assume the value in TextEntry should've outputted to be 2.

Search bar, <input/>

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(...)

Reactjs app doesn't show image preview in safari

I am trying to make an app where a user can upload an image and send it off to an email, it's working fine on all browsers except Safari. For both mobile and web browsers, when I choose an image to upload nothing seems to be previewed nor is it even loaded (ready to be sent). Is there anything I can do to fix this? My code as it stands is really simple:
const EnterDetailPage = props => {
const [imageUrl, setImageUrl] = useState("");
const [imageFile, setImageFile] = useState();
const [upload, setUpload] = useState(null);
const handleUploadChange = async e => {
setLoading(true);
const file = e.target.files[0];
if (!file) {
return;
}
setUpload(URL.createObjectURL(file));
setImageFile(file);
const ref = firebase
.storage()
.ref()
.child(uuid.v4());
const snapshot = await ref.put(file);
let getImageUrl = await snapshot.ref.getDownloadURL();
setImageUrl(getImageUrl);
setLoading(false);
console.log(getImageUrl);
};
let imgPreview = null;
if (upload) {
imgPreview = (
<Avatar
variant="square"
src={upload}
alt="Avatar"
className={classes.bigAvatar}
/>
);
}
return(
<div className="m-auto p-16 sm:px-24 sm:mx-auto max-w-xl">
<input
accept="image/jpeg,image/gif,image/png"
className="hidden"
id="button-file"
type="file"
// onChange={handleUploadChange}
onInput={handleUploadChange}
onClick={event => {
event.target.value = null;
}}
/>
<label
htmlFor="button-file"
className={`${classes.bigAvatar} mt-8 bg-gray-300 m-auto flex items-center justify-center relative w-128 h-128 rounded-4 a-mr-16 a-mb-16 overflow-hidden cursor-pointer shadow-1 hover:shadow-xl`}
>
<div className="absolute flex items-center justify-center w-full h-full z-50">
{imageUrl ? null :
<Icon fontSize="large" color="primary" className="cloud-icon">
cloud_upload
</Icon>}
</div>
{imgPreview}
</label>
);
}:
I compared my code to this article here: https://w3path.com/react-image-upload-or-file-upload-with-preview/
and it seems like I've done exactly the same thing...how come I'm not getting the same results?
There's quite a bit going on your codesandbox example, but by stripping it down its bare bones, I was able to track down the issue...
Safari doesn't seem to support input elements that try to use the onInput event listener -- the callback is never executed. Instead, you can use the onChange event listener.
For the example below, I faked an API call by setting a Promise with a timeout, but this not needed and is only for demonstration purposes. In addition, I like using objects over multiple individual states, especially when the state needs to be synchronous -- it also is cleaner, easier to read, and functions more like a class based component.
Demo: https://jd13t.csb.app/
Source:
components/DetailPage.js
import React, { useRef, useState } from "react";
import { CircularProgress, Icon, Fab } from "#material-ui/core";
const initialState = {
isLoading: false,
imageName: "",
imagePreview: null,
imageSize: 0
};
const EnterDetailPage = () => {
const [state, setState] = useState(initialState);
const uploadInputEl = useRef(null);
const handleUploadChange = async ({ target: { files } }) => {
setState(prevState => ({ ...prevState, isLoading: true }));
const file = files[0];
await new Promise(res => {
setTimeout(() => {
res(
setState(prevState => ({
...prevState,
imageName: file.name,
imagePreview: URL.createObjectURL(file),
imageSize: file.size,
isLoading: false
}))
);
}, 2000);
});
};
const resetUpload = () => {
setState(initialState);
uploadInputEl.current.value = null;
};
const uploadImage = async () => {
if (state.imagePreview)
setState(prevState => ({ ...prevState, isLoading: true }));
await new Promise(res => {
setTimeout(() => {
res(alert(JSON.stringify(state, null, 4)));
resetUpload();
}, 2000);
});
};
const { imagePreview, imageName, imageSize, isLoading } = state;
return (
<div style={{ padding: 20 }}>
<div style={{ textAlign: "center" }}>
<div>
<input
accept="image/jpeg,image/gif,image/png"
className="hidden"
id="button-file"
type="file"
ref={uploadInputEl}
onChange={handleUploadChange}
/>
<label htmlFor="button-file">
<div>
{imagePreview ? (
<>
<img
src={imagePreview}
alt="Avatar"
style={{ margin: "0 auto", maxHeight: 150 }}
/>
<p style={{ margin: "10px 0" }}>
({imageName} - {(imageSize / 1024000).toFixed(2)}MB)
</p>
</>
) : (
<Icon fontSize="large" color="primary" className="cloud-icon">
cloud_upload
</Icon>
)}
</div>
</label>
<Fab
variant="extended"
size="large"
color="primary"
aria-label="add"
className=""
type="button"
onClick={uploadImage}
>
{isLoading ? (
<CircularProgress style={{ color: "white" }} />
) : (
"Submit"
)}
</Fab>
{imagePreview && (
<Fab
variant="extended"
size="large"
color="default"
aria-label="add"
className=""
type="button"
onClick={resetUpload}
>
Cancel
</Fab>
)}
</div>
</div>
</div>
);
};
export default EnterDetailPage;

Resources