I'll try to be as brief as I can since my doubt is quite big.
What I'm trying to do: I'm creating a simple app that reads a JSON file stored locally (%APPDATA% folder, but it doesn't matter for now) that contains an array of objects called TCodes:
[
{
"code": "TCODE1",
"description": "TCode 1",
"keywords": "tcode1"
}
]
TCodes are then mapped and showed inside a MUI List
in a custom component called TCodesList
, placed inside the Home
component. Above the TCodesList
there is a Toolbar
custom component that wraps a input
field and an IconButton
that opens a MUI Dialog
that lets the user create a new TCode and store it in the JSON file.
Reading and writing operations are done with the fs
module, I will omit how it is done since it is not relevant. It's enough to know that I do a fetch
request to the /api/tcodes
endpoint that do different things based on the request verb (GET
or POST
).
Problem: When I create a new TCode, it is written inside the JSON file, but the TCodesList does not get updated.
Here is the index.js
file where the TCodes are read from the file through the useSWR
hook:
//imports ...
function useTCodes() {
const fetcher = (...args) => fetch(...args).then((res) => res.json());
const { data, error, isLoading } = useSWR('/api/tcodes', fetcher);
return {
data: data,
isLoading: isLoading,
isError: error
}
}
export default function Home() {
const { data } = useTCodes();
return (
<div>
<Toolbar data={data} />
<div className={styles.tcodes_container}>
<TCodesList data={data}/>
</div>
</div>
);
};
The TCodesList
component just maps the data passed as a prop and renders each ListItem
:
export default function TCodesList({ data }) {
const filtered = data?.filter((tCode) => {
return tCode.code.toLowerCase().includes(''.toLowerCase());
});
return (
<List>
{
filtered && filtered.map((tCode, index) => (
<div key={index}>
{index != 0 && <Divider />}
<ListItem>
<div>
<ListItemText className={styles.tcodes_list_item_code}>{tCode.code}</ListItemText>
<ListItemText className={styles.tcodes_list_item_description}>{tCode.description}</ListItemText>
</div>
</ListItem>
</div>
))
}
</List>
);
}
Here is the AddDialog
component where I send the POST
request to write the new TCode in the JSON file:
//imports ...
export default function AddDialog({ open, setOpen }) {
const [tCode, setTCode] = useState('');
const [description, setDescription] = useState('');
const [keywords, setKeywords] = useState('');
const initState = () => {
setTCode('');
setDescription('');
setKeywords('');
}
const closeDialog = () => {
setOpen(false);
}
const addTCode = () => {
fetch('http://localhost:3000/api/tcodes', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
code: tCode,
description: description,
keywords: keywords
})
});
initState();
closeDialog();
}
const handleTCodeChange = (event) => {
setTCode(event.target.value);
}
const handleDescriptionChange = (event) => {
setDescription(event.target.value);
}
const handleKeywordsChange = (event) => {
setKeywords(event.target.value);
}
return (
<Dialog open={open}
onClose={closeDialog}>
<DialogTitle>Add new TCode</DialogTitle>
<DialogContent>
<TextField value={tCode}
onChange={handleTCodeChange}
autoFocus
fullWidth
margin="dense"
label="TCode"
type="text"
variant='outlined' />
<TextField value={description}
onChange={handleDescriptionChange}
fullWidth
margin='dense'
label="Description"
type="text"
variant='outlined' />
<TextField value={keywords}
onChange={handleKeywordsChange}
fullWidth
margin='dense'
label="Keywords"
type="text"
variant='outlined' />
</DialogContent>
<DialogActions>
<Button onClick={closeDialog}>Cancel</Button>
<Button onClick={addTCode}>Add</Button>
</DialogActions>
</Dialog>
);
}
The addTCode
function is where the file is updated. In this way I would have expected the SWR package to revalidate the data and update it inside the TCodesList
component.
P.S. I'm passing data
as props throughout the app. I explored different solutions to better manage the state providing inside the app and to manage the state updating (context, prop drilling, context dispatchers) but it seemed all a bit of an overkill so I'm sticking with SWR right now since it has a few benefits.
I'm probably missing something obvious since I'm quite new to this. Does anyone have any idea on how to accomplish what explained above?
Thanks in advance for the help.
CodePudding user response:
You need to ask SWR
to mutate the data after you make your POST request. You can do that by passing mutate
returned by useSWR
to TCodesList
:
//imports ...
function useTCodes() {
const fetcher = (...args) => fetch(...args).then((res) => res.json());
const { data, error, isLoading, mutate } = useSWR("/api/tcodes", fetcher);
return {
data: data,
isLoading: isLoading,
isError: error,
mutate: mutate,
};
}
export default function Home() {
const { data, mutate } = useTCodes();
return (
<div>
<Toolbar data={data} />
<div className={styles.tcodes_container}>
<TCodesList data={data} mutate={mutate} />
</div>
</div>
);
}
Then pass this same mutate
to AddDialog
along with data
, to use it inside addTCode
, like so:
const addTCode = async () => {
const newItem = {
code: tCode,
description: description,
keywords: keywords,
};
await fetch("http://localhost:3000/api/tcodes", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(newItem),
});
// update the local data immediately and revalidate (refetch)
// NOTE: key is not required when using useSWR's mutate as it's pre-bound
mutate([ ...data, newItem ]);
initState();
closeDialog();
};