How to lift up DatePicker(#mui) value to a parent component? - reactjs

I've just started to study ReactJS and want to write a simple app with DatePicker. My idea is to create a component, called, DateTimeSelector (Child component), with DatePicker, and to call DateTimeSelector in App.js (Parent component) multiple times (at least, to specify start and end date).
And I'm stuck at lifting up state from child to parent component.
Here is my code:
1) DateTimeSelector (Child) component
...
export default function DateTimeSelector(props) {
const [value, setValue] = React.useState(null);
function onChangeHandler(newValue) {
setValue(newValue);
console.log(newValue);
}
return (
<LocalizationProvider dateAdapter={AdapterDateFns}>
<DatePicker label={props.text}
value={value}
onChange={onChangeHandler}
renderInput={(params) => <TextField {...params} />}
/>
</LocalizationProvider>
);
}
2) Main (Parent) component
Within this component I want to output values of each DateTimeSelector component in console and, for example, print these values in .
Now in console I see only values from my DateTimeSelector component, but not from the parent.
So, my question is how to pass date values from DatePickers to parent component as the state variables: startDate and endDate?
...
function App() {
const [startDate, setStartDate] = React.useState(null);
const [endDate, setEndDate] = React.useState(null);
function startDateChangeHandler(value) {
setStartDate(value);
console.log(startDate);
}
function endDateChangeHandler(value) {
setEndDate(value);
console.log(endDate);
}
return (
<Container>
<Grid container spacing={2}>
<Grid item xs={3}>
<div>
<DateTimeSelector text='Start Date'
value={startDate}
onChange={startDateChangeHandler} />
</div>
<div>
<DateTimeSelector text='End Date'
value={endDate}
onChange={endDateChangeHandler}/>
</div>
</Grid>
<Grid item xs={9}>
<div><p>Start Date: {startDate ? JSON.stringify(startDate) : null}</p>
<p>End Date: {endDate ? JSON.stringify(endDate) : null}</p>
</div>
</Grid>
</Grid>
</Container>
);
}
export default App;

I found the solution! Thanks for the advise not to have state in Child!
DateTimeSelector (Child)
export default function DateTimeSelector(props) {
function onChangeHandler(date) {
props.onChange(date);
}
return (
<LocalizationProvider dateAdapter={AdapterDateFns}>
<DatePicker label={props.text}
value={props.date}
onChange={onChangeHandler}
renderInput={(params) => <TextField {...params} />}
/>
</LocalizationProvider>
);
}
App (Parent)
function App() {
const [startDate, setStartDate] = React.useState(null);
const [endDate, setEndDate] = React.useState(null);
function startDateChangeHandler(date) {
console.log('startDateChangeHandler');
setStartDate(date);
console.log(startDate);
}
function endDateChangeHandler(date) {
console.log('endDateChangeHandler');
setStartDate(date);
console.log(endDate);
}
return (
<Container>
<Grid container spacing={2}>
<Grid item xs={3}>
<div>
<DateTimeSelector text='Start Date'
date={startDate}
onChange={startDateChangeHandler} />
</div>
<div>
<DateTimeSelector text='End Date'
date={endDate}
onChange={endDateChangeHandler}/>
</div>
</Grid>
<Grid item xs={9}>
<div>
<p id='start_date'>Start Date: {startDate ? JSON.stringify(startDate) : null}</p>
<p id='end_date'>End Date: {endDate ? JSON.stringify(endDate) : null}</p>
</div>
</Grid>
</Grid>
<Button variant="contained">Hello World!</Button>
</Container>
);
}

Related

TypeError: Cannot read properties of undefined (reading 'userData') when trying to pass data using context api with react router

