Problems integrating react-hooks-forms with material ui - reactjs

**Hello folks,
my problem is there are no error messages displayed even if nothing is inside. Anyone know what I did wrong? The code is in the attachment.Everything works except the message when nothing was selected in the autocomplete
.
.
.
Am I doing it wrong with the formprovider? Or should I better take the register method from the useForm - Methods? Or schould the text field itself have a controller?
**
const validationSchema = yup.object().shape({
selKategorie: yup.string().required("Select Validation Field is Required"),
}).required();
function Anlage(props) {
const { status, data, isError, isFetching } = useAnlegeData();
const [step, setStep] = React.useState(0);
const methods = useForm({
resolver: yupResolver(validationSchema),
});
const { handleSubmit, formState: { errors }, watch } = methods;
const labels = ['First Step', 'Secound Step', 'Confirmation'];
const handleSteps = (step) => {
switch (step) {
case 0:
return <FirstStep kategorien={data.Kategorie} methods={methods} />
case 1:
return <SecoundStep />
case 2:
return <Confirm />
default:
return <FirstStep />
}
}
return(
<>
<MyNavbar BACK={true}/>
{status.loading && <Loading /> }
<Container component="main" sx={{ mb: 4}}>
<Paper variant="outlined"
sx={{ my: { xs: 3, md: 6 }, p: { xs: 2, md: 3 } }}>
<Stepper activeStep={step} sx={{ py: 3 }} alternativeLabel>
{labels.map((label) => (
<Step key={label}>
<StepLabel>{label}</StepLabel>
</Step>
))}
</Stepper>
{status === 'success' &&
handleSteps(step)}
</Paper>
<p>{errors?.selKategorie?.message}</p>
</Container>
</>
)
}
export default function FirstStep(props) {
return(
<>
<FormProvider {...props.methods}>
<Grid container spacing={2}>
<Grid item xs={12} sm={6}>
<ControlledAutocomplete
name="selKategorie"
options={props.kategorien}
getOptionLabel={(option) => {
console.log('get option label: ',option);
return option.MASKENKEY; }
}
renderInput={(params) => <TextField {...params} label="Ticketkategorie" margin="normal" />}
defaultValue={null}
/>
</Grid>
</Grid>
<Box sx={{ display: 'flex', justifyContent: 'flex-end' }}>
<Button
variant="contained"
sx={{ mt: 3, ml: 1 }}
disabled={false}
color="primary"
onClick={() => null}
>
Next
</Button>
</Box>
</FormProvider>
</>
)
}
const ControlledAutocomplete = ({ options = [], renderInput, getOptionLabel, defaultValue, name}) => {
const { control, formState: { errors } } = useFormContext();
let isError = true;
let errorMessage = "";
if (errors && errors.hasOwnProperty(name)) {
isError = true;
errorMessage = errors[name].message;
}
return (
<Controller
render={({ field: { onChange } }) => (
<Autocomplete
options={options}
getOptionLabel={getOptionLabel}
//renderOption={renderOption}
renderInput={renderInput}
onChange={onChange}
/>
)}
onChange={([, data]) => {
return data
}}
name={name}
control={control}
defaultValue={defaultValue}
errorMessage={errorMessage}
error={isError}
/>
)
}

Related

How to stop modal from closing when clicking on a select option?

