繁体   English   中英

使用 React Bootstrap 复选框和钩子更改布尔值

[英]Changing boolean value with React Bootstrap checkbox and hooks

尝试使用 React Bootstrap 复选框和钩子更改 isVegan 对象(嵌套布尔值)。 我可以毫无问题地访问该对象(例如,如果 isVegan 为 true,则选中复选框),但无法修改状态。 正如您在 Redux 开发工具(包括图像链接)中所见,isVegan 对象通过我的状态传递并且是可访问的。 我还对厨师集合中的其他对象使用了类似的代码,没有任何问题,因此相信问题要么与复选框有关,要么与 isVegan 对象如何嵌套在厨师集合中有关。 (最后,我知道下面的一些代码可能是额外的,我缩小了我的原始文件以简化这个例子)

import React, { useState, useEffect, setState } from 'react';
import { Form, Button, Row, Col, Tabs, Tab } from 'react-bootstrap';
import { LinkContainer } from 'react-router-bootstrap';
import { useDispatch, useSelector } from 'react-redux';
import { getChefDetails, updateChefProfile } from '../../actions/chefActions';
import { CHEF_UPDATE_PROFILE_RESET } from '../../constants/chefConstants';
import FormContainer from '../../components/FormContainer/FormContainer.component';

import './ProfileEditPage.styles.scss';

const ProfileEditPage = ({ location, history }) => {
  const [first_name, setFirstName] = useState('')
  const [last_name, setLastName] = useState('')
  const [username, setUsername] = useState('')
  const [email, setEmail] = useState('')
  const [password, setPassword] = useState('')
  const [confirmPassword, setConfirmPassword] = useState('')
  const [isVegan, setIsVegan] = useState('')
  const [bio, setBio] = useState('')
  const [message, setMessage] = useState(null)

  const dispatch = useDispatch()

  const chefDetails = useSelector(state => state.chefDetails)
  const { loading, error, chef } = chefDetails

  const chefLogin = useSelector(state => state.chefLogin)
  const { chefInfo } = chefLogin

  const chefUpdateProfile = useSelector(state => state.chefUpdateProfile)
  const { success } = chefUpdateProfile

  useEffect(() => {
    if(!chefInfo) {
      history.push('/login')
    } else {
      if(!chef || !chef.username || success) {
        dispatch({ type: CHEF_UPDATE_PROFILE_RESET })
        dispatch(getChefDetails('profile'))
      } else {
        setFirstName(chef.first_name)
        setLastName(chef.last_name)
        setUsername(chef.username)
        setEmail(chef.email)
        setBio(chef.bio)
        setIsVegan(chef.isVegan)
      }
    }
  }, [dispatch, history, chefInfo, chef, success])

  const submitHandler = (e) => {
    e.preventDefault()
    if (password !== confirmPassword) {
      setMessage('Passwords do not match')
    } else {
      dispatch(updateChefProfile({
        id: chef._id,
        first_name,
        last_name,
        username,
        email,
        password,
        bio,
        isVegan
      }))
    }
  }

  const [key, setKey] = useState('auth')

  //const isVegan = chef.diets[0].isVegan
  //const isVegetarian = chef.diets[0].isVegetarian

  console.log(isVegan)

  return (
    <FormContainer className="profileEditPage">
      <h1>Chef Profile</h1>
      <Form className='profileEditPageForm' onSubmit={submitHandler}>
        <Tabs id="profileEditPageTabs" activeKey={key} onSelect={(k) => setKey(k)}>
          <Tab eventKey='auth' title="Auth">
            <Form.Group controlId='first_name'>
              <Form.Label>First Name</Form.Label>
              <Form.Control
                type='text'
                placeholder='Enter your first name'
                value={first_name}
                onChange={(e) => setFirstName(e.target.value)}
                required
              >
              </Form.Control>
            </Form.Group>

            <Form.Group controlId='last_name'>
              <Form.Label>Last Name</Form.Label>
              <Form.Control
                type='text'
                placeholder='Enter your last name'
                value={last_name}
                onChange={(e) => setLastName(e.target.value)}
                required
              >
              </Form.Control>
            </Form.Group>

            <Form.Group controlId='username'>
              <Form.Label>Username</Form.Label>
              <Form.Control
                type='text'
                placeholder='Enter a username'
                value={username}
                onChange={(e) => setUsername(e.target.value)}
                required
              >
              </Form.Control>
              <Form.Text className='muted'>Your username will be public</Form.Text>
            </Form.Group>

            <Form.Group controlId='email'>
              <Form.Label>Email</Form.Label>
              <Form.Control
                type='email'
                placeholder='Enter your email'
                value={email}
                onChange={(e) => setEmail(e.target.value)}
                required
              >
              </Form.Control>
            </Form.Group>

            <Form.Group controlId='password'>
              <Form.Label>Password</Form.Label>
              <Form.Control
                type='password'
                placeholder='Enter your password'
                value={password}
                onChange={(e) => setPassword(e.target.value)}
              >
              </Form.Control>
            </Form.Group>

            <Form.Group controlId='confirmPassword'>
              <Form.Label>Confirm Password</Form.Label>
              <Form.Control
                type='password'
                placeholder='Confirm password'
                value={confirmPassword}
                onChange={(e) => setConfirmPassword(e.target.value)}
              >
              </Form.Control>
            </Form.Group>
          </Tab>
          <Tab eventKey='chef-detail' title="Chef Detail">
            <Form.Group controlId='isVegan'>
              <Form.Check
                type='checkbox'
                label='Vegan?'
                checked={isVegan}
                value={isVegan}
                onChange={(e) => setIsVegan(e.target.checked)}
              />
            </Form.Group>

            <Form.Group controlId='bio'>
              <Form.Label>Chef Bio</Form.Label>
              <Form.Control
                as='textarea'
                rows='5'
                maxLength='240'
                placeholder='Enter bio'
                value={bio}
                onChange={(e) => setBio(e.target.value)}
              >
              </Form.Control>
              <Form.Text className='muted'>Your bio will be public</Form.Text>
            </Form.Group>
          </Tab>
        </Tabs>

        <Button type='submit' variant='primary'>
          Update
        </Button>
      </Form>


    </FormContainer>

  )
}