I am trying to pass the UserData from the App component to dashboard component via context api.But when it redirects to /dashboard it gives me the error that TypeError: Cannot read properties of undefined (reading 'userData').I've been trying to solve the issue but i have been unsuccessful.Can anyone help me with it?
Here is my app.js
function App() {
// const [pageData, setPageData] = useState({});
const [userData , setUserData] = useState({});
const [isLoggedIn, setIsLoggedIn] = useState(false);
const handleLogin = (res) => {
// update the state of the App component with the received data
console.log(res);
setUserData(res);
// you can do whatever you want with the received data here
};
return (
<div className="App">
<UserContext.Provider value={{ isLoggedIn, setIsLoggedIn, userData, setUserData}}>
<Routes>
<Route exact path="/dashboard" element={<Dashboard />} />
<Route
path="/"
element={
isLoggedIn ? (
<Navigate to="/dashboard" />
) : (
<Login setLoggedIn={setIsLoggedIn} handleLogin={handleLogin} />
)
}
/>
</Routes>
</UserContext.Provider>
</div>
);
}
export default App;
This is my dashboard component:
const Dashboard = () => {
const {userData} = useContext(UserContext);
const page = ["Home", "Logout"];
const [sidebar, setSidebar] = useState(false);
const isMobile = useMediaQuery("(max-width:600px)"); // check if screen size is below 600px
console.log(userData.username , userData.password);
const handleToggle = () => {
setSidebar(!sidebar);
};
return (
<div>
<Navbar pages={page} />
<Grid container spacing={2}>
<Grid item xs={12} sm={3}>
{isMobile && (
<Button style={{ margin: "8px" }} onClick={handleToggle}>
Toggle Sidebar
</Button>
)}
{!isMobile && <Sidebar />}
{sidebar && isMobile && <Sidebar />}
</Grid>
<Grid item xs={12} sm={9}>
<Grid container spacing={4}>
<Grid item xs={12} sm={12}>
<Typography variant="h5">Dashboard</Typography>
</Grid>
<Grid item xs={12} sm={12} >
<Typography variant="h3">Welcome back! {userData.username}</Typography>
</Grid>
<Grid item xs={12} sm={12}>
<Paper elevation={3} sx={{height:"15rem",width:"35rem"}}>
<Typography variant="h6" >
Your Current Business Ventures
</Typography>
<Typography variant="body1">
None
</Typography>
<Typography variant="body1" >
{userData.username}
</Typography>
</Paper>
</Grid>
</Grid>
</Grid>
</Grid>
</div>
);
};
export default Dashboard;
I tried a few online solutions but werent of any use
If you're creating your context using React.createContext(), then the initial state of context is undefined, which means userData is undefined briefly.
In your example, to see userData just ignore when it's undefined initially using a ? before the any properties on the undefined object.
console.log({ username: userData?.username, password: userData?.password });

React : Error: Rendered fewer hooks than expected. This may be caused by an accidental early return statement

