S
SolidJSā€¢2y ago
Bersaelor

Why can't I access `useParams()` in `onClick`?

If I do
const handleClick = () => {
console.log('useParams():', useParams())
// ...
<div onClick={handleClick} >
const handleClick = () => {
console.log('useParams():', useParams())
// ...
<div onClick={handleClick} >
I get
Uncaught Error: Make sure your app is wrapped in a <Router />
at invariant (utils.js?v=929afe85:28:15)
at useRouter (routing.js?v=929afe85:9:32)
Uncaught Error: Make sure your app is wrapped in a <Router />
at invariant (utils.js?v=929afe85:28:15)
at useRouter (routing.js?v=929afe85:9:32)
If I call useParams() anywhere else it works fine, is the handleClick somwhere out of the <Router> hierarchy?
14 Replies
thetarnav
thetarnavā€¢2y ago
useParams() does a lookup for context so it needs to access current owner object and event-listeners are async - they aren't executed in scope of an owner. So to fix it 1. access context as high as possible, so I guess top-level of the component in this case 2. use getOwner + runWithOwner to get access to owner obj in the event handler
Bersaelor
BersaelorOPā€¢2y ago
oh dear, I would have looked for ages trying to figure that out! So
const owner = getOwner();
const handleClick = () => {
// This callback gets run without owner.
// Restore owner via runWithOwner:
runWithOwner(owner, () => {
var params = useParams();
// ..
});
}
const owner = getOwner();
const handleClick = () => {
// This callback gets run without owner.
// Restore owner via runWithOwner:
runWithOwner(owner, () => {
var params = useParams();
// ..
});
}
thetarnav
thetarnavā€¢2y ago
Yup something like this I assume the runWithOwner will be reworked in the new solid versions. it's a quite new api so it's not as refined - more of an escape hatch. And I probalby should stop suggesting it as solutions for people... šŸ˜…
Bersaelor
BersaelorOPā€¢2y ago
i tried a lot opf other things and they didn't work. And I would really like to make what happens in the onClick depend on the path the user is currently on in my case, I want to navigate to one layer above in the hierarchy, i.e.
host.com/detail1/detail2/detail3 -> host.com/detail1/detail2/
host.com/detail1/detail2/detail3 -> host.com/detail1/detail2/
so the escape hatch may be the only option?
thetarnav
thetarnavā€¢2y ago
why not like this
const params = useParams()
function handleClick() {
// logic that depends on params
}
const params = useParams()
function handleClick() {
// logic that depends on params
}
Bersaelor
BersaelorOPā€¢2y ago
I thought I shouldn't do that since it doesn't show the reactive connection?
thetarnav
thetarnavā€¢2y ago
not sure I follow. params is a reactive object I think. Or a signal. Doesn't matter really. But it can be accessed in the callback to the get latest value. It'll be the same as using useParams there to get that object
Bersaelor
BersaelorOPā€¢2y ago
I got very defensive about: ā€˜ā€™ā€™ var concrete = accessor() ā€˜ā€™ā€™ After a few pitfalls
thetarnav
thetarnavā€¢2y ago
ah no signals passed to context should always be wrapped in functions/getters etc. Similarly to props actually, just with props the compiler wraps them for you. I would not try to do this though:
const params = useParams()
const { someParam } = params
const params = useParams()
const { someParam } = params
Bersaelor
BersaelorOPā€¢2y ago
Ic. Yeah
Similarly to props actually, just with props the compiler wraps them for you.
Yeah i learned that the hard way šŸ˜…
thetarnav
thetarnavā€¢2y ago
And if you really avoid these pitfalls, I would do this
const params = createMemo(() => useParams())
function handleClick() {
if (params().foo) {...}
}
const params = createMemo(() => useParams())
function handleClick() {
if (params().foo) {...}
}
then you are 100% safe šŸ˜
Bersaelor
BersaelorOPā€¢2y ago
mhmm, sort of a related question, whats the standard way of checking wether useParams() changed i.e. I have a
const [selection, setSelection] = createSignal(useParams().selectionId)
const [selection, setSelection] = createSignal(useParams().selectionId)
Now, the user can either click anywhere to change the selection. Or they can change the url (by clicking back/forward), to also change the selection but then I have 2 createEffect blocks, (1) when the currently selected Id is unequal the current path, therefore we need to update the path. (2) when the path is unequal the selection, then we update the selection so the path updates reactively based on the selection. But the selection also updates based on the path I guess I should use
createEffect(async (prev) => {
const hasSelectionChanged = prev != selection()

if (hasSelectionChanged) {
// update path
} else {
setSelection(path)
}
return selection()
}, selection())
createEffect(async (prev) => {
const hasSelectionChanged = prev != selection()

if (hasSelectionChanged) {
// update path
} else {
setSelection(path)
}
return selection()
}, selection())
to determine which direction to update?
thetarnav
thetarnavā€¢2y ago
why wouldn't you write directly to the path, instead of having another signal?
Bersaelor
BersaelorOPā€¢2y ago
mhmm, it's not a 1:1 relationship necessarily, I do have functions pathComponentForDetail and detailForPathComponent thank you @thetarnav , I had a little blockade, of course I can just do
const selectedDetail = createMemo(() => detailForPathComponent(params().detail))
const setSelectedDetail = (detail: string) => {
const path = `${params().category}/${params().itemId}/${pathComponentForDetail(detail) || ''}`
navigate(path, { resolve: false })
}
const selectedDetail = createMemo(() => detailForPathComponent(params().detail))
const setSelectedDetail = (detail: string) => {
const path = `${params().category}/${params().itemId}/${pathComponentForDetail(detail) || ''}`
navigate(path, { resolve: false })
}
Want results from more Discord servers?
Add your server