How to properly derive state from the URL
Unanswered
Acacia-ants posted this in #help-forum
Acacia-antsOP
I'm currently building a search page and trying to use the URL and search params as the source of truth for the state but I'm running into some issues.
I initially had it as something simpler to below, but found that the back button didn't work (the react state caused it to redirect immediately), so I've had to do this. But it feels overly complicated, which makes me think it's the wrong approach.
For example, I have a select that changes the way the list is sorted and it looks like this:
Is there a better way to do this?
I initially had it as something simpler to below, but found that the back button didn't work (the react state caused it to redirect immediately), so I've had to do this. But it feels overly complicated, which makes me think it's the wrong approach.
For example, I have a select that changes the way the list is sorted and it looks like this:
function CompanySelect({
className,
sort,
}: {
className?: string;
sort: string;
}) {
const [selected, setSelected] = React.useState(sort);
const [isStateUpdating, setIsStateUpdating] = React.useState(false);
const router = useRouter();
const pathname = usePathname();
const searchParams = useSearchParams();
const newSearchParams = React.useMemo(
() => new URLSearchParams(searchParams.toString()),
[searchParams]
);
selected !== ""
? newSearchParams.set("sort", selected)
: newSearchParams.delete("sort");
if (selected !== searchParams.get("sort")) {
newSearchParams.delete("page");
}
React.useEffect(() => {
if (
isStateUpdating &&
searchParams.toString() !== newSearchParams.toString()
) {
setIsStateUpdating(false);
router.push(`${pathname}?${newSearchParams.toString()}`);
}
}, [isStateUpdating, newSearchParams, pathname, router, searchParams]);
if (
!isStateUpdating &&
searchParams.toString() !== newSearchParams.toString()
) {
setSelected(searchParams.get("sort") || "relevant");
}
return <Insert Select Component here />
}Is there a better way to do this?
7 Replies
Acacia-antsOP
Hey @Siberian Flycatcher thanks! That does simplify it for this component. Would the same hold true for a more complicated component like a combobox with multi select?
@Acacia-ants Hey <@707695420721201153> thanks! That does simplify it for this component. Would the same hold true for a more complicated component like a combobox with multi select?
If the selected value is in the searchParams, 100%
Acacia-antsOP
@DirtyCajunRice | AppDir - so I would just derive it all via the URL instead of having the state in react + delete from url instead of deleting from stand in the unselect/handlekeydown events?
export function PerksCombobox({ json }: { json: PerkResponse }) {
const [open, setOpen] = React.useState(false);
const results = json.results;
const [inputValue, setInputValue] = React.useState("");
const inputRef = React.useRef<HTMLInputElement>(null);
const initialSearchParams = useSearchParams();
const benefits = initialSearchParams.get("benefits")?.split(",");
const benefitsArr = getInitialBenefits({ benefits, results });
const [selected, setSelected] = React.useState<Perk[]>(benefitsArr);
const newSearchParam = selected
.map((item) => item.value.toLowerCase().replace(/ /g, "-"))
.join(",");
const { searchParams, isStateUpdating, setIsStateUpdating } =
useUpdateSearchParam({
key: "benefits",
newSearchParam: newSearchParam,
});
const handleUnselect = React.useCallback(
(result: Perk) => {
setSelected((prev) => prev.filter((s) => s.value !== result.value));
},
[setSelected]
);
const handleKeyDown = React.useCallback(
(e: React.KeyboardEvent<HTMLDivElement>) => {
const input = inputRef.current;
if (input) {
if (e.key === "Delete" || e.key === "Backspace") {
if (input.value === "") {
setSelected((prev) => {
const newSelected = [...prev];
newSelected.pop();
return newSelected;
});
}
}
}
},
[setSelected]
);
const selectables = matchSorter(results, inputValue, {
keys: ["value"],
}).filter(
(result) =>
!selected.some((selectedItem) => selectedItem.value === result.value)
);@Acacia-ants <@184479429404262410> - so I would just derive it all via the URL instead of having the state in react + delete from url instead of deleting from stand in the unselect/handlekeydown events?
export function PerksCombobox({ json }: { json: PerkResponse }) {
const [open, setOpen] = React.useState(false);
const results = json.results;
const [inputValue, setInputValue] = React.useState("");
const inputRef = React.useRef<HTMLInputElement>(null);
const initialSearchParams = useSearchParams();
const benefits = initialSearchParams.get("benefits")?.split(",");
const benefitsArr = getInitialBenefits({ benefits, results });
const [selected, setSelected] = React.useState<Perk[]>(benefitsArr);
const newSearchParam = selected
.map((item) => item.value.toLowerCase().replace(/ /g, "-"))
.join(",");
const { searchParams, isStateUpdating, setIsStateUpdating } =
useUpdateSearchParam({
key: "benefits",
newSearchParam: newSearchParam,
});
const handleUnselect = React.useCallback(
(result: Perk) => {
setSelected((prev) => prev.filter((s) => s.value !== result.value));
},
[setSelected]
);
const handleKeyDown = React.useCallback(
(e: React.KeyboardEvent<HTMLDivElement>) => {
const input = inputRef.current;
if (input) {
if (e.key === "Delete" || e.key === "Backspace") {
if (input.value === "") {
setSelected((prev) => {
const newSelected = [...prev];
newSelected.pop();
return newSelected;
});
}
}
}
},
[setSelected]
);
const selectables = matchSorter(results, inputValue, {
keys: ["value"],
}).filter(
(result) =>
!selected.some((selectedItem) => selectedItem.value === result.value)
);
that has been my experience. and you can make the whole process easier on yourself by making a helper function that you pass an array of keys/values to set or delete so you dont have so much boilerplate
Acacia-antsOP
In this situation, wouldn't this mean that the combobox wouldn't re-render because there would be no state change to trigger a re-render in react? e.g. at the moment, the component re-renders whenever
selected changes but it selected is derived from the URL there wouldn't be any reason for it to re-render? Or am I not thinking about it properly..Acacia-antsOP
ohhh! That is interseting, thansk so mcuh!