export default ProfileEditPage;

行动

export const getChefDetails = (id) => async (dispatch, getState) => {
  try {
    dispatch({
      type: CHEF_DETAILS_REQUEST
    })

    const { chefLogin: { chefInfo} } = getState()

    const config = {
      headers: {
        'Content-Type': 'application/json',
        Authorization: `Bearer ${chefInfo.token}`
      }
    }

    const { data } = await axios.get(
      `/api/chefs/${id}`,
      config
    )

    dispatch({
      type: CHEF_DETAILS_SUCCESS,
      payload: data
    })

  } catch (error) {
    dispatch({
      type: CHEF_DETAILS_FAILURE,
      payload:
        error.response && error.response.data.message
          ? error.response.data.message
          : error.message,
    })
  }
}

export const updateChefProfile = (chef) => async (dispatch, getState) => {
  try {
    dispatch({
      type: CHEF_UPDATE_PROFILE_REQUEST
    })

    const { chefLogin: { chefInfo } } = getState()

    const config = {
      headers: {
        'Content-Type': 'application/json',
        Authorization: `Bearer ${chefInfo.token}`
      }
    }

    const { data } = await axios.put(
      `/api/chefs/profile`,
      chef,
      config
    )

    dispatch({
      type: CHEF_UPDATE_PROFILE_SUCCESS,
      payload: data
    })

    dispatch({
      type: CHEF_LOGIN_SUCCESS,
      payload: data
    })

    localStorage.setItem('chefInfo', JSON.stringify(data))

  } catch (error) {
    dispatch({
      type: CHEF_UPDATE_PROFILE_FAILURE,
      payload:
        error.response && error.response.data.message
          ? error.response.data.message
          : error.message,
    })
  }
}

减速机

export const chefDetailsReducer = (state = { chef: { } }, action) => {
  switch(action.type) {
    case CHEF_DETAILS_REQUEST:
      return { ...state, loading: true }
    case CHEF_DETAILS_SUCCESS:
      return { loading: false, chef: action.payload }
    case CHEF_DETAILS_FAILURE:
      return { loading: false, error: action.payload }
    case CHEF_DETAILS_RESET:
      return {
        chef: {}
      }
    default:
      return state
  }
}