I've made a custom filter for MUI's datagrid, the filter has two select's which allow you to filter by the column and filter type. The selects are quite big and endup outside the modal, when clicking on an option the whole modal closes, how can I prevent this from happening?
I've used this tutorial - Detect click outside React component to detect clicks outside the filter.
The code below shows the filter and I've also made an codesandbox example here - https://codesandbox.io/s/awesome-panka-g92vhn?file=/src/DataGridCustomFilter.js:0-6708
any help would be appreciated
import React, { useState, useEffect, useRef } from "react";
import {
Button,
Stack,
FormControl,
InputLabel,
Select,
MenuItem,
Paper,
Grid,
IconButton,
TextField,
ClickAwayListener
} from "#material-ui/core";
import FilterListIcon from "#mui/icons-material/FilterList";
import AddIcon from "#mui/icons-material/Add";
import CloseIcon from "#mui/icons-material/Close";
import { useForm, useFieldArray, Controller } from "react-hook-form";
import { columns } from "./columns";
const filterTypes = {
string: ["contains", "equals", "starts with", "ends with", "is any of"],
int: ["contains", "equals", "less than", "greater than"]
};
function FilterRow({
len,
setOpen,
field,
control,
columns,
index,
handleRemoveFilter
}) {
return (
<Grid container spacing={0}>
<Grid
item
md={1}
style={{
display: "flex",
alignSelf: "flex-end",
alignItems: "center"
}}
>
<IconButton
size="small"
onClick={() => {
if (len === 1) {
setOpen(false);
} else {
console.log(index, "---");
handleRemoveFilter(index);
}
}}
>
<CloseIcon style={{ fontSize: "20px" }} />
</IconButton>
</Grid>
<Grid item md={4}>
<Controller
name={`filterForm.${index}.column`}
control={control}
render={({ field: { onChange, value }, fieldState: { error } }) => (
<FormControl variant="standard" sx={{ width: "100%" }}>
<InputLabel>Column</InputLabel>
<Select
value={value}
onChange={onChange}
label="Column"
defaultValue=""
>
{columns.map((a) => {
return a.exclude_filter === true ? null : (
<MenuItem value={a.headerName}>{a.headerName}</MenuItem>
);
})}
</Select>
</FormControl>
)}
/>
</Grid>
<Grid item md={3}>
<Controller
name={`filterForm.${index}.filter`}
control={control}
render={({ field: { onChange, value }, fieldState: { error } }) => (
<FormControl variant="standard" sx={{ width: "100%" }}>
<InputLabel>Filter</InputLabel>
<Select
value={value}
onChange={onChange}
label="Filter"
defaultValue=""
>
{filterTypes.string.map((a) => {
return <MenuItem value={a}>{a}</MenuItem>;
})}
</Select>
</FormControl>
)}
/>
</Grid>
<Grid item md={4}>
<Controller
name={`filterForm.${index}.value`}
control={control}
render={({ field: { onChange, value }, fieldState: { error } }) => (
<FormControl>
<TextField
onChange={onChange}
value={value}
label="Value"
variant="standard"
/>
</FormControl>
)}
/>
{/* )} */}
</Grid>
</Grid>
);
}
function DataGridCustomFilter() {
const { control, handleSubmit } = useForm();
const { fields, append, remove } = useFieldArray({
control,
name: "filterForm"
});
const [open, setOpen] = useState(false);
const onSubmit = (data) => {};
useEffect(() => {
if (fields.length === 0) {
append({
column: "ID",
filter: filterTypes.string[0],
value: ""
});
}
}, [fields]);
const [clickedOutside, setClickedOutside] = useState(false);
const myRef = useRef();
const handleClickOutside = (e) => {
if (myRef.current && !myRef.current.contains(e.target)) {
setClickedOutside(true);
setOpen(!open);
}
};
useEffect(() => {
document.addEventListener("mousedown", handleClickOutside);
return () => document.removeEventListener("mousedown", handleClickOutside);
});
return (
<>
<Button
startIcon={<FilterListIcon />}
size="small"
onClick={() => {
setOpen(!open);
}}
// disabled={isDisabled}
>
FILTERS
</Button>
{open ? (
<div ref={myRef}>
<Paper
style={{
width: 550,
padding: 10,
zIndex: 1300,
position: "absolute",
inset: "0px auto auto 0px",
margin: 0,
display: "block"
// transform: "translate3d(160.556px, 252.222px, 0px)",
}}
variant="elevation"
elevation={5}
>
<form onSubmit={handleSubmit(onSubmit)}>
<Stack spacing={0.5}>
<div style={{ maxHeight: 210, overflow: "scroll" }}>
{fields.map((field, index) => {
return (
<div style={{ paddingBottom: 5 }}>
<FilterRow
len={fields.length}
control={control}
setOpen={setOpen}
field={field}
columns={columns}
handleRemoveFilter={() => remove(index)}
{...{ control, index, field }}
// handleClickAway={handleClickAway}
/>
</div>
);
})}
</div>
<div style={{ marginTop: 10, paddingLeft: 40 }}>
<Stack direction="row" spacing={1}>
<Button size="small" startIcon={<AddIcon />}>
ADD FILTER
</Button>
<Button size="small" type="submit">
{fields.length > 1 ? "APPLY FILTERS" : "APPLY FILTER"}
</Button>
</Stack>
</div>
</Stack>
</form>
</Paper>
</div>
) : null}
</>
);
}
export default DataGridCustomFilter;
So far I've tried MUI's ClickAwayListener and the example above, both seem to give the same result
DataGrid component uses NativeSelect. I have checked your codesandbox and tried replacing Select to NativeSelect and MenuItem to Option. filter is working properly. below is sample code for update.
...
<NativeSelect
value={value}
onChange={onChange}
label="Column"
defaultValue=""
>
{columns.map((a) => {
return a.exclude_filter === true ? null : (
<option value={a.headerName}>{a.headerName}</option >
);
})}
</NativeSelect>
...

How to disable Mui-focusVisible Mui-focused for an AccordionSummary element?

