#How to use type guard for null checks using a custom function

93 messages · Page 1 of 1 (latest)

sweet lichen
#

I have created a function.

export default function hasValue(
  ...params: Array<string | number | object | undefined | null>
) {
  return params.every((value) => {
    if (value === null || value === undefined) return false;
    if (typeof value === "object") return Object.keys(value).length;
    return true;
  });
}

Now let's say I have created two values that can be undefined.

let a: string | undefined = "a";
let b: number | undefined = 5;

if (hasValue(a, b)) {
  //Here a and b still can be undefined as per typescript, but logically, they can't be.
  //how can I create a type guard in that function such that a and b won't be undefined in this if block.

} 

warm sage
#

params is Array<string | number | object> comes to mind but not sure if that works with the rest parameter.

sweet lichen
#

doesn't work with rest parameters :(

storm gull
#

just explicitly passing an array isn't that bad though:

still wigeonBOT
#
mkantor#0

Preview:```ts
export default function hasValue(
param: Array<
string | number | object | undefined | null

): param is Array<string | number | object | null> {
return param.every(value => {
if (value === null || value === undefined)
return false
if (typeof value === "object")
return Object.keys(value).leng
...```

storm gull
#

err wait that only works because of the assignment. you need to pack and then destructure i guess

#

okay here's something that actually works. it's a bit more involved though:

still wigeonBOT
#
mkantor#0

Preview:```ts
type NonNullableElements<
A extends readonly unknown[]

= {
[K in keyof A]: NonNullable<A[K]>
}
export default function hasValue<
A extends readonly unknown[]
(param: A): param is NonNullableElements<A> {
return param.every(value => {
if (value === null || value === undefined)
return false
...```

sweet lichen
#

since I am not destructuring params

storm gull
#

correct, you need to pass an array. as stated previously there's no way to do it with multiple parameters

sweet lichen
#

I also tried passing array

#

but the elements were still undefined as per typescript

storm gull
# still wigeon

check the types here, it works in this case. did you do something differently?

sweet lichen
#

I will try again

still wigeonBOT
#

@sweet lichen Here's a shortened URL of your playground link! You can remove the full link from your message.

pinacas#0

Preview:```ts
type NonNullableElements<
A extends readonly unknown[]

= {
[K in keyof A]: NonNullable<A[K]>
}
export default function hasValue<
A extends readonly unknown[]
(param: A): param is NonNullableElements<A> {
return param.every(value => {
if (value === null || value === undefine
...```

sweet lichen
#

what am I doing wrong here

storm gull
#

TS only narrows the value passed in. in your case that value is an anonymous array. you need to bind it to a variable and then narrow that

still wigeonBOT
#
mkantor#0

Preview:ts ... function doSomething() { const x = [a] if (hasValue(x)) { const a = x[0] console.log(a) } }

sweet lichen
#

I tested, it only works if I make an array of elements
then pass it to function
then destructure it again

storm gull
#

it's the same deal as b not getting narrowed here:

still wigeonBOT
#
mkantor#0

Preview:ts declare let a: number | null let b = a if (a !== null) { b //^? }

storm gull
#

even though we technically checked it (indirectly)

#

anyway for your use case this seems pretty unergonomic. what's the bigger picture? maybe there's a better way to organize your code to avoid having to do this awkward dance?

sweet lichen
#

Is making an array of the elements then destructuring a good way to write code?

storm gull
#

i would definitely look for other options before leaning too heavily on this setup

#

i think i need to know more about your real code to give specific advice

sweet lichen
#

I will explain my use case

#

It's related to react query
useQuestion is a react hook that returns useQuery Hook

const { data } = useQuestions( ... );
// since data can be undefined here 
const {question, correct, incorrect, marks} = data || {};

...

useEffect(()=> {

  //now I have to use typeguard for question, correct, incorrect, marks
  postData(question, correct, incorrect, marks)
}, [someValue])

#

So I can do something like this

  if (!question || !correct ....)
    postData(....)
warm sage
#

does it make sense to just have default values for all of them?

sweet lichen
#

Actually no,
since they would be coming from API
and can be anything

#

But I was thinking of creating some generic soolution that might work everywhere, so that was my approach

warm sage
#

that doesn't matter too much, does it make sense in the context of your app?

#

if the api doesn't provide a marks, could you just default to 0?

sweet lichen
#

I can do something like that
But then I would also have to validate this again, if marks === 0, something like that

warm sage
#

could you not just handle that normally?

#

in what case would the api omit values?

sweet lichen
#

I can do something like this, at early lines, before destructuring

if (quiz === undefined) return <>Some loading screen</>

but that's bad practice I think

warm sage
#

wait i think i misread some stuff

#

one sec

#

if the api doesn't actually omit those values, then couldn't you just use zod or something to verify the schema once and then propagate those known, populated values?

#

i feel like i'm still missing something about the context here

#

oh wait, it's about the wait time before the fetch completes, right?

sweet lichen
#

no no

#

I am not trying to validate values, just trying to create a typeguard function, so the typescript won't throw, value can be undefined when passing to function, that doesn't take undefined values

function doSomething(a: number | undefined) {
  if (hasValue(a)) {
    anotherDoSomething(a);
    // ^ a can be undefined

  }
}

function anotherDoSomething(a: number) {}
#

the function hasValue is at top of this thread

storm gull
#

in your real code what will happen in the else condition? like business-logic wise, how do you intend to handle nullish values?

storm gull
sweet lichen
sweet lichen
storm gull
#

thanks, i think i get it. next question is, you wrote this before:

const {question, correct, incorrect, marks} = data || {};

is that just an example or does your initial assignment of those variables really look like that? if it's just an example, what does the real thing look like?

#

(also relevant i guess: what is the type of data?)

sweet lichen
#

It was an example

#
export interface Questions {
  category: string;
  id: string;
  tags: string[];
  difficulty: string;
  regions: string[];
  isNiche: boolean;
  question: Question;
  correctAnswer: string;
  incorrectAnswers: string[];
  type: string;
}
#

this is data type for data

#

I am doing a school project, a mini quiz application

I am trying to pull data from api, and do some operation on it

#

So the react query provides multiple value when we use useQuery hook

data, isLoading

but the data will be of type Questions | undefined

When I destructure the data
the destructured variables will also be of type in union with undefined
for example category: string | undefined

Logically, I would be using isLoading state that react query to show loading screen;

const {data, isLoading} = useQuiz();
const {category, name, ...} = data;
...
function doOperation() {
  doSomething(category, name, ...);
  // Logically category, name won't be undefined here, but typescript will show
}

...

if (isLoading) return <LoadingScreen />

return <Button onClick={doOperations}/>
storm gull
#

but the data will be of type Questions | undefined
in that case i would just check for undefined once upfront rather than spreading the undefined-ness across a bunch of different variables by destructuring and then having to deal with it in multiple places over and over

#

it'll be both cleaner and more efficient to do that way ☝️

sweet lichen
#

I think so, though I was taught to not sandwich return statements between hooks functions.

warm sage
#

isLoading sounds like it'd be a discriminant

storm gull
#
if (data === undefined) {
  return <LoadingScreen />
}
const {question, correct, incorrect, marks} = data;
// the rest of your code...
sweet lichen
warm sage
#

are you sure the useQuery isn't actually something like { data: T, isLoading: false } | { data: undefined, isLoading: true }

sweet lichen
#

I am sure it isn't I believe

warm sage
still wigeonBOT
#
that_guy977#0

Preview:```ts
declare function useQuiz():
| {data: string; isLoading: false}
| {data: undefined; isLoading: true}

const {data, isLoading} = useQuiz()

if (!isLoading) {
data
//^?
}```

warm sage
#

useQuery is a DU actually

#

as stated in the docs

#

sounds like you're losing that info with the useQuiz wrapper

#

you would need to check isSuccess or status i think?

sweet lichen
#
export default function useQuiz (code: string, enabled: boolean) {
  const { QUIZZES } = strapiUrls;
  return useQuery({
    queryKey: ["quiz", code],
    queryFn: async () => {
      const quiz = await strapiInstance.getCollection<Quiz>(QUIZZES, {
        params: { filters: { code } },
      });
      return quiz[0]
    },
    enabled,
    retry: 3,
  });
}

this is my useQuiz function

warm sage
#

also btw; you don't need to exit early with the return. you could have a loading page overlay, and have the unpopulated page under it, then when the data comes, just remove the overlay and populate the page

warm sage
#

have you tried using isSuccess like the docs say?

sweet lichen
#

I haven't read about isSuccess will explore on it. Thanks

warm sage
sweet lichen
#

!resolved

#

Thanks everyone, though it didn't solve initial problem, I think I got the better solution. which is a win :)

warm sage
sweet lichen
#

That was a fun read

#

After much interaction and wasted time, it finally becomes clear that the user really wants help with X, and that Y wasn't even a suitable solution for X.
Y was a better solution for me :P

#

uh, I have bad reading comprehension I think.

#

Just asking, Y would be the hasValue implementation I did, and X would be isSuccess one?

sweet lichen
warm sage
#

X wouldve been the issue of consuming data from useQuiz, Y wouldve been the issue of trying to use hasValue to validate the data