首页 > 解决方案 > React useState 显示为空

问题描述

我试图将我的类组件重构为带有钩子的功能,但在初始状态声明方面遇到了困难。

代码如下:

export default function Profile(){ 
const [profile, setProfile] = React.useState({
 firstname: '',
 lastname: ''
})

 return (
  <SomeComponent value={profile.firstname} />
)

}

错误是“无法读取属性 'firstname' of null”

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

}

标签: reactjsnulluse-state

解决方案


推荐阅读