T
TanStack•16mo ago
absent-sapphire

useQuery and useEffect (Possible Anti-Pattern)

Hi there - I have a particular pattern I've been using in my React/Next codebase. It feels a bit like an anti-pattern, but I'm not sure the best way to adjust my code. Right now, when I have a "details" page, or some page where you can view and edit an object, the component is structured something like this: - data fetching is done using react-query to retrieve the object in question. for example, we are fetching an associate by their ID, pulled from the page params - for each editable attribute, we have a useState, and any edits made by the user are set into this - whenever the data changes, we have a useEffect that manually sets all of the useStates to the value of the data (because data is fetched asynchronously) - in the actual TSX, it's a basic form with radix ui components - the submit button calls a mutation that pulls data from the useStates I think the useEffect is probably not necessary, or at the very least it feels wrong. Right now it's needed because the data is initially undefined, and I want to populate "defaults" of the pseudo-form/page. Is this wrong? and if so, what is a better approach? I appreciate any tips - thanks!
"use client";
// ..... imports left out for brevity
export default function AssociateDetails({
params,
}: { params: { id: string } }) {

const cookies = useCookies();
const router = useRouter();
const pathname = usePathname();
const queryClient = useQueryClient();

const { mutate } = useMutation(
UpdateAssociate(queryClient, cookies),
);

const { data } = useQuery(
AssociateConnection(
{
after: "",
query: "",
filters: [{ field: "id", value: [params.id] }],
limit: 1,
sortBy: "id",
direction: "DESC",
},
cookies,
),
);

const associate = data?.edges[0]?.node;

const [dailyLimit, setDailyLimit] = useState(associate?.dailyLimit || 100);
const [sendingDelay, setSendingDelay] = useState(associate?.sendDelaySeconds || 20);

useEffect(() => {
if (!associate) return;
setDailyLimit(associate.dailyLimit);
setSendingDelay(associate.sendDelaySeconds);
}, [associate]);

return (
<Sheet
open={pathname === `/associates/${params.id}`}
onOpenChange={() => {
router.push("/associates");
}}
>
<SheetContent>
<SheetHeader>
<SheetTitle>Details</SheetTitle>
</SheetHeader>
<div>
<Label htmlFor="width">
Daily Limit
</Label>
<Input
id="width"
value={dailyLimit}
onChange={(e) => setDailyLimit(Number(e.target.value))}
/>
<Label htmlFor="maxWidth">
Sending Delay
</Label>
<Input
id="maxWidth"
value={sendingDelay}
onChange={(e) => setSendingDelay(Number(e.target.value))}
/>
</div>
<SheetFooter>
<Button
onClick={() => {
if (!associate) return;
mutate({
associateID: associate.id,
dailyLimit: dailyLimit,
sendDelay: sendingDelay,
});
}}
>
Save Changes
</Button>
</SheetFooter>
</SheetContent>
</Sheet>
);
}
"use client";
// ..... imports left out for brevity
export default function AssociateDetails({
params,
}: { params: { id: string } }) {

const cookies = useCookies();
const router = useRouter();
const pathname = usePathname();
const queryClient = useQueryClient();

const { mutate } = useMutation(
UpdateAssociate(queryClient, cookies),
);

const { data } = useQuery(
AssociateConnection(
{
after: "",
query: "",
filters: [{ field: "id", value: [params.id] }],
limit: 1,
sortBy: "id",
direction: "DESC",
},
cookies,
),
);

const associate = data?.edges[0]?.node;

const [dailyLimit, setDailyLimit] = useState(associate?.dailyLimit || 100);
const [sendingDelay, setSendingDelay] = useState(associate?.sendDelaySeconds || 20);

useEffect(() => {
if (!associate) return;
setDailyLimit(associate.dailyLimit);
setSendingDelay(associate.sendDelaySeconds);
}, [associate]);

return (
<Sheet
open={pathname === `/associates/${params.id}`}
onOpenChange={() => {
router.push("/associates");
}}
>
<SheetContent>
<SheetHeader>
<SheetTitle>Details</SheetTitle>
</SheetHeader>
<div>
<Label htmlFor="width">
Daily Limit
</Label>
<Input
id="width"
value={dailyLimit}
onChange={(e) => setDailyLimit(Number(e.target.value))}
/>
<Label htmlFor="maxWidth">
Sending Delay
</Label>
<Input
id="maxWidth"
value={sendingDelay}
onChange={(e) => setSendingDelay(Number(e.target.value))}
/>
</div>
<SheetFooter>
<Button
onClick={() => {
if (!associate) return;
mutate({
associateID: associate.id,
dailyLimit: dailyLimit,
sendDelay: sendingDelay,
});
}}
>
Save Changes
</Button>
</SheetFooter>
</SheetContent>
</Sheet>
);
}
4 Replies
like-gold
like-gold•16mo ago
Yeah check out tkdodo query form
like-gold
like-gold•16mo ago
React Query and Forms
Forms tend to blur the line between server and client state, so let's see how that plays together with React Query.
absent-sapphire
absent-sapphireOP•16mo ago
ahh nice. Seems like TkDodo has an article for everything 😅 thanks
conscious-sapphire
conscious-sapphire•16mo ago
I really do 😂

Did you find this page helpful?