I'm using a custom element for changing the group name of an AccordionSummary.
The UI in normal mode
Element code
Upon clicking the edit button for changing name, mui-focused mui-focusVisible property is passed to AccordionSummary which creates a grey background over the element. Upon clicking save, the mui-focused property is removed again. However, when trying to submit pressing enter and not clicking save, mui-focused stays and AccordionSummary is greyed/focused even in non-edit mode.
Mui-focused upon edit
Mui-focused/focusVisible element code
Greyed UI
I have also tried using a modal form instead for changing the group name. Got the exact same issue of a persistent mui-focused upon submitting by pressing enter.
Is there any way where I could override/disable focused property? I don't want to preventDefault for enter keypress because of bad UX. Sharing the relevant code below.
const AccordionSummary = withStyles((theme) => ({
content: {
marginTop: theme.spacing(1),
marginBottom: theme.spacing(1),
'&$expanded': {
marginTop: theme.spacing(1),
marginBottom: theme.spacing(1),
},
},
expanded: {},
}))(MuiAccordionSummary)
const useStyles = makeStyles((theme) => ({
turbineIcon: {
marginRight: theme.spacing(2),
},
gap: {
gap: theme.spacing(1),
},
}))
const GroupSummary = ({
onChangeGroupParameter,
}) => {
const classes = useStyles()
const handleGroupNameValidation = (newName) => {
return /^[A-Za-z][A-Za-z0-9\s_\-]+$/.test(newName)
}
const handleGroupNameChange = useCallback(
(newGroupName) => {
onChangeGroupParameter(groupId, 'group', newGroupName)
},
[onChangeGroupParameter, groupId]
)
return (
<AccordionSummary
aria-controls={`${groupId}-content`}
id={`${groupId}-header`}
>
<Box
display="flex"
width="100%"
alignItems="center"
justifyContent="center"
flexWrap="wrap"
className={classes.gap}
>
<TurbineIconButton big visual className={classes.turbineIcon} />
<Tooltip title="Add">
<EditableTypography
variant="h4"
initialValue={groupId}
onValidate={handleGroupNameValidation}
onSave={handleGroupNameChange}
label="group name"
/>
</Tooltip>
</Box>
</AccordionSummary>
)
}
EditableTypography-
const useStyles = makeStyles((theme) => ({
noPadding: {
padding: 0,
},
iconStyle: {
width: theme.spacing(2.5),
height: theme.spacing(2.5),
},
}))
const defaultOnValidate = () => true
const EditableTypography = ({
initialValue,
onSave,
onValidate,
textFieldProps,
iconButtonProps,
containerProps,
...rest
}) => {
const classes = useStyles()
const [value, setValue] = useState(initialValue)
const [editing, setEditing] = useState(false)
const handleKeyPress = useCallback(
(e) => {
if (e.key === 'Enter') {
handleValueSave(value)
} else if (e.key === 'Escape') {
setEditing(false)
setValue(initialValue)
}
},
[handleValueSave, setEditing, setValue, initialValue, value]
)
const isValid = useMemo(() => onValidate(value), [onValidate, value])
const getHelperText = useCallback(() => {
if (value === initialValue) return 'No change'
if (!isValid) return 'Invalid'
return ''
}, [isValid, value, initialValue])
const helperText = useMemo(() => getHelperText(value), [getHelperText, value])
const handleValueSave = useCallback(
(value) => {
setEditing(false)
if (isValid) {
onSave(value)
}
},
[setEditing, onSave, isValid]
)
const handleTextFieldOnChange = useCallback((e) => setValue(e.target.value), [
setValue,
])
const handleSaveClick = useCallback(() => handleValueSave(value), [
handleValueSave,
value,
])
const handleEditClick = useCallback(
(e) => {
e.stopPropagation()
setEditing(true)
},
[setEditing]
)
return (
<Box display="flex" alignItems="center" {...containerProps}>
{editing && (
<>
<TextField
autoFocus
helperText={helperText}
error={!isValid}
value={value}
variant="outlined"
onChange={handleTextFieldOnChange}
onKeyDown={handleKeyPress}
className={classes.noPadding}
{...textFieldProps}
/>
<IconButton onClick={handleSaveClick} {...iconButtonProps}>
<SaveIcon size="small" />
</IconButton>
</>
)}
{!editing && (
<>
<Typography {...rest}>{value}</Typography>
<IconButton onClick={handleEditClick} {...iconButtonProps}>
<EditIcon size="small" className={classes.iconStyle} />
</IconButton>
</>
)}
</Box>
)
}
Modal code-
<EditGroupNameModal
show={editGroupOpen}
initialValue={groupId}
onModalClose={handleModalClose}
onSave={handleGroupNameChange}
/>
const EditGroupNameModal = ({ initialValue, show, onModalClose, onSave }) => {
const classes = useStyles()
const handleClose = () => {
onModalClose(false)
}
const handleSubmit = useCallback((data) => {
onSave(data.groupName)
handleClose()
})
return (
<Modal
className={classes.modal}
open={show}
onClose={handleClose}
onModalClose={handleClose}
>
<EditGroupForm
initialValue={initialValue}
onCancel={handleClose}
onSubmit={handleSubmit}
/>
</Modal>
)
}
const EditGroupForm = ({ onCancel, initialValue, onSubmit }) => {
const validationSchema = Yup.object().shape({
groupName: Yup.string()
.required('Group name is required')
.matches(
/^[A-Za-z][A-Za-z0-9\s_\-]+$/,
'Group name should have min 2 characters and no special characters except dash (-)'
),
})
const {
register,
handleSubmit,
formState: { errors, isDirty },
} = useForm({
defaultValues: {
groupName: initialValue,
},
resolver: yupResolver(validationSchema),
})
const classes = useStyles()
return (
<div>
<form onSubmit={handleSubmit(onSubmit)}>
<Paper className={classes.paper}>
<Box
className={classes.rootBox}
display="flex"
flexDirection="column"
>
<Box mb={3}>
<Typography variant="h4">Edit Group Name</Typography>
</Box>
<Box mb={3}>
<TextField
autoFocus
fullWidth
defaultValue={initialValue}
{...register('groupName')}
// onKeyPress={(e) => {
// e.key === 'Enter' && e.preventDefault()
// }}
/>
<span style={{ color: 'red' }}>{errors.groupName?.message}</span>
</Box>
<Box ml="auto">
<Button
style={{ marginRight: '1vw' }}
variant="contained"
onClick={onCancel}
>
Cancel
</Button>
<Button
variant="contained"
color="primary"
type="submit"
disabled={!isDirty}
>
Save
</Button>
</Box>
</Box>
</Paper>
</form>
</div>
)
}

