Search code examples
reactjsformsredux-thunk

out of phase data in form value using useSelector


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:

table fragment

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


Solution

  • 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;