我试图通过钩子将我的类组件重构为功能,但在初始状态声明方面遇到了困难。
代码如下:
export default function Profile(){
const [profile, setProfile] = React.useState({
firstname: '',
lastname: ''
})
return (
<SomeComponent value={profile.firstname} />
)
}
错误是“无法读取null的属性'firstname'”
console.log显示profile.firstname值
我确实知道不像setState那样不存在值,但是我不知道如何等待它,然后... 因为在类组件中,初始状态实际上是在渲染之前实际初始化的。
应在之后的空字符串中获取实际值,所以不是这样:/
完整代码,我认为复制粘贴的内容太多了:
import React, { Component } from 'react'
import {
Container,
Typography,
Paper,
Grid,
Avatar,
IconButton,
TextField,
Select,
InputLabel,
NativeSelect } from '@material-ui/core';
import { makeStyles } from '@material-ui/core/styles';
import { MuiPickersUtilsProvider, KeyboardDatePicker } from '@material-ui/pickers';
import DateFnsUtils from '@date-io/date-fns';
import Skeleton from '@material-ui/lab/Skeleton'
import jwt_decode from 'jwt-decode'
import { Redirect } from 'react-router-dom';
import { useHistory } from 'react-router';
import {
getProfile,
getProfileAvatar,
editProfile } from '../services/calls'
import UploadButton from './UploadButton'
import {
CheckCircleIcon,
CancelIcon,
EditIcon
} from '@material-ui/icons';
import ProfileAvatar from '../components/ProfileAvatar'
import ProfileDataLine from '../components/ProfileDataLine'
import { AuthContext } from '../services/AuthContextProvider'
import { timestring, date_parse } from '../services/suppl'
const useStyles = makeStyles((theme) => ({
root: {
minHeight: 300,
flexGrow: 1,
minWidth: 300,
transform: 'translateZ(0)',
'@media all and (-ms-high-contrast: none)': {
display: 'none'},
},
avatar:{
width: "100px",
height: "100px",
'font-size': '1.5em'
},
paper: {
marginTop: theme.spacing(5),
padding: theme.spacing(3),
display: 'flex',
flexDirection: 'column',
alignItems: 'center'
},
about: {
flexDirection: 'column',
alignItems: 'center'
},
edit: {
'align-self': 'baseline',
'max-width': 'fit-content',
'min-width': 'fit-content'
},
profilecont: {
alignItems: 'center',
justifyContent: 'space-evenly',
},
error: {
color: 'red',
'font-size': '0.8em'
},
message: {
color: 'green',
'font-size': '0.8em'
},
debug: {
position: 'fixed',
top: '120px',
left: '200px',
'z-index': '10000',
'font-size': '1em',
padding: '0 0',
margin: '0 0',
'& > *': {
'font-size': '0.5em',
padding: '0 0',
margin: '0 0'
}
}
}));
export default function Profile (props) {
const classes = useStyles();
const history = useHistory();
const auth = React.useContext(AuthContext);
const [profile, setProfile] = React.useState({
firstname: '',
lastname: '',
id: auth.id,
email: '',
created: '',
last_login: '',
num_logins: '',
groups_amount: 0,
students_amount: 0,
location: 'unknown',
bday: '',
pricing: '',
rights: ''
});
const [edit, setEdit] = React.useState(false);
const [edited, setEdited] = React.useState(null);
const [error, setError] = React.useState('');
const [message, setMessage] = React.useState('');
const [avatar, setAvatar] = React.useState('');
const [disabled, setDisabled] = React.useState(false);
const reloader = () => {
setAvatar(this.state.avatar+Math.floor(Math.random()*(10**12)))
}
const profile_loader = () => {
if (auth.loggedIn){
const token = auth.accessToken
getProfile(profile.id).then(res => {
if (res && !res.error) {
setProfile({
...profile,
firstname: res.firstname,
lastname: res.lastname,
email: res.email,
created: res.time_created,
last_login: res.last_login ? res.last_login : 'No logins yet',
num_logins: res.login_amounts,
groups_amount: res.groups_amount,
students_amount: res.students_amount,
location: res.location ? res.location : 'Unknown',
bday: res.bday ? date_parse(res.bday) : 'Unknown',
pricing: res.pricing,
rights: res.rights
})
setAvatar(res.avatar_url+Math.floor(Math.random()*(10**12)))
}
else{
history.push(`/users`)
}
})
}
else {
history.push(`/`)
}
}
React.useEffect(() => {
profile_loader()
});
const profile_edit = () => {
if (auth.loggedIn){
const token = auth.accessToken
let user = {id: profile.id}
const data = edited
console.log(data)
const keys = Object.keys(data)
for (var i = keys.length - 1; i >= 0; i--) {
user[keys[i]] = data[keys[i]]
}
editProfile(user).then(res => {
if (res && !res.error) {
console.log(res)
setMessage('Profile info has been changed', profile_loader())
}
else {
setError(res.error, this.profile_loader())
}
})
}
else {
history.push(`/`)
}
}
const editFields = () => {
setEdit(true)
}
const onChange = e => {
e.target.value === '' && !disabled && setDisabled(true); setError('fields cannot be emptied');
e.target.value !== '' && disabled && setDisabled(false); setError('');
setEdited({
...edited,
[e.target.name]: e.target.value
})
}
const onDateChange = date => {
setEdited({
...edited,
bday: date.toUTCString()
})
}
const editFieldsApply = () => {
setEdit(false)
if(!edited){
setMessage('Nothing changed', setTimeout(() => setMessage(''), 5000))
}
else{
profile_edit()
setTimeout(() => setMessage(''), 5000)
setTimeout(() => setError(''), 5000)
}
}
const editFieldsCancel = () => {
setEdit(false)
setEdited(null)
}
const pricing = (
profile ?
profile.pricing === 0 ? 'Demo' :
profile.pricing === 1 ? 'Basic subscription' :
profile.pricing === 2 ? 'Premium subscription' :
null
: null
);
const rights = (
profile ?
profile.rights === 0 ? 'Student' :
profile.rights === 1 ? 'Teacher' :
profile.rights === 2 ? 'Administrator' :
profile.rights === 3 ? 'Superuser' :
null
: null
);
const now = new Date().getTime()/1000
return (
profile && <Container component="main" maxWidth="md" spacing={2}>
<Paper className={classes.paper}>
<Typography component="h1" variant="h6" style={{ marginBottom: '25px' }}>
Profile ID:{profile.id}
</Typography>
<Grid container spacing={2} className={classes.profilecont}>
<Grid item>
<Grid item md={true} sm={true} xs={true} >
<IconButton>
<ProfileAvatar id={profile.id} avatar={profile.avatar} w='100px' h='100px'/>
{profile.id == auth.id && <UploadButton reloader={() => {reloader()}} />}
</IconButton>
</Grid>
</Grid>
<Grid item className={classes.about}>
<ProfileDataLine editable label='First Name' onChange={onChange} edit={edit} purpose='firstname' value={profile.firstname} new_value={edited.firstname} />
<ProfileDataLine editable label='Last Name' onChange={onChange} edit={edit} purpose='lastname' value={profile.lastname} new_value={edited.lastname} />
<ProfileDataLine editable label='E-Mail' onChange={onChange} edit={edit} purpose='email' value={profile.email} new_value={edited.email} />
<ProfileDataLine label='Created' edit={edit} purpose='created' value={profile.created} />
<ProfileDataLine label='Last login' edit={edit} purpose='last_login' value={profile.last_login} />
<ProfileDataLine label='Num logins' edit={edit} purpose='num_logins' value={profile.num_logins} />
<ProfileDataLine label='Groups' edit={edit} purpose='groups_amount' value={profile.groups_amount} />
<ProfileDataLine label='Students' edit={edit} purpose='students_amount' value={profile.students_amount} />
<ProfileDataLine label='Location' edit={edit} purpose='location' value={profile.location} />
<Grid item md={true} sm={true} xs={true}>
{!edit
? profile.bday instanceof Date && <Typography
id="bday"
name="bday"
type="bday">
Birthday: {timestring(profile.bday).split(' ')[0]}
</Typography>
: <MuiPickersUtilsProvider utils={DateFnsUtils}>
<KeyboardDatePicker
margin="normal"
id="date-picker-dialog"
label="Choose date"
format="dd.MM.yyyy"
disableFuture
name='bday'
value={edited.bday != 'Unknown' ? edited.bday : new Date().toISOString().split(' ')[0]}
onChange={onDateChange}
KeyboardButtonProps={{
'aria-label': 'change date',
}}
/>
</MuiPickersUtilsProvider>
}
</Grid>
<ProfileDataLine editable picker edit={edit} new_value={edited.pricing} label='Billing plan' purpose='pricing' value={pricing}/>
<ProfileDataLine editable picker edit={edit} new_value={edited.rights} label='Rights' purpose='rights' value={rights}/>
</Grid>
<Grid item className={classes.edit} md={true} sm={true} xs={true}>
{auth.loggedIn & auth.exp > now && auth.id == profile.id
&& <div>
{!edit
? <IconButton onClick={editFields}><EditIcon /></IconButton>
: <div>
<IconButton onClick={editFieldsApply} disabled={disabled}><CheckCircleIcon /></IconButton>
<IconButton onClick={editFieldsCancel}><CancelIcon /></IconButton>
</div>
}
</div>
}
</Grid>
</Grid>
</Paper>
<Grid container justify="center" style={{ marginTop: '5px' }}>
{error !== '' && <Typography className={classes.error}>{error}</Typography>}
{message !== '' && <Typography className={classes.message}>{message}</Typography>}
</Grid>
</Container>
)
}