Trying to change the isVegan object (nested boolean) with React Bootstrap checkbox and hooks. I can access the object without any issues (eg checkbox is checked if isVegan is true), but have been unable to modify the state. As you can see in the Redux dev tools (image link included), the isVegan object is passed through my state and is accessible. I have also used similar code for the other objects in the chef collection without any issues so believe the issue is either related to the checkbox or how the isVegan object is nested in the chef collection. (Lastly, I know some of the code below may be extra, I slimmed down my original file to simplify this example)
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;
Actions
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,
})
}
}
Reducers
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
}
}
Controller
// @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')
}
})
After much back and forth I believe the issue is with how the response "payload" is stored back in state by the reducer. The response object is a flat object with isVegan
at the root, but in state isVegan
is in a nested diets
array.
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),
})
The reducer takes the payload and also saves it directly to a chefInfo
property and overwriting any existing data.
export const chefUpdateProfileReducer = (state = { }, action) => {
switch(action.type) {
...
case CHEF_UPDATE_PROFILE_SUCCESS:
return { loading: false, success: true, chefInfo: action.payload }
...
}
}
Reducer should merge in response payload. In your redux screenshot I don't see a chefInfo
key so I'll write this to match the screenshot as closely as possible.
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),
},
};
...
}
}
Note: This is a best guess to state structures and types since your reducers appear to have a very minimally defined initial state, so this likely needs to be tweaked to fits your exact state structure.
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.