Reputation: 115
What I want to achieve:
What I have tried:
const createGUID = (): string => {
return "";
};
const callService = (path: string, payload: Payload): void => {
//
};
type Payload = {
id: string;
timeStemp: number;
asyncCall: boolean;
};
function createPayloadWithData<T extends Payload>(
id: string,
asyncCall: boolean,
data?: {}
): T {
let ts = Date.now();
let payload = { id, asyncCall, timeStemp: ts, ...data };
return payload; "//<--- TS error: { id: string; asyncCall: boolean; timeStemp: number; }' is assignable to the constraint of type 'T', but 'T' could be instantiated with a different subtype of constraint 'Payload
}"
const registerUser = (username: string) => {
const userId = createGUID();
const payload = createPayloadWithData(userId, false, { username });
callService("users", payload);
};
const updateRecords = (records: number[]) => {
const recordsId = createGUID();
const payload = createPayloadWithData(recordsId, true, { records });
callService("records", payload);
};
registerUser("john");
updateRecords([1, 2, 3]);
(In sandbox: https://codesandbox.io/s/ecstatic-bas-97ucu?file=/src/index.ts)
TS throws an error for the return value of createPayloadWithData. Please allow me to say that I know WHY it does not work. I understand the logic and I have read the wonderful explanation here: could be instantiated with a different subtype of constraint 'object'
What I don't understand, is how to do it correctly. Would really appreciate your help. Thanks!
Upvotes: 1
Views: 795
Reputation: 13539
The problem here is that generic defined like that: T extends Payload
.
It means T
can contain more properties but id
, timeStemp
and async
for sure and returned T
has to respect it.
type PayloadMore = Payload & {required: boolean};
const result: PayloadMore = createPayloadWithData<PayloadMore>('', false);
// but it won't work because `required` isn't present in the return value.
The way you need to do it is to move data
as generic and unite it in the return
const createGUID = (): string => {
return "";
};
const callService = (path: string, payload: Payload): void => {
//
};
type Payload = {
id: string;
timeStemp: number;
async: boolean;
};
// optional case
function createPayloadWithData(
id: string,
async: boolean
): Payload;
// case with data
function createPayloadWithData<T extends object>(
id: string,
async: boolean,
data: T
): Payload & T;
// implementation
function createPayloadWithData(
id: string,
async: boolean,
data?: {}
): Payload {
let ts = Date.now();
let payload = { id, async, timeStemp: ts, ...data };
return payload;
}
const registerUser = (username: string) => {
const userId = createGUID();
const payload = createPayloadWithData(userId, false, { username });
payload.username // <- now it works
callService("users", payload);
};
const updateRecords = (records: number[]) => {
const recordsId = createGUID();
const payload = createPayloadWithData(recordsId, true, { records });
payload.id // <- now it works
payload.records // <- now it works
callService("users", payload);
};
Upvotes: 3