Mui Checkbox Event not firing when clicked

I have a filter component that renders a several checkboxes and a parent component that controls the state. Everything renders fine but the checkboxes are not clickable and are not firing any event.
Here the filter component:
const FilterComponent = ({ options, handleChange, checked }) => {
return (
<Box sx={{ backgroundColor: '#EDEDED', borderRadius: '5px' }}>
<FormControl sx={{ padding: '19px' }}>
<FormGroup>
<Typography component='h2' variant='h6' sx={{ fontWeight: '700' }}>Brand</Typography>
{options['brands'].map((brand, index) => {
return (
<FormControlLabel
key={index}
control={
<Checkbox
checked={checked['brands'].indexOf(brand) > -1}
onClick={e => handleChange(e, 'brands')}
name={brand}
checkedIcon={<CheckBoxIcon
sx={{
color: "#82BF37"
}}
/>}
/>
}
label={brand}
/>
)
})}
</FormGroup>
</FormControl>
</Box>
)
And the parent component:
const ProductList = ({ products, filter = {
brands: [],
types: [],
ages: [],
breeds: [],
features: [],
petTypes: []
} }) => {
const [filteredProducts, setFilteredProducts] = useState(products)
const classes = useStyles()
const filterModel = getFilterModel(products)
const [checked, setChecked] = useState(filter)
const handleChange = (event, group) => {
console.log(event)
let checkedOptn = [...checked[group]]
setChecked({
...checked,
[group]: checkedOptn.indexOf(event.target.name) > -1 ? checkedOptn.filter(item => item !== event.target.name) :
[...checkedOptn, event.target.name]
})
}
React.useEffect(() => {
const filtered = filterPrdcts(products, checked)
setFilteredProducts(filtered)
}, [checked,products])
return (
<Grid className={classes.wrapper} container >
<Grid item xs={3} sx={{ paddingTop: '10px' }}>
<FilterComponent options={filterModel} handleChange={handleChange} checked={checked} />
</Grid>
<Grid item xs={9} sx={{ paddingTop: '10px' }}>
<div className={classes.productListWrapper}>
{filteredProducts?.map((product, index) => {
return (
<ProductCard product={product} key={index} />
)
})}
</div>
</Grid>
</Grid>
)
}
I pass the event controller function (handleChange) to the child event but does not work.
Appreciate help.
You need to use onChange not onClick for handleChange event.
Sorry to bother you all. It was because the zIndex value on the wrapper element. When I removed the zIndex value, everything started to work fine.

Undesired scrolling in React on rendering component

I have a component where the search results of a query are displayed. When clicking on a item to view its details, instead of loading normally (scrolled all the way up), the details page is, somehow, being affected by the scroll position of the results page. I know I could probably solve this by including window.scrollTo({ top: 0, left: 0 }), but I just want it to behave normally like it should and eliminate what's causing this issue.
Results.js
function Results() {
const matches = useMediaQuery("(max-width:767px)");
const navigate = useNavigate();
const [details, setDetails] = useState({ results: [] });
// Create a custom hook that binds to the `search` property from `location` url object
function useQuery() {
const { search } = useLocation();
return React.useMemo(() => new URLSearchParams(search), [search]);
}
let query = useQuery().toString();
useEffect(() => {
window.scrollTo({ top: 0, left: 0, behavior: "smooth" });
// On successful response, assign the results to `results` array in `details` object
axios
.get(`/api/results/${query}`)
.then((response) => {
setDetails(response.data);
})
.catch((error) => {
console.log(error);
});
}, [query]);
const results = details.total_results;
const pages = details.total_pages;
const baseURL = "https://image.tmdb.org/t/p/w154";
const placeholderImg =
"https://www.genius100visions.com/wp-content/uploads/2017/09/placeholder-vertical.jpg";
return (
<CustomCard
title="Results"
content={
<div>
<Grid container>
<Typography>{results ? results : 0} results found</Typography>
<Grid item xs={12}>
<List sx={{ mt: 3 }}>
{details.results.map((result) => {
return (
<ResultItem
key={result.id}
id={result.id}
imgURL={
result.poster_path
? baseURL + result.poster_path
: placeholderImg
}
title={result.title}
year={result.release_date}
synopsis={result.overview}
/>
);
})}
</List>
</Grid>
</Grid>
<Pagination
sx={{ mt: 2 }}
count={pages}
siblingCount={matches ? 0 : 1}
onChange={(event, page) => {
const url = new URLSearchParams(window.location.search);
url.set("page", page);
const query = url.toString();
navigate(`/results?${query}`);
}}
/>
</div>
}
/>
);
}
export default Results;
ResultItems.jsx
function ResultItem(props) {
const navigate = useNavigate();
const matches = useMediaQuery("(max-width:767px)");
return (
<div>
<ListItem disablePadding>
<ListItemButton alignItems="flex-start" sx={{ pl: 0, pt: 2 }}>
<div>
<img
src={props.imgURL}
alt={`${props.title} poster`}
style={
matches
? { width: "80px", height: "120px" }
: { width: "154px", height: "231px" }
}
/>
</div>
<ListItemText
sx={{ ml: 3 }}
primary={props.title}
secondary={
<React.Fragment>
<Typography component="span" variant="body2">
{props.year.slice(0, 4)}
</Typography>
<br />
<br />
{matches ? null : (
<span style={{ textAlign: "justify" }}>{props.synopsis}</span>
)}
</React.Fragment>
}
onClick={() => navigate(`/details/${props.id}`)}
/>
</ListItemButton>
</ListItem>
<Divider />
</div>
);
}
export default ResultItem;
Details.js
function Details() {
const matches = useMediaQuery("(max-width:718px)");
const navigate = useNavigate();
const [details, setDetails] = useState({
tmdbDetails: { genres: [] },
tmdbCredits: { cast: [], crew: [] },
userData: { rating: null, review: "", view_count: null },
});
const [show, setShow] = useState(false);
const [isUpdated, setIsUpdated] = useState(false);
// GET MOVIE DETAILS AND USER LOGGED DATA
useEffect(() => {
const url = new URL(window.location.href);
axios
.get(`/api${url.pathname}`)
.then((response) => {
setDetails(response.data);
})
.catch((err) => {
console.log(err);
});
}, [isUpdated]);
const baseURL = "https://image.tmdb.org/t/p/w342";
const placeholderImg =
"https://www.genius100visions.com/wp-content/uploads/2017/09/placeholder-vertical.jpg";
// Expand section to log new diary entry or view/edit previous logged data
function handleExpand() {
setShow(!show);
}
// ENTRY DATA
const [userData, setUserData] = useState({
rating: null,
review: "",
date: new Date(),
});
// New object passing user data and movie data to be saved on database
const entryData = {
...userData,
title: details.tmdbDetails.title,
year: getYear(details),
director: getDirector(details),
genres: getGenres(details),
runtime: details.tmdbDetails.runtime,
};
// Control value on Date Picker and set it to `userData.date`
function handleDate(date) {
setUserData((prevValue) => {
return {
...prevValue,
date: date.toISOString(),
};
});
}
// Control value on Rating selector and set it to `userData.rating`
function handleRating(event, value) {
setUserData((prevValue) => {
return {
...prevValue,
rating: value,
};
});
}
// Control value on Text Area and set it to `userData.review`
function handleReview(event) {
const { value } = event.target;
setUserData((prevValue) => {
return {
...prevValue,
review: value,
};
});
}
// Submit entry to database and navigate to Diary
function onSubmit(event) {
event.preventDefault();
const url = new URL(window.location.href);
axios.post(`/api${url.pathname}`, entryData).then((res) => {
navigate("/diary");
});
}
// Function passed to the "WatchedPanel" component to be executed on saving changes after edit entry. It changes `isUpdated` state to force a re-render of `useEffect()` and update entry data on-screen
function handleUpdateDetails() {
setIsUpdated(!isUpdated);
}
return (
<Container component="main" maxWidth="md">
<Card sx={{ padding: matches ? 0 : 3, paddingBottom: 0, margin: 2 }}>
<div style={{ textAlign: "right" }}>
<IconButton
aria-label="close"
style={{ color: "#e0e0e0" }}
onClick={() => {
navigate(-1);
}}
>
<CloseIcon />
</IconButton>
</div>
<CardContent>
<Grid container alignItems="center">
{/* MOVIE TITLE & YEAR */}
<Grid item xs={12}>
<Typography variant="h5">{details.tmdbDetails.title}</Typography>
<Typography sx={{ mb: 2 }} variant="h6">
({getYear(details)})
</Typography>
</Grid>
{/* MOVIE POSTER */}
<Grid item xs={12} sm={matches ? 12 : 5} md={4}>
<div style={{ textAlign: matches ? "center" : "left" }}>
<Poster
source={
details.tmdbDetails.poster_path
? baseURL + details.tmdbDetails.poster_path
: placeholderImg
}
altText={`${details.tmdbDetails.title} poster`}
/>
</div>
</Grid>
{/* MOVIE DETAILS */}
<Grid item xs={12} sm={7} md={8}>
<Collapse in={matches ? show : true}>
<Credits
director={getDirector(details).join(", ")}
cast={getCast(details)}
genres={getGenres(details).join(", ")}
runtime={details.tmdbDetails.runtime}
/>
</Collapse>
</Grid>
<Grid item xs={12} sx={{ mt: 2 }}>
<Collapse in={matches ? show : true}>
<Typography style={{ fontWeight: "bold" }}>Synopsis</Typography>
<Typography>{details.tmdbDetails.overview}</Typography>
</Collapse>
</Grid>
{/* EXPAND SECTION BUTTON */}
{matches ? (
<Fab
color="primary"
size="small"
aria-label="expand"
sx={{ m: "auto", mt: 2 }}
onClick={handleExpand}
>
{show ? <RemoveIcon /> : <AddIcon />}
</Fab>
) : null}
</Grid>
</CardContent>
</Card>
{/* LOG MOVIE PANEL */}
{details.userData === undefined ? (
<UnwatchedPanel
date={userData.date}
onDateChange={handleDate}
rating={userData.rating}
onRatingChange={handleRating}
review={userData.review}
onReviewChange={handleReview}
view_count={
details.userData === undefined ? null : details.userData.view_count
}
onClick={onSubmit}
/>
) : (
<WatchedPanel
date={userData.date}
onDateChange={handleDate}
rating={details.userData.rating}
review={details.userData.review}
view_count={details.userData.view_count}
onSubmit={onSubmit}
onSaveChanges={handleUpdateDetails}
/>
)}
</Container>
);
}
export default Details;
My mistake, actually: this is, indeed, the normal React behavior. If you're navigating from one (scrollable) component to another the scrollbar isn't aware of it, since you're just changing views without reloading the whole page, and will keep it's y (and x) position. To avoid this behavior you need to manually set the scroll position on re-render, like so:
useEffect(() => {
window.scrollTo({ top: 0, left: 0 });
// Do some stuff
}, []);

Material UI Avatar Image Upload

I managed it to make an Avatar chooser, but I don't know how to save the picture in Firebase or how to even save it as a Profile picture.
This is how it looks like:
This comes out if I click on the button:
I can choose a pic, but it just not saving it anywhere and also not displaying the picture in the avatar.
My code:
function Profile(props, navigation) {
const classes = useStyles();
const user = useUser(props.route.params.uid);
const [time, setTime] = useState("7:30");
const [timing, setTiming] = useState([]);
const [timeAfternoon, setTimeAfternoon] = useState("7:30");
const [timingAfternoon, setTimingAfternoon] = useState([]);
const [sickDaysStart, setSickDaysStart] = useState(Date.now());
const [sickDaysEnd, setSickDaysEnd] = useState(Date.now());
const [sickDaysConfirm, setSickDaysConfirm] = useState([]);
const [donwloadURL, setDownloadURL] = useState([]);
const onLogout = () => {
firebase.auth().signOut();
};
function handelSickDaysStart(e) {
setSickDaysStart(e.target.value);
}
function handelSickDaysEnd(e) {
setSickDaysEnd(e.target.value);
}
function handleTime(e) {
setTime(e.target.value);
}
function handleTimeAfternoon(e) {
setTimeAfternoon(e.target.value);
}
function delayMorning() {
db.collection("delayInTheMorning")
.doc()
.set({
time,
user,
})
.then(() => {
//If you wish to push the written data to your local state, you can do it here
setTiming([...timing, { time }]);
console.log("Documents saved succesfully");
})
.catch((err) => {
console.log(err);
});
}
function delayAfternoon() {
db.collection("delayInTheAfternoon")
.doc()
.set({
timeAfternoon,
})
.then(() => {
//If you wish to push the written data to your local state, you can do it here
setTimingAfternoon([...timingAfternoon, { timeAfternoon }]);
console.log("Documents saved succesfully");
})
.catch((err) => {
console.log(err);
});
}
function sickDaysStartEnd() {
db.collection("DaysofSickness")
.doc()
.set({
sickDaysStart,
sickDaysEnd,
user,
})
.then(() => {
//If you wish to push the written data to your local state, you can do it here
setSickDaysConfirm([
...sickDaysConfirm,
{ sickDaysStart, sickDaysEnd },
]);
console.log("Documents saved succesfully");
})
.catch((err) => {
console.log(err);
});
}
function isCurrentUserProfile() {
if (props.route.params.uid === firebase.auth().currentUser.uid) {
return true;
} else {
return false;
}
}
async function handleFileInputChange(e) {
const files = e.target.files;
const file = files[0];
const storage = firebase.storage();
const usersImageRef = storage
.ref()
.child(`users/${user.uid}/profilepicture.jpg`);
const snap = await usersImageRef.put(file);
const donwloadURL = await snap.ref.getDownloadURL();
setDownloadURL(donwloadURL);
await firebase.auth().currentUser.updateProfile({ photoURL: donwloadURL });
}
if (user === null) {
return <div className={classes.root} />;
}
return (
<ScrollView style={styles.root}>
<Container className={classes.div}>
<input
accept="image/*"
className={classes.input}
id="contained-button-file"
multiple
type="file"
onChange={handleFileInputChange}
/>
<label>
<IconButton>
<Avatar
src="../assets/ana.png"
style={{
margin: "10px",
width: "60px",
height: "60px",
}}
/>
</IconButton>
</label>
<Typography className={classes.text}> {user.name} </Typography>
<Typography className={classes.text}> {user.email} </Typography>
{isCurrentUserProfile() ? (
<Button
className={classes.btn}
size="large"
variant="outlined"
onClick={() => onLogout()}
>
Logout
</Button>
) : null}
</Container>
<Card className={classes.div}>
{/* //Verspätung */}
<CardContent className={classes.cardBackGround}>
<Typography variant="h5" className={classes.cardTyp}>
{" "}
Verspätung{" "}
</Typography>
<Container className={classes.cardContainer}>
<TextField
id="time"
label="Zeit"
type="time"
defaultValue="07:30"
InputLabelProps={{
shrink: true,
}}
inputProps={{
step: 300, // 5 min
}}
onChange={(value) => {
handleTime(value);
}}
/>
<Button className={classes.cardBtn} onClick={() => delayMorning()}>
Absenden
</Button>
</Container>
</CardContent>
{/* //Krankenmledungen */}
<CardContent className={classes.cardBackGround}>
<Typography variant="h5" className={classes.cardTyp}>
Krankenmledungen
</Typography>
<Container className={classes.cardContainer}>
<TextField
id="date"
label="Von"
type="date"
defaultValue="2021-09-14"
className={classes.textField}
InputLabelProps={{
shrink: true,
}}
onChange={(value) => {
handelSickDaysStart(value);
}}
/>
<TextField
id="date"
label="bis"
type="date"
defaultValue="2021-09-20"
className={classes.textField}
InputLabelProps={{
shrink: true,
}}
onChange={(value) => {
handelSickDaysEnd(value);
}}
/>
</Container>
<Button
className={classes.cardBtnKM}
onClick={() => sickDaysStartEnd()}
>
Absenden
</Button>
</CardContent>
{/* //Verspätung Abolung*/}
<CardContent className={classes.cardBackGround}>
<Typography variant="h5" className={classes.cardTyp}>
{" "}
Verspätung Abholung
</Typography>
<Container className={classes.cardContainer}>
<TextField
id="time"
label="Zeit"
type="time"
defaultValue="07:30"
InputLabelProps={{
shrink: true,
}}
inputProps={{
step: 300, // 5 min
}}
onChange={(value) => {
handleTimeAfternoon(value);
}}
/>
<Button
className={classes.cardBtn}
onClick={() => delayAfternoon()}
>
Absenden
</Button>
</Container>
</CardContent>
</Card>
{/* <List> */}
{/* Verspätungs Liste */}
{timing.map((item) => {
return (
<List className={classes.lists}>
<ListItem className={classes.list1}>
<ListItemAvatar>
<Avatar></Avatar>
</ListItemAvatar>
<ListItemText
primary={user.name}
secondary={`Verspätung in der Früh ${item.time}`}
/>
</ListItem>
</List>
);
})}
{/* Krankmeldung */}
{timingAfternoon.map((item) => {
return (
<List className={classes.lists}>
<ListItem className={classes.list}>
<ListItemAvatar>
<Avatar></Avatar>
</ListItemAvatar>
<ListItemText
primary={user.name}
secondary={`Verspätung bei der Abholung ${item.timeAfternoon}`}
/>
</ListItem>
</List>
);
})}
{/* Verspätungs Nachmittag */}
{sickDaysConfirm.map((item) => {
return (
<List className={classes.lists}>
<ListItem className={classes.list}>
<ListItemAvatar>
<Avatar></Avatar>
</ListItemAvatar>
<ListItemText
primary={user.name}
secondary={`Krankmeldung von ${item.sickDaysStart} bis ${item.sickDaysEnd}`}
/>
</ListItem>
</List>
);
})}
</ScrollView>
);
}
There is also a imagePicker from Expo, I was thinking of trying that but I'm just not sure.
If you want to change the profile picture of a user you can use this code:
import { getAuth, updateProfile } from "firebase/auth";
const auth = getAuth();
updateProfile(auth.currentUser, {
displayName: "User Name", photoURL: "https://example.com/jane-q-user/profile.jpg"
}).then(() => {
// Profile updated!
// ...
}).catch((error) => {
// An error occurred
// ...
});
You can find more info about it here.
To get the picture URL you would need to upload it to Firebase Storage and get the downloadURL:
import { getStorage, ref, uploadBytes, getDownloadURL } from "firebase/storage";
const storage = getStorage();
const storageRef = ref(storage, 'some-child');
// 'file' comes from the Blob or File API
uploadBytes(storageRef, file).then((snapshot) => {
console.log('Uploaded a blob or file!');
getDownloadURL(snapshot.ref).then((downloadURL) => {
console.log('File available at', downloadURL);
});
});
I would also change the input to not have multiple files to select. If you would share more of your code I can integrate those code snippets in your code in case you have problems with it.
UPDATE:
Here is the whole example. I see you use the old SDK so the example is with it:
function Profile(props, navigation) {
const classes = useStyles();
const user = useUser(props.route.params.uid);
const delay = DayandTime();
const [time, setTime] = useState("7:30");
const [timing, setTiming] = useState([]);
const [downloadurl, setDU] = useState("/images/example.jpg");
const [timeAfternoon, setTimeAfternoon] = useState("7:30");
const [timingAfternoon, setTimingAfternoon] = useState([]);
const [sickDaysStart, setSickDaysStart] = useState(Date.now());
const [sickDaysEnd, setSickDaysEnd] = useState(Date.now());
const [sickDaysConfirm, setSickDaysConfirm] = useState([]);
const onLogout = () => {
firebase.auth().signOut();
};
function handelSickDaysStart(e) {
setSickDaysStart(e.target.value);
}
function handelSickDaysEnd(e) {
setSickDaysEnd(e.target.value);
}
function handleTime(e) {
setTime(e.target.value);
}
function handleTimeAfternoon(e) {
setTimeAfternoon(e.target.value);
}
function delayMorning() {
db.collection("delayInTheMorning")
.doc()
.set({
time,
user,
})
.then(() => {
//If you wish to push the written data to your local state, you can do it here
setTiming([...timing, { time }]);
console.log("Documents saved succesfully");
})
.catch((err) => {
console.log(err);
});
}
function delayAfternoon() {
db.collection("delayInTheAfternoon")
.doc()
.set({
timeAfternoon,
})
.then(() => {
//If you wish to push the written data to your local state, you can do it here
setTimingAfternoon([...timingAfternoon, { timeAfternoon }]);
console.log("Documents saved succesfully");
})
.catch((err) => {
console.log(err);
});
}
function sickDaysStartEnd() {
db.collection("DaysofSickness")
.doc()
.set({
sickDaysStart,
sickDaysEnd,
user,
})
.then(() => {
//If you wish to push the written data to your local state, you can do it here
setSickDaysConfirm([
...sickDaysConfirm,
{ sickDaysStart, sickDaysEnd },
]);
console.log("Documents saved succesfully");
})
.catch((err) => {
console.log(err);
});
}
function isCurrentUserProfile() {
if (props.route.params.uid === firebase.auth().currentUser.uid) {
return true;
} else {
return false;
}
}
if (user === null) {
return <div className={classes.root} />;
}
async function handleFileInputChange(e) {
const files = e.target.files;
const file = files[0];
const storage = firebase.storage();
const usersImageRef = storage
.ref()
.child(`users/${user.uid}/profilepicture.jpg`);
const snap = await usersImageRef.put(file);
const downloadURL = await snap.ref.getDownloadURL();
setDU(downloadURL);
await firebase.auth().updateProfile({ photoURL: downloadURL });
}
return (
<ScrollView style={styles.root}>
<Container className={classes.div}>
<input
accept="image/*"
className={classes.input}
id="contained-button-file"
multiple
type="file"
onChange={handleFileInputChange}
/>
<label htmlFor="contained-button-file">
<IconButton>
<Avatar
src="/images/example.jpg"
style={{
margin: "10px",
width: "60px",
height: "60px",
}}
/>
</IconButton>
</label>
<Typography className={classes.text}> {user.name} </Typography>
<Typography className={classes.text}> {user.email} </Typography>
{isCurrentUserProfile() ? (
<Button
className={classes.btn}
size="large"
variant="outlined"
onClick={() => onLogout()}
>
Logout
</Button>
) : null}
</Container>
<Card className={classes.div}>
{/* //Verspätung */}
<CardContent>
<Typography variant="h5" className={classes.cardTyp}>
{" "}
Verspätung{" "}
</Typography>
<Container className={classes.cardContainer}>
<TextField
id="time"
label="Zeit"
type="time"
defaultValue="07:30"
InputLabelProps={{
shrink: true,
}}
inputProps={{
step: 300, // 5 min
}}
onChange={(value) => {
handleTime(value);
}}
/>
<Button className={classes.cardBtn} onClick={() => delayMorning()}>
Absenden
</Button>
</Container>
</CardContent>
{/* //Krankenmledungen */}
<CardContent className={classes.cardKrankmeldung}>
<Typography variant="h5" className={classes.cardTyp}>
{" "}
Krankenmledungen{" "}
</Typography>
<Container className={classes.cardContainer}>
<TextField
id="date"
label="Von"
type="date"
defaultValue="2021-09-14"
className={classes.textField}
InputLabelProps={{
shrink: true,
}}
onChange={(value) => {
handelSickDaysStart(value);
}}
/>
<TextField
id="date"
label="bis"
type="date"
defaultValue="2021-09-20"
className={classes.textField}
InputLabelProps={{
shrink: true,
}}
onChange={(value) => {
handelSickDaysEnd(value);
}}
/>
</Container>
<Button
className={classes.cardBtnKM}
onClick={() => sickDaysStartEnd()}
>
Absenden
</Button>
</CardContent>
{/* //Verspätung Abolung*/}
<CardContent>
<Typography variant="h5" className={classes.cardTyp}>
{" "}
Verspätung Abholung
</Typography>
<Container className={classes.cardContainer}>
<TextField
id="time"
label="Zeit"
type="time"
defaultValue="07:30"
InputLabelProps={{
shrink: true,
}}
inputProps={{
step: 300, // 5 min
}}
onChange={(value) => {
handleTimeAfternoon(value);
}}
/>
<Button
className={classes.cardBtn}
onClick={() => delayAfternoon()}
>
Absenden
</Button>
</Container>
</CardContent>
</Card>
<List>
{/* Verspätungs Liste */}
<ListItem>
<ListItemAvatar>
<Avatar></Avatar>
</ListItemAvatar>
<ListItemText
primary={user.name}
secondary={`Verspätung in der Früh ${delay}`}
/>
</ListItem>
{/* Verspätungs Krankmeldung */}
<ListItem>
<ListItemAvatar>
<Avatar></Avatar>
</ListItemAvatar>
<ListItemText
primary={user.name}
secondary={`Krankmeldung ${delay}`}
/>
</ListItem>
{/* Verspätungs Nachmittag */}
<ListItem>
<ListItemAvatar>
<Avatar></Avatar>
</ListItemAvatar>
<ListItemText
primary={user.name}
secondary={`Verspätung Nachmittag ${delay}`}
/>
</ListItem>
</List>
</ScrollView>
);
}
const mapStateToProps = (store) => ({
currentUser: store.userState.currentUser,
posts: store.userState.posts,
following: store.userState.following,
});
export default connect(mapStateToProps, null)(Profile);
const useStyles = makeStyles({
root: {
backgroundColor: "white",
},
div: {
marginTop: 20,
marginLeft: 15,
marginRight: 15,
backgroundColor: "white",
},
avatar: {
marginBottom: 10,
},
btn: {
marginTop: 10,
width: 250,
marginBottom: 30,
},
text: {
fontSize: 25,
marginTop: 10,
},
cardTyp: {
textAlign: "left",
paddingLeft: 13,
},
cardBtn: {
marginTop: 20,
marginLeft: 30,
},
cardBtnKM: {
marginTop: 20,
marginLeft: 10,
},
cardContainer: {
display: "flex",
},
});
const styles = StyleSheet.create({
root: {
backgroundColor: "white",
},
});

Resources