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.
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