I never see this error before.
I found where error is occurred.
However, i don't know why it occurred.
there is no return code before setState. If i use {imgPath} only, there is no error occurred.
What I do
I create another state for test. If i use state into component, error occurred. It is line 86.
this is my full code
I delete import component code
import { FC, useRef, useState } from 'react';
import styled from 'styled-components';
import { 팀정보변경, uploadLogo } from 'controller/modifyTeam';
import { getTeamInformation } from 'controller/team';
// module
import { useSnackbarStore } from 'module/store/snackbar';
import { useTeamStore } from 'module/store/team';
const Form: FC<Props> = ({ teamname, timezone, id, name, url }) => {
const { updateTeam } = useTeamStore();
const { pushSnackbar } = useSnackbarStore();
const [isEmpty, setIsEmpty] = useState<boolean>(false);
const [imgPath, setImgPath] = useState<string>('');
const [test, setTest] = useState<string>('');
// ref
const teamNameRef = useRef<HTMLInputElement>(null);
const timeLineRef = useRef<HTMLInputElement>(null);
const imgRef = useRef<HTMLInputElement>(null);
const widthSX = (width: string) =>
useSX({
width: `${width}`,
});
const paddingSX = useSX({
padding: '4px 12px !important',
});
// formData
const onClickUploadImage: () => void = () => imgRef?.current?.click();
const onDeleteImage: () => void = () => setImgPath('');
const updateTeamInformation: () => void = async () => {
const res = await getTeamInformation();
updateTeam(res.data.result);
};
const onSubmit: () => void = async () => {
const timeline = timeLineRef.current.value.slice(4, 10).replaceAll(':', '');
const res = await 팀정보변경(teamNameRef.current.value, timeline, imgPath, id);
if (res.data.msg === 'created') {
await updateTeamInformation();
pushSnackbar({
id: 'teamworthchanged',
type: 'Info',
title: `${t('snackbar.team_worth_changed')}`,
});
}
};
const onChangeImage: (event: any) => void = async (event: any) => {
const formData = new FormData();
formData.append('logo', event.target.files[0]);
const res = await uploadLogo(formData);
setImgPath(res.data.result.path);
};
return (
<>
<SideBar index={1} />
<Container>
<Contents xs={12} sm={8} md={6} lg={5}>
<Font className="h5">{t('nav.menu.settings.basic')}</Font>
<WhiteSpace />
<Grid container item xs={12} direction="column">
{/* 기본설정 */}
<Font className="body-2">{t('settings.basic.profile.heading')}</Font>
<Font className="caption">{t('settings.basic.profile.sub')}</Font>
<Grid container flexWrap="wrap" justifyContent="space-between">
<WhiteSpace />
<Grid xs={4} item container alignItems="center">
{imgPath === '' ? (
url === null ? (
<DefaultImg
container
justifyContent="center"
alignItems="center"
sx={widthSX('80px')}
>
{name?.slice(0, 1)}
</DefaultImg>
) : (
<TeamIcon src={url} alt="팀 로고" />
)
) : (
<TeamIcon src={imgPath} alt={'팀 로고'} />
)}
</Grid>
{/* 이미지 업로드 */}
<Grid container item xs="auto" alignItems="center" flexWrap="wrap" sx={UploadImageSX}>
<label htmlFor="contained-button-file">
<MuiButton
content={
<Grid container alignItems="center">
<Grid item>{t('btn.upload_image')}</Grid>
<Grid item>
<MdiIcon width={16} height={16} src="/icons/upload/ic_upload_white.svg" />
</Grid>
</Grid>
}
size="small"
type="contained"
sx={paddingSX}
onClick={onClickUploadImage}
/>
</label>
<ImageInput
accept="image/*"
id="contained-button-file"
onChange={onChangeImage}
type="file"
ref={imgRef}
/>
{/* 이미지 삭제 버튼 */}
<MuiButton
content={t('btn.remove')}
size="small"
color={palette.gray3}
sx={paddingSX}
onClick={onDeleteImage}
/>
</Grid>
</Grid>
</Grid>
<WhiteSpace />
{/* 팀이름 INPUT */}
<Input
label="common.team_name"
isEmpty={isEmpty}
setIsEmpty={setIsEmpty}
ref={teamNameRef}
value={teamname}
/>
<WhiteSpace />
{/* 시간대 */}
<Grid container item xs={12} direction="column">
<Font className="body-2">{t('settings.basic.timezone.heading')}</Font>
<Font className="caption">{t('settings.basic.timezone.sub')}</Font>
</Grid>
<WhiteSpace />
{/* <TimeLine currentTime={timezone} time={time} setTime={setTime} /> */}
<Timeline ref={timeLineRef} time={timezone} />
<WhiteSpace />
<WhiteSpace />
<MuiButton
content={t('btn.save_changes')}
sx={widthSX('100%')}
type="contained"
onClick={onSubmit}
/>
</Contents>
</Container>
</>
);
};
export default Form;

React Error: Warning: React has detected a change in the order of Hooks called by Form

I am learning the react js and following this tutorial but whenever I type something in the text field I am getting an error like following.
I am using the material ui.
The code is as following:
const Form = () => {
const [postData, setPostData] = useState({
creator: "",
title: "",
message: "",
tags: "",
selectedFile: "",
});
const classes = useStyles();
const dispatch = useDispatch();
const handleSubmit = (e) => {
e.preventdefault();
dispatch(createPost(postData));
};
return (
<Paper className={classes.paper}>
<form
className={`${classes.root} ${classes.form}`}
onSubmit={handleSubmit} >
<Typography variant="h6">Creating a Memory</Typography>
<TextField
name="creator"
label="Creator"
value={postData.creator}
onChange={(e) =>
setPostData({ ...postData, creator: e.target.value })
} />
//... other fields
<Button
className={classes.buttonSubmit}
type="submit"
fullWidth >
submit
</Button>
</form>
</Paper>
);
};
The app.js code where Form is getting called in:
function App() {
const classes = useStyles();
const dispatch = useDispatch();
useEffect(() => {
dispatch(getPosts());
}, [dispatch]);
return (
<Container maxWidth="lg">
<AppBar className={classes.appBar} position="static" color="inherit">
<Typography className={classes.heading} variant="h2" align="center">
Memories
</Typography>
<img
className={classes.image}
src={memories}
alt="memories"
/>
</AppBar>
<Grow in>
<Container>
<Grid
spacing={3}
>
<Grid item xs={12} sm={7}>
<Posts />
</Grid>
<Grid item xs={12} sm={4}>
<Form /> //I am calling it here
</Grid>
</Grid>
</Container>
</Grow>
</Container>
);
}
The useState is in the correct order and I am not using the other hooks as well but somehow getting an error.
This problem happens when you call your hooks in loops, conditions, or nested functions.
I think You are calling some kind of hook inside getPosts() function. This function is inside useEffect, so it will call conditionally, thus error.
Read this guide for a more detailed explanation.

Grid of card in material-ui react

Hello I'm trying to create that I had three cards in a row right now one after the other
Can't find a solution
It's a complement
const Posts = ({ setCurrentId }) => {
const fuzzySearch = (list, searchValue) => {
let buf = ".*" + searchValue.replace(/(.)/g, "$1.*").toLowerCase();
var reg = new RegExp(buf);
let newList = list.filter(function (e) {
return reg.test(e.title.toLowerCase()&&e.message);
});
return newList;
};
const [searchValue, setSearchValue] = useState("");
const posts = useSelector((state) => state.posts);
const classes = useStyles();
const [pageNumber, setPageNumber] = useState(1);
const [buttonnext, setbuttonnext] = useState(false);
const [prebutton, setprebutton] = useState(true);
const limit=8;
const [startIndex,setstartIndex] = useState();
const [endIndex,setendIndex] = useState();
useEffect(()=>{
setstartIndex((pageNumber-1)*limit)
setendIndex(pageNumber*limit)
console.log(startIndex)
console.log(endIndex)
},[posts,pageNumber])
const Next = ()=>{
if(pageNumber === (Math.floor((posts.length+limit -1)/limit))){
setbuttonnext(true)
}else{
setPageNumber(pageNumber+1)
setprebutton(false)
}
}
const Previous = () =>{
if(pageNumber === 1){
setprebutton(true)
}else{
setPageNumber(pageNumber-1)
setbuttonnext(false)
}
}
return(
!posts.length ? <CircularProgress /> : <>
< >
<AppBar className={classes.appBar} position="static" color="inherit">
<Typography className={classes.heading} variant="h2" >קבוצות אחרונות</Typography>
<Paper component="form" className={classes.root}>
<IconButton className={classes.iconButton} aria-label="menu">
</IconButton>
<InputBase
className={classes.input}
placeholder="חיפוש "
inputProps={{ 'aria-label': 'search ' }}
value={searchValue}
onChange={(e) => setSearchValue(e.target.value)}
/>
<IconButton className={classes.iconButton} aria-label="search">
<SearchIcon />
</IconButton>
<Divider className={classes.divider} orientation="vertical" />
</Paper>
</AppBar>
<ol>
{fuzzySearch(posts, searchValue).slice(startIndex,endIndex).map((d) => (
<Grid key={d._id} item xs={10} sm={6} md={6}>
<Grow in>
<Post post={d} setCurrentId={setCurrentId} />
</Grow>
</Grid>) )}
</ol>
</>
<Grid className={classes.container} container alignItems="stretch" spacing={3}>
<Grid container direction="row-reverse"justifyContent="center"alignItems="flex-end" >
<>
<button className={classes.button} onClick={Next}>הבא</button>
<p className={classes.numText}>{ pageNumber}</p>
<Button className={classes.button} onClick={Previous}>אחורה</Button>
</>
</Grid>
</Grid></>
)
};
export default Posts;
Then called to app in app.js Grid in shape
return (
<Container maxWidth="lg" >
<Grow in>
<Container>
<Posts setCurrentId={setCurrentId} />
</Container>
</Grow>
</Container>
);
};
export default App;
I would to create a grid that I had three in a row
I don't know what you are trying to achieve, but I assume that you are trying to display 3 cards in a row. The idea is that Material-UI is using flexbox, and they are divided into 12 equal columns.
<Grid spacing={2}>
// Define grid for card items
{
data.map((dataItem) => (
<Grid key={dataItem.id}>
// ... items inside
</Grid>
))
}
</Grid>

How to focus text input on transition end?

If the Mui Collapsible has extended I want to show a text input that is focused. The autofocus property does not work. Is there away to achive the focus?
Sandbox
<Collapse in={checked}>
<input autofocus type="text" />
</Collapse>
You can assign a ref to input element and make it focused in the useEffect according to the value of checked.
Sandbox
export default function SimpleCollapse() {
const classes = useStyles();
const [checked, setChecked] = React.useState(false);
const textInput = React.useRef(null);
const handleChange = () => {
setChecked((prev) => !prev);
};
React.useEffect(() => {
if (checked) {
textInput.current.focus();
}
}, [checked]);
return (
<div className={classes.root}>
<FormControlLabel
control={<Switch checked={checked} onChange={handleChange} />}
label="Show"
/>
<div className={classes.container}>
<Collapse in={checked}>
<Paper elevation={4} className={classes.paper}>
<input ref={textInput} type="text" />
</Paper>
</Collapse>
</div>
</div>
);
}

Resources