Search code examples
reactjsreact-hooksreact-bootstrapreact-tablereact-modal

React-bootstrap table not refreshed after axios call


I have a table that workig perfectly on page load, that page also include a modal component to update a line of the table, I'm using a custom hook to makes API calls and it works well.

The problem is after a line modification, I dont find the way to refresh the table. The DB get updated, the Toast shows the update success, the call for the new list is fired and got the new list in DBHook with console.log('Result', result) but, the useEffect with heats dependency never get fired.

If I go to another page and come back or do F5, the list refresh properly.

dbHook :

export const useAxios = (url, method, data) => {
    const [responseData, setResponseData] = useState(undefined)
    const [status, setStatus] = useState(undefined)
    const [error, setError] = useState('')
    const [loading, setLoading] = useState(true)

    const params = {
        method: method,
        url: url,
        headers: { accept: '*/*' },
        data: data,
    }

    const fetchData = async (params) => {
        //console.log('in axios', method, url, data)
        try {
            const result = await axios.request(params)
            //console.log('Result', result)
            setResponseData(result.data)
            setStatus(result.status)
            //console.log('resultStatus', result.status)
        } catch (error) {
            setError(error.response)
            //console.log('Error', error)
        } finally {
            setLoading(false)
            //console.log('axios finished', url)
        }
    }

    useEffect(() => {
        fetchData(params)
        // eslint-disable-next-line react-hooks/exhaustive-deps
    }, [url])
    return { responseData, error, loading, status }
}

Modal :

export default function HeatModal({
    isShow,
    triggerModal,
    activeHeat,
    setActiveHeat,
    saveHeat,
}) {
    function save() {
        saveHeat()
        triggerModal()
    }

    return (
        <>
            <Modal show={isShow} onHide={triggerModal}>
                <Modal.Header>
                    <Modal.Title>{activeHeat.name}</Modal.Title>
                </Modal.Header>
                <Modal.Body>
                    <Form>
                        <FormGroup as={Row}>
                            <FormLabel column>
                                Nouvelle date de chaleur
                            </FormLabel>
                            <Col>
                                <MyDatePicker
                                    field={'date'}
                                    value={activeHeat.date}
                                    setter={setActiveHeat}
                                />
                            </Col>
                        </FormGroup>
                    </Form>
                </Modal.Body>
                <Modal.Footer as={NavContainer}>
                    <Button variant="outline-success" onClick={triggerModal}>
                        Annuler
                    </Button>

                    <Button variant="success" onClick={save}>
                        Confirmer
                    </Button>
                </Modal.Footer>
            </Modal>
        </>
    )
}

List :

const HeatList = () => {
const [heatsUrl, setHeatsUrl] = useState('/heats')
    const heat = {
        idHeat: -1,
        name: '',
        date: new Date(),
        idDog: -1,
    }
    const [heatsWithName, setHeatsWithNames] = useState([])
    const [heatPerPage, setHeatPerPage] = useState(10)
    const [firstHeat, setFirstHeat] = useState(0)
    const [showModal, setShowModal] = useState(false)
    const [activeHeat, setActiveHeat] = useState(heat)
    const [editUrl, setEditUrl] = useState('')
    const {
        responseData: heats,
        loading: heatsIsLoading,
        error: heatsIsError,
    } = useAxios(heatsUrl, 'GET', null)
    const { responseData: dogs, loading: dogsIsLoading } = useAxios(
        '/dogs',
        'GET',
        null
    )
    const { responseData: editResponse, loading: editLoading } = useAxios(
        editUrl,
        'PUT',
        activeHeat
    )
    useEffect(() => {
        console.log('activeHeat', activeHeat.idHeat)
        editResponse && console.log('editResponse', editResponse.idHeat)
        if (editResponse && activeHeat.idHeat === editResponse.idHeat) {
            console.log('in use Effect2')
            setActiveHeat(editResponse)
            toastSuccess(SAVE_SUCCESS)
            setHeatsUrl('/heats')
        } else if (editResponse === '') {
            console.log('in use Effect3')
            toastError(SAVE_ERROR)
        }
        // eslint-disable-next-line react-hooks/exhaustive-deps
    }, [editResponse])
    useEffect(() => {
        console.log('in new heats array')
        let newHeatsArr = []
        if (heats && dogs) {
            console.log('in if', heats)
            heats.map((h) => newHeatsArr.push(buildHeat(h, true)))
        }
        setHeatsWithNames(newHeatsArr)
        console.log('newHeatsArray', newHeatsArr)
        // eslint-disable-next-line react-hooks/exhaustive-deps
    }, [heats, dogs])
    const { items, requestSort, sortConfig } = useSortableData(heatsWithName)
    const getClassNamesFor = (name) => {
        if (!sortConfig) {
            return
        }
        return sortConfig.key === name ? sortConfig.direction : undefined
    }
    const triggerModal = () => {
        setShowModal((prev) => !prev)
    }
    function buildHeat(origHeat, withName) {
        const newHeat = {
            idHeat: origHeat.idHeat,
            date: origHeat.date,
            idDog: origHeat.idDog,
        }
        if (withName) {
            let dogName =
                dogs && dogs.find((d) => d.idDog === origHeat.idDog).name
            Object.assign(newHeat, dogName && { name: dogName })
        }
        return newHeat
    }

    const modifyHeat = (h) => {
        setActiveHeat(h)
        triggerModal()
    }
    const saveHeat = () => {
        console.log('aH', activeHeat)
        setEditUrl('/heat/' + activeHeat.idHeat)
    }
    return (
        <Container>
            {heatsIsLoading || dogsIsLoading ? (
                <Spinner animation="border" variant="primary" />
            ) : (
                <div className="table-container">
                    <h1 className="text-center">Liste des chaleurs</h1>
                    <HeatModal
                        isShow={showModal}
                        triggerModal={triggerModal}
                        activeHeat={activeHeat}
                        setActiveHeat={setActiveHeat}
                        saveHeat={saveHeat}
                    />
                    <Paginator
                        items={items}
                        dogPerPage={heatPerPage}
                        setDogPerPage={setHeatPerPage}
                        firstDog={firstHeat}
                        setFirstDog={setFirstHeat}
                    />
                    <Table
                        striped
                        bordered
                        hover
                        responsive
                        className="table-fixed"
                    >
                        <thead>
                            <tr>
                                <th>
                                    <button
                                        type="buttton"
                                        onClick={() => requestSort('name')}
                                        className={getClassNamesFor('name')}
                                    >
                                        Nom
                                    </button>
                                </th>
                                <th>
                                    <button
                                        type="buttton"
                                        onClick={() => requestSort('date')}
                                        className={getClassNamesFor('date')}
                                    >
                                        Date dernière chaleur
                                    </button>
                                </th>
                                <th>Actions</th>
                            </tr>
                        </thead>
                        <tbody>
                            {items &&
                                items
                                    .slice(
                                        firstHeat,
                                        Number(firstHeat) + Number(heatPerPage)
                                    )
                                    .map((heat) => (
                                        <tr key={heat.idHeat}>
                                            <td>{heat.name}</td>
                                            <td>
                                                {formatDate().format(
                                                    new Date(heat.date)
                                                )}
                                            </td>
                                            <td>
                                                <Button className="noBackground noBorder">
                                                    <ActionLogoStyle
                                                        src={AddLogo}
                                                    />
                                                </Button>
                                                <Button
                                                    className="noBackground noBorder"
                                                    onClick={() =>
                                                        modifyHeat(heat)
                                                    }
                                                >
                                                    <ActionLogoStyle
                                                        src={ModifyLogo}
                                                    />
                                                </Button>
                                            </td>
                                        </tr>
                                    ))}
                        </tbody>
                    </Table>
                </div>
            )}
        </Container>
    )
}

export default HeatList

Thanks for your help.


Solution

  • Thanks Brendan,

    This is a real good post that helped me find the solution.

    Final hooks :

    const dataFetchReducer = (state, action) => {
        switch (action.type) {
            case 'FETCH_INIT':
                return {
                    ...state,
                    isLoading: true,
                    isError: false,
                }
            case 'FETCH_SUCCESS':
                return {
                    ...state,
                    isLoading: false,
                    isError: false,
                    data: action.payload,
                }
            case 'FETCH_FAILURE':
                return {
                    ...state,
                    isLoading: false,
                    isError: true,
                }
            default:
                throw new Error()
        }
    }
    
    export const useAxios = (initialUrl, method, initialData) => {
        const [url, setUrl] = useState(initialUrl)
        const [state, dispatch] = useReducer(dataFetchReducer, {
            isLoading: false,
            isError: false,
            data: initialData,
        })
    
        useEffect(() => {
            let didCancel = false
            const params = {
                method: method,
                url: url,
                headers: { accept: '*/*' },
                data: initialData,
            }
    
            const fetchData = async (params) => {
                dispatch({ type: 'FETCH_INIT' })
                console.log('in axios', method, url, params)
                try {
                    const result = await axios(params)
                    // console.log('result', result)
                    if (!didCancel) {
                        dispatch({ type: 'FETCH_SUCCESS', payload: result.data })
                    }
                } catch (error) {
                    if (!didCancel) {
                        dispatch({ type: 'FETCH_FAILURE' })
                    }
                }
            }
            url && fetchData(params)
            return () => {
                didCancel = true
            }
        }, [initialData, method, url])
        return [state, setUrl]
    }

    final Modal:

    export default function HeatModal({
        isShow,
        triggerModal,
        activeHeat,
        setActiveHeat,
        saveHeat,
    }) {
        function save() {
            saveHeat()
            triggerModal()
        }
    
        return (
            <>
                <Modal show={isShow} onHide={triggerModal}>
                    <Modal.Header>
                        <Modal.Title>{activeHeat.name}</Modal.Title>
                    </Modal.Header>
                    <Modal.Body>
                        <Form>
                            <FormGroup as={Row}>
                                <FormLabel column>
                                    Nouvelle date de chaleur
                                </FormLabel>
                                <Col>
                                    <MyDatePicker
                                        field={'date'}
                                        value={activeHeat.date}
                                        setter={setActiveHeat}
                                    />
                                </Col>
                            </FormGroup>
                        </Form>
                    </Modal.Body>
                    <Modal.Footer as={NavContainer}>
                        <Button variant="outline-success" onClick={triggerModal}>
                            Annuler
                        </Button>
    
                        <Button variant="success" onClick={save}>
                            Confirmer
                        </Button>
                    </Modal.Footer>
                </Modal>
            </>
        )
    }

    final List:

    const HeatList = () => {
        const heat = {
            idHeat: -1,
            name: '',
            date: new Date(),
            idDog: -1,
        }
        const [heatsWithName, setHeatsWithNames] = useState([])
        const [heatPerPage, setHeatPerPage] = useState(10)
        const [firstHeat, setFirstHeat] = useState(0)
        const [showModal, setShowModal] = useState(false)
        const [activeHeat, setActiveHeat] = useState(heat)
        const [heatToSave, setHeatToSave] = useState(undefined)
        const [{ data: dogs, isLoading: dogsIsLoading }] = useAxios(
            '/dogs',
            'GET',
            null
        )
        const [{ data: editResponse, isLoading: editLoading }, setEditUrl] =
            useAxios('', 'PUT', heatToSave)
        const [{ data: heats, isLoading: heatsIsLoading }] = useAxios(
            '/heats',
            'GET',
            editResponse
        )
        useEffect(() => {
            if (
                editResponse &&
                activeHeat.idHeat === editResponse.idHeat &&
                editResponse > 0
            ) {
                console.log('in use Effect2')
                toastSuccess(SAVE_SUCCESS)
            } else if (editResponse === '') {
                console.log('in use Effect3')
                toastError(SAVE_ERROR)
            }
        }, [activeHeat.idHeat, editResponse])
        useEffect(() => {
            console.log('in new heats array', heats, 'dogs', dogs)
            let newHeatsArr = []
            let dogList = []
            if (heats && dogs && Array.isArray(heats)) {
                dogList = dogs
                    .filter(({ gender }) => gender === 'Femelle')
                    .filter(({ status }) => status === 'Élevage')
                    .filter(({ state }) => state === 'Vivant')
    
                // console.log('in if', dogList)
                dogList.map((d) =>
                    newHeatsArr.push({
                        idDog: d.idDog,
                        name: d.name,
                        idHeat: heats.find((h) => d.idDog === h.idDog)
                            ? heats.find((h) => d.idDog === h.idDog).idHeat
                            : -1,
                        date: heats.find((h) => d.idDog === h.idDog)
                            ? heats.find((h) => d.idDog === h.idDog).date
                            : 'Jamais',
                    })
                )
            }
            setHeatsWithNames(newHeatsArr)
            console.log('newHeatsArray', newHeatsArr)
        }, [heats, dogs])
        const { items, requestSort, sortConfig } = useSortableData(heatsWithName)
        const getClassNamesFor = (name) => {
            if (!sortConfig) {
                return
            }
            return sortConfig.key === name ? sortConfig.direction : undefined
        }
        const triggerModal = () => {
            setShowModal((prev) => !prev)
        }
        const modifyHeat = (h) => {
            setActiveHeat(h)
            triggerModal()
        }
        const saveHeat = () => {
            console.log('aH', activeHeat)
            setEditUrl('/heat/' + activeHeat.idHeat)
            setHeatToSave(activeHeat)
        }
        return (
            <Container>
                {heatsIsLoading || dogsIsLoading ? (
                    <Spinner animation="border" variant="primary" />
                ) : (
                    <div className="table-container">
                        <h1 className="text-center">Liste des chaleurs</h1>
                        <HeatModal
                            isShow={showModal}
                            triggerModal={triggerModal}
                            activeHeat={activeHeat}
                            setActiveHeat={setActiveHeat}
                            saveHeat={saveHeat}
                        />
                        <Paginator
                            items={items}
                            dogPerPage={heatPerPage}
                            setDogPerPage={setHeatPerPage}
                            firstDog={firstHeat}
                            setFirstDog={setFirstHeat}
                        />
                        <Table
                            striped
                            bordered
                            hover
                            responsive
                            className="table-fixed"
                        >
                            <thead>
                                <tr>
                                    <th>
                                        <button
                                            type="buttton"
                                            onClick={() => requestSort('name')}
                                            className={getClassNamesFor('name')}
                                        >
                                            Nom
                                        </button>
                                    </th>
                                    <th>
                                        <button
                                            type="buttton"
                                            onClick={() => requestSort('date')}
                                            className={getClassNamesFor('date')}
                                        >
                                            Date dernière chaleur
                                        </button>
                                    </th>
                                    <th>Actions</th>
                                </tr>
                            </thead>
                            <tbody>
                                {items &&
                                    items
                                        .slice(
                                            firstHeat,
                                            Number(firstHeat) + Number(heatPerPage)
                                        )
                                        .map((heat) => (
                                            <tr key={heat.idDog}>
                                                <td>{heat.name}</td>
                                                <td>
                                                    {heat.date === 'Jamais'
                                                        ? 'Jamais'
                                                        : formatDate().format(
                                                              new Date(heat.date)
                                                          )}
                                                </td>
                                                <td>
                                                    <Button className="noBackground noBorder">
                                                        <ActionLogoStyle
                                                            src={AddLogo}
                                                        />
                                                    </Button>
                                                    <Button
                                                        className="noBackground noBorder"
                                                        disabled={
                                                            heat.date === 'Jamais'
                                                        }
                                                        onClick={() =>
                                                            modifyHeat(heat)
                                                        }
                                                    >
                                                        <ActionLogoStyle
                                                            src={ModifyLogo}
                                                        />
                                                    </Button>
                                                </td>
                                            </tr>
                                        ))}
                            </tbody>
                        </Table>
                    </div>
                )}
            </Container>
        )
    }
    
    export default HeatList