export const chefUpdateProfileReducer = (state = { }, action) => {
  switch(action.type) {
    case CHEF_UPDATE_PROFILE_REQUEST:
      return { loading: true }
    case CHEF_UPDATE_PROFILE_SUCCESS:
      return { loading: false, success: true, chefInfo: action.payload }
    case CHEF_UPDATE_PROFILE_FAILURE:
      return { loading: false, error: action.payload }
    case CHEF_UPDATE_PROFILE_RESET:
      return { }
    default:
      return state
  }
}

控制器

// @description Get chef profile
// @route GET /api/chefs/profile
// @access Private
const getChefProfile = asyncHandler(async (req, res) => {
  const chef = await Chef.findById(req.chef._id)

  if(chef) {
    res.json({
      _id: chef._id,
      first_name: chef.first_name,
      last_name: chef.last_name,
      username: chef.username,
      email: chef.email,
      bio: chef.bio,
      isVegan: chef.isVegan
    })
  } else {
    res.status(404)
    throw new Error('Chef not found')
  }
})

// @description Update chef profile
// @route PUT /api/chefs/profile
// @access Private
const updateChefProfile = asyncHandler(async (req, res) => {
  const chef = await Chef.findById(req.chef._id)

  if(chef) {
    chef.first_name = req.body.first_name || chef.first_name
    chef.last_name = req.body.last_name || chef.last_name
    chef.username = req.body.username || chef.username
    chef.email = req.body.email || chef.email
    chef.bio = req.body.bio || chef.bio
    chef.isVegan = req.body.isVegan || chef.isVegan

    if (req.body.password) {
      chef.password = req.body.password
    }

    const updatedChef = await chef.save()

    res.json({
      _id: updatedChef._id,
      first_name: updatedChef.first_name,
      last_name: updatedChef.last_name,
      username: updatedChef.username,
      email: updatedChef.email,
      bio: updatedChef.bio,
      isVegan: updatedChef.isVegan,
      token: generateToken(updatedChef._id),
    })

  } else {
    res.status(404)
    throw new Error('Chef not found')
  }
})

问题

经过多次来回,我认为问题在于reducer如何将响应“有效负载”存储回状态。 响应对象是一个以isVegan为根的平面对象,但状态isVegan位于嵌套的diets数组中。

res.json({
  _id: updatedChef._id,
  first_name: updatedChef.first_name,
  last_name: updatedChef.last_name,
  username: updatedChef.username,
  email: updatedChef.email,
  bio: updatedChef.bio,
  isVegan: updatedChef.isVegan,
  token: generateToken(updatedChef._id),
})

减速器获取有效负载并将其直接保存到chefInfo属性并覆盖任何现有数据。

export const chefUpdateProfileReducer = (state = { }, action) => {
  switch(action.type) {
    ...

    case CHEF_UPDATE_PROFILE_SUCCESS:
      return { loading: false, success: true, chefInfo: action.payload }

    ...
  }
}

解决方案

Reducer 应该在响应负载中合并。 在您的 redux 屏幕截图中,我没有看到chefInfo键,因此我将编写此代码以尽可能匹配屏幕截图。

export const chefUpdateProfileReducer = (state = { }, action) => {
  switch(action.type) {
    ...

    case CHEF_UPDATE_PROFILE_SUCCESS:
      const {
        _id,
        isVegan,
        token,
        ...chefDetails // i.e. first & last name, username, email, bio
      } = action.payload;

      return {
        ...state, // <-- shallow copy state
        loading: false,
        success: true,
        chef: {
          ...state.chef, // <-- shallow copy existing chef details
          ...chefDetails, // shallow copy new chef details
          diets: state.chef.diets.map(diet => diet._id === _id ? { // <-- map existing state
            ...diet, // <-- shallow copy diet object
            isVegan // <-- overwrite isVegan property
          } : diet),
        },
      };

    ...
  }
}

注意:这是对状态结构和类型的最佳猜测,因为您的 reducer 似乎具有非常低定义的初始状态,因此可能需要对其进行调整以适合您的确切状态结构。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM