3

I have a function that return object with two props (res, mes) where one of them is null:

const fetchJSON = <Res, Body>(link: string, body: Body): Promise<{ res: Res; mes: null } | { res: null; mes: Popup }> => {
  return new Promise(resolve => {
    fetch(link,
      body: JSON.stringify(body)
    })
      .then(res => res.json())
      .then((resJson: Res) => {
        resolve({ res: resJson, mes: null })
      })
      .catch(err => {
        resolve({ res: null, mes: { type: 'err', text: 'some error' } })
      })
  })
}

If after i use response of fetch without desctruction everything works fine:

 const result = await fetchJSON<ResReaderData, ApiReaderData>('api/reader_data', { id })
 if (result.mes) return popupPush(result.mes)
 setProfile(result.res.reader)

But if i use object descruction:

const { res, mes } = await fetchJSON<ResReaderData, ApiReaderData>('api/reader_data', { readerId, role: 'alfa' })
  if (mes) return popupPush(mes)

  console.log(res.id)

Typescript doesn't understand that res is not null even if i checked mes:

enter image description here

Is there a way to fix this or i just need to forget about object destruction?

Or maybe there another approach for wrappers like this?

1

2 Answers 2

1

If you know that the property that you are dealing with won't be null. You can use the non-null assertion operator (!) to assert that it isn't null or undefined.

Example console.log(res!.id):

function getResult(type: 'res' | 'mes') {
  let result: {
    res?: { foo: string },
    mes?: { bar: string },
  } = {};

  if (type === 'res') {
    result.res = {
      foo: 'res',
    };
  } else {
    result.mes = {
      bar: 'mes',
    };
  }

  return result;
}

const { res, mes } = getResult('res');

console.log(res!.foo); // non-null assertion
console.log(mes?.bar); // optional chaining

Additionally, you can use the optional chaining operator (?) to have the expression return null if the preceding property is null.

Sign up to request clarification or add additional context in comments.

Comments

1

as far as I know this is a current limitation of typescript. at the time you deconstruct the type is { res: Res; mes: null } | { res: null; mes: Popup } so when unpacking it has to consider all possibilities meaning both res and messages can be null.

After seperating the 2 variables there is no way for the union before to link the 2 variables, they are separate. I look forward to a time when this limitation can be overcome but as of now you are stuck with keeping guards within one object.

one possible solution if your data structures allow it is to just return one field that can be differentiated:

const fetchJSON = <Res, Body>(link: string, body: Body): Promise<{ res: Res; mes: null } | { res: null; mes: Popup }> => {
  return new Promise(resolve => {
    fetch(link,
      body: JSON.stringify(body)
    })
      .then(res => res.json())
      .then((resJson: Res) => {
        resolve(resJson)
      })
      .catch(err => {
        resolve({ type: 'err', text: 'some error' })
      })
  })
}

function isErrorMessage(obj: any): obj is Popup {
    return obj && obj.type === "err";
}

async function MAIN(){

    const result = await fetchJSON<ResReaderData, ApiReaderData>('api/reader_data', { id })
     if (isErrorMessage(result)) return popupPush(result)
     setProfile(result.reader)
}

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.