good evening
I am making an excercise, just a simple CRUD fetching data using redux-thunk. I want to modified a selected item and show field data and then manipulate the values. For that I have a custom hook called useForm:
import { useState } from "react"
export const useForm = (initialState = {}) => {
const [values, setValues] = useState(initialState)
const reset = () => {
setValues(initialState)
}
const handleInputChange = ({ target }) => {
setValues({
...values,
[target.name]: target.value
})
}
return [values, handleInputChange, reset]
and this my UpdateItem component (I know that to refactor is a must):
import axios from 'axios'
import React from 'react'
import { useForm } from '../hooks/useForm'
import { baseURL } from '../configuration/baseURL'
import { useDispatch, useSelector } from 'react-redux';
import { types } from '../types/types';
import { useHistory } from 'react-router-dom'
import Swal from 'sweetalert2'
export default function UpdateItem() {
const history = useHistory()
const dispatch = useDispatch()
const { selected } = useSelector(state => state.axiosDataReducer)
const { data } = useSelector(state => state.axiosDataReducer)
const id = selected?.id
const selectedItemtoModify = data?.filter(x => x?.id === id)
console.log(data !== undefined && selectedItemtoModify)
console.log(selectedItemtoModify[0]?.name)
console.log(selectedItemtoModify[0]?.cost)
console.log(selectedItemtoModify[0]?.department[0].name)
console.log(selectedItemtoModify[0]?.department[0].identification)
console.log(selectedItemtoModify[0]?.category[0].name)
console.log(selectedItemtoModify[0]?.category[0].id)
const name = selectedItemtoModify[0]?.name
const cost = selectedItemtoModify[0]?.cost
const departmentName = selectedItemtoModify[0]?.department[0].name
const departmentIdentification = selectedItemtoModify[0]?.department[0].identification
const categoryName = selectedItemtoModify[0]?.category[0].name
const categoryId = selectedItemtoModify[0]?.category[0].id
const [formValues, handleInputChange] = useForm({
newName: name,
newCost: cost,
newDepartmentName: departmentName,
newDepartmentIdentification: departmentIdentification,
newCategoryName: categoryName,
newCategoryId: categoryId
})
const {
newName,
newCost,
newDepartmentName,
newDepartmentIdentification,
newCategoryName,
newCategoryId } = formValues
const handleUpdateItem = async (e) => {
e.preventDefault()
try {
await axios.put(`${baseURL}${id}`, {
"id": +id,
"name": newName,
"cost": +newCost,
"department": [
{
"name": newDepartmentName,
"identification": newDepartmentIdentification
}
],
"category": [
{
"name": newCategoryName,
"id": +newCategoryId
}
]
})
const modified = await axios.get(`${baseURL}${id}`)
const { data } = modified
dispatch({
type: types.modify,
modifiedItem: data
});
Swal.fire({
icon: 'success',
title: 'Your item has been modified',
showConfirmButton: false,
timer: 1500
})
setTimeout(() => {
history.push('/')
}, 1500);
} catch (error) {
Swal.fire({
icon: 'error',
title: 'Oops...',
text: 'Something went wrong!',
footer: 'Unable to modify item, who passes the id?'
})
return dispatch({
type: types.error,
msg: 'Unable to modify item'
})
}
}
return (
<div className='container mb-5 pb-3 bg-light'>
<form className='mt-3' onSubmit={handleUpdateItem}>
<input
className='input mb-1 p-3'
type='text'
title='Name'
placeholder='Name'
name='newName'
autoComplete='off'
value={newName}
onChange={handleInputChange} />
<input
className='input mb-1 p-3'
type='text'
title='Cost'
placeholder='Cost'
name='newCost'
autoComplete='off'
value={newCost}
onChange={handleInputChange} />
<input
className='input mb-1 p-3'
type='text'
title='Department Name'
placeholder='Department Name'
name='newDepartmentName'
autoComplete='off'
value={newDepartmentName}
onChange={handleInputChange} />
<input
className='input mb-1 p-3'
type='text'
title='Department Identification'
placeholder='Department Identification'
name='newDepartmentIdentification'
autoComplete='off'
value={newDepartmentIdentification}
onChange={handleInputChange} />
<input
className='input mb-1 p-3'
type='text'
title='Category Name'
placeholder='Category Name'
name='newCategoryName'
autoComplete='off'
value={newCategoryName}
onChange={handleInputChange} />
<input
className='input mb-1 p-3'
type='text'
title='Category Id'
placeholder='Category Id'
name='newCategoryId'
autoComplete='off'
value={newCategoryId}
onChange={handleInputChange} />
<button className='btn btn-success ' type=' submit'>
Modify Item
</button>
</form>
</div>
)
}
and this is the reducer:
import { types } from "../types/types";
const initialState = {
data: null,
selected: null,
deleted: '',
created: null,
modified: null,
error: ''
}
export const axiosDataReducer = (state = initialState, action) => {
switch (action.type) {
case types.get:
return {
...state,
data: action.data
}
case types.selected:
return {
...state,
selected: action.selectedItem
}
case types.delete:
return {
...state,
data: state.data.filter(item => item.id !== action.deletedItem.id),
deleted: action.deletedItem
}
case types.created:
return {
...state,
created: action.createdItem
}
case types.modified:
return {
...state,
modified: action.modifiedItem
}
case types.error:
return {
...state,
error: action.msg
}
default:
return state;
}
}
everithing is ok, but, when I select an option from a table:
It doesn't work as expected because there is a delay fetching the data and the form has no value on it at first, BUT after the value is obtained but not showing in the form: first nothing then updates
I can introduce some other values, and submit those values and works perfect
and then when I select some other field it shows the previous value instead of the actual selected by me value: I've selected Raspberries and I obtain Cherries as console.log show...and it keeps going
What about this out of phase data obtaining? @Shyam https://stackoverflow.com/users/8884388/shyam?
EDITED I've made this change in my UpdateItem.js component, the result still the same: How can I wait for selected to be fully loaded??
const { selected } = useSelector(state => state.axiosDataReducer)
console.log(selected)
console.log(selected?.name)
console.log(selected?.cost)
console.log(selected?.department[0].name)
console.log(selected?.department[0].identification)
console.log(selected?.category[0].name)
console.log(selected?.category[0].id)
const id = selected?.id
const name = selected?.name
const cost = selected?.cost
const departmentName = selected?.department[0].name
const departmentIdentification = selected?.department[0].identification
const categoryName = selected?.category[0].name
const categoryId = selected?.category[0].id
EDITING WITH MY SOLUTION what i did:
I split component in two: UpdateItem.js
import axios from 'axios'
import React from 'react'
import { useForm } from '../hooks/useForm'
import { baseURL } from '../configuration/baseURL'
import { useDispatch } from 'react-redux';
import { types } from '../types/types';
import { useHistory } from 'react-router-dom'
import Swal from 'sweetalert2'
export default function UpdateItem({ id, name, cost, departmentName, departmentIdentification, categoryName, categoryId }) {
const history = useHistory()
const dispatch = useDispatch()
const [formValues, handleInputChange] = useForm({
newName: name,
newCost: cost,
newDepartmentName: departmentName,
newDepartmentIdentification: departmentIdentification,
newCategoryName: categoryName,
newCategoryId: categoryId
})
const {
newName,
newCost,
newDepartmentName,
newDepartmentIdentification,
newCategoryName,
newCategoryId } = formValues
const handleUpdateItem = async (e) => {
e.preventDefault()
try {
await axios.put(`${baseURL}${id}`, {
"id": +id,
"name": newName,
"cost": +newCost,
"department": [
{
"name": newDepartmentName,
"identification": newDepartmentIdentification
}
],
"category": [
{
"name": newCategoryName,
"id": +newCategoryId
}
]
})
const modified = await axios.get(`${baseURL}${id}`)
const { selected } = modified
dispatch({
type: types.modify,
modifiedItem: selected
});
Swal.fire({
icon: 'success',
title: 'Your item has been modified',
showConfirmButton: false,
timer: 1500
})
setTimeout(() => {
history.push('/')
}, 1500);
} catch (error) {
Swal.fire({
icon: 'error',
title: 'Oops...',
text: 'Something went wrong!',
footer: 'Unable to modify item, who passes the id?'
})
return dispatch({
type: types.error,
msg: 'Unable to modify item'
})
}
}
return (
<div className='container mt-5 mb-5 pb-3 bg-light'>
<form className='mt-3' onSubmit={handleUpdateItem}>
<input
className='input mb-1 p-3'
type='text'
title='Name'
placeholder='Name'
name='newName'
autoComplete='off'
value={newName}
onChange={handleInputChange} />
<input
className='input mb-1 p-3'
type='text'
title='Cost'
placeholder='Cost'
name='newCost'
autoComplete='off'
value={newCost}
onChange={handleInputChange} />
<input
className='input mb-1 p-3'
type='text'
title='Department Name'
placeholder='Department Name'
name='newDepartmentName'
autoComplete='off'
value={newDepartmentName}
onChange={handleInputChange} />
<input
className='input mb-1 p-3'
type='text'
title='Department Identification'
placeholder='Department Identification'
name='newDepartmentIdentification'
autoComplete='off'
value={newDepartmentIdentification}
onChange={handleInputChange} />
<input
className='input mb-1 p-3'
type='text'
title='Category Name'
placeholder='Category Name'
name='newCategoryName'
autoComplete='off'
value={newCategoryName}
onChange={handleInputChange} />
<input
className='input mb-1 p-3'
type='text'
title='Category Id'
placeholder='Category Id'
name='newCategoryId'
autoComplete='off'
value={newCategoryId}
onChange={handleInputChange} />
<button className='btn btn-success ' type=' submit'>
Modify Item
</button>
</form>
</div>
)
}
and the other: ConditionalRenderUpdateItem
import React from 'react'
import { useSelector } from 'react-redux';
import UpdateItem from '../screen/UpdateItem';
export default function ConditionalRenderUpdateItem() {
const { selected } = useSelector(state => state.axiosDataReducer)
console.log(selected)
console.log(selected?.name)
console.log(selected?.cost)
console.log(selected?.department[0].name)
console.log(selected?.department[0].identification)
console.log(selected?.category[0].name)
console.log(selected?.category[0].id)
const id = selected?.id
const name = selected?.name
const cost = selected?.cost
const departmentName = selected?.department[0].name
const departmentIdentification = selected?.department[0].identification
const categoryName = selected?.category[0].name
const categoryId = selected?.category[0].id
return (
<div>
{(selected !== null) &&
<UpdateItem
id={id}
name={name}
cost={cost}
departmentName={departmentName}
departmentIdentification={departmentIdentification}
categoryName={categoryName}
categoryId={categoryId}
/>}
</div>
)
}
and this is it
EDITING AGAIN DUE TO NEW PROBLEM
As I explained in the comment below, now I have the same issue, populating the form values with previous data...to fix this as suggested by @Drew Reese I implemented a useEffect this way after extracting also the reset function from my custom hook:
const [formValues, handleInputChange, reset] = useForm({
newName: name,
newCost: cost,
newDepartmentName: departmentName,
newDepartmentIdentification: departmentIdentification,
newCategoryName: categoryName,
newCategoryId: categoryId
})
useEffect(() => {
reset()
}, [id])
in UpdateItem.js
and this is the custom hook I am using, created by tehacher Fernando Herrera from Udemy:
useForm
import { useState } from "react"
export const useForm = (initialState = {}) => {
const [values, setValues] = useState(initialState)
const reset = () => {
setValues(initialState)
}
const handleInputChange = ({ target }) => {
setValues({
...values,
[target.name]: target.value
})
}
return [values, handleInputChange, reset]
}
EDITED AS FINAL PIECE OF ANSWER
its also needed to include all the form values as dependencies of the useEffect:
useEffect(() => {
reset()
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [id, name, cost, departmentName, departmentIdentification, categoryName, categoryId])
Your question and comments are a bit unclear, but it sort of sounds like you need to reset the form data when you've selected a new value and the state.axiosDataReducer
state has updated.
When it's updated in your redux store the component will rerender and you'll have the latest selected value. There's nothing to wait for, React and redux will provide the updated values to the component.
Memoize the form's initial data values and pass to the hook, and use an useEffect
hook with a dependency on the selected
state to call the useForm
hook's reset
function. When the selected
state updates the useEffect
hook will trigger its callback and the reset
function will should be invoked and reset the form fields with the latest initialData
value.
export default function UpdateItem() {
...
const { data, selected } = useSelector(state => state.axiosDataReducer);
const { id } = selected;
const initialData = React.useMemo(() => {
const selectedItemtoModify = data?.filter(x => x?.id === id);
return {
newName: selectedItemtoModify[0]?.name,
newCost: selectedItemtoModify[0]?.cost,
newDepartmentName: selectedItemtoModify[0]?.department[0].name,
newDepartmentIdentification: selectedItemtoModify[0]?.department[0].identification,
newCategoryName: selectedItemtoModify[0]?.category[0].name,
newCategoryId: selectedItemtoModify[0]?.category[0].id
};
}, [selected]);
const [formValues, handleInputChange, reset] = useForm(initialData);
useEffect(() => {
reset();
}, [selected]);
const {
newName,
newCost,
newDepartmentName,
newDepartmentIdentification,
newCategoryName,
newCategoryId,
} = formValues;