Using Typescript generic types - javascript

I have an interface that describes basic database CRUD operations like so:
export interface IUserDB {
insert: (params: UserAttributes) => Promise<UserResult>
findById: ({ id }: { id: number }) => Promise<UserResult | null>
findByEmail: ({ email }: { email: string }) => Promise<UserResult | null>
remove: ({ id }: { id: number }) => Promise<any>
update: ({
id,
...changes
}: { id: number } & UserAttributes) => Promise<UserResult>
}
I then go ahead to use this interface in this manner:
const makeAddUser = ({ usersDb }: { usersDb: IUserDb }) => {
return async function addUser(userDetails: User) {
const user = makeUser(userDetails)
const exists = await usersDb.findByEmail({ email: user.getEmail() })
if (exists) {
throw new UniqueConstraintError('Email')
}
const newUser = await usersDb.insert({
name: user.getName(),
email: user.getEmail(),
password: user.getPassword(),
username: user.getUsername(),
})
const { id } = newUser.user
await publisher(id.toString(), 'newuser.verify')
await consumer('verify_queue', verifyUser, '*.verify')
return newUser
}
}
I want to be able to reuse this interface for PostDb and CommentDb without me having to write another interface that describes a Post database operation, how can I achieve this? I read about Typescript Generics, but I am not really sure how to apply it to this case, any help will be appreciated. I am still trying to get a hang of Typescript. Thank you very much!

Not really sure how to explain it other than to just show you, but here is what I would do (with some filler code to make things work, but just focus on the interfaces). But basically you define an interface, and pass in the types you want it to support inside <>. So below I do
export interface IDB<Attributes, Result> {
which means you pass in two additional types to this:
interface IUserDb extends IDB<UserAttributes, UserResult>
and now IUserDb is an IDB where any place IDB has Attributes it instead uses UserAttributes, and same with Result and UserResult:
https://codesandbox.io/s/typescript-playground-export-forked-nej0s?file=/index.ts:0-1569
export interface IDB<Attributes, Result> {
insert: (params: Omit<Attributes, 'id'>) => Promise<Result>
findById: ({ id }: { id: number }) => Promise<Result | null>
remove: ({ id }: { id: number }) => Promise<any>
update: ({
id,
...changes
}: { id: number } & Attributes) => Promise<Result>
}
interface UserAttributes {
id: string;
name: string;
username: string;
email: string;
password: string;
}
interface UserResult extends UserAttributes {
getEmail(): string
getName(): string
getPassword(): string
getUsername(): string
}
export interface IUserDb extends IDB<UserAttributes, UserResult>{
findByEmail: ({ email }: { email: string }) => Promise<UserResult | null>
}
const makeUser = (user: UserAttributes): UserResult => {
const u: any = { ...user };
u.getName = () => u.name;
u.getEmail = () => u.email;
u.getPassword = () => u.password;
u.getUsername = () => u.username;
return u as UserResult;
}
const UniqueConstraintError = (err: string) => new Error(`Unique Constraint Error: ${err}`);
const makeAddUser = ({ usersDb }: { usersDb: IUserDb }) => {
return async function addUser(userDetails: UserAttributes) {
const user = makeUser(userDetails)
const exists = await usersDb.findByEmail({ email: user.getEmail() })
if (exists) {
throw UniqueConstraintError('Email')
}
const newUser = await usersDb.insert({
name: user.getName(),
email: user.getEmail(),
password: user.getPassword(),
username: user.getUsername(),
})
return newUser
}
}

Related

If the mysql result value is an array and generic T is not an array, can it be removed in typescript?

type User = {
name: string
email: string
}
This is my code,
import type { PoolConnection, RowDataPacket, OkPacket } from "mysql2/promise";
type dbDefaults = RowDataPacket[] | RowDataPacket[][] | OkPacket | OkPacket[];
type dbQuery<T> = T & dbDefaults;
type FlattenIfArray<T> = T extends (infer R)[] ? R : T;
function isFlattenArray<T>(rows: any[]): rows is T[] {
return rows.length < 1;
}
// eslint-disable-next-line consistent-return
export async function queryWrapper<T>(
{ query, values }: { query: string; values?: string[] },
conn: PoolConnection
): Promise<T | undefined> {
try {
await conn.beginTransaction();
const [rows, _] = await conn.query<dbQuery<T>>(query, values || undefined);
await conn.commit();
if (isFlattenArray<FlattenIfArray<T>>(rows)) {
return rows;
}
return rows;
} catch (error) {
await conn.rollback();
} finally {
conn.release();
}
}
Mysql code only returns the array
There is no problem with User[].
But When using User, I want to remove the arrangement.
So I used this code, but it didn't work. What should I do?
I added additional explanation.
const result = queryWrapper<User>(query, values, conn)
When use user, It comes out like this.
[
{
name: "user-name",
email: "user#gmail.com"
}
]
But I want it comes out like this.
{
name: "user-name",
email: "user#gmail.com"
}
I'll reply by myself.
type dbDefaults =
| RowDataPacket[]
| RowDataPacket[][]
| OkPacket
| OkPacket[]
| ResultSetHeader;
type Query = { query: string; values?: any[] };
type QueryFunction<T = any> = () => Promise<[T & dbDefaults, FieldPacket[]]>;
type AlwaysArray<T> = T extends (infer R)[] ? R[] : T[];
// eslint-disable-next-line consistent-return
export async function queryTransactionWrapper<T = any>(
queries: QueryFunction[],
conn: PoolConnection
): Promise<[AlwaysArray<T>, FieldPacket[]][] | undefined> {
try {
await conn.beginTransaction();
// await conn.query("START TRANSACTION;");
const executedQueries = await Promise.all(
queries.map((query) => {
return query();
})
);
// await conn.query("COMMIT;");
await conn.commit();
return executedQueries;
} catch (error) {
logger.error(colors.blue(JSON.stringify(error)));
await conn.rollback();
} finally {
conn.release();
}
}
export function findOne({ query, values }: Query, conn: PoolConnection) {
return function () {
return conn.query<RowDataPacket[]>(query, values);
};
}
export function find({ query, values }: Query, conn: PoolConnection) {
return function () {
return conn.query<RowDataPacket[]>(query, values);
};
}
export function update({ query, values }: Query, conn: PoolConnection) {
return function () {
return conn.query<ResultSetHeader>(query, values);
};
}
export function insert({ query, values }: Query, conn: PoolConnection) {
return function () {
return conn.query<OkPacket>(query, values);
};
}
when call findOneUser
async findByEmail(email: string): Promise<IUser | undefined> {
const conn = await this.mysql.getConnection();
const query = `Select * FROM ${USER_TABLE} WHERE email=?`;
const findUserQueryFunction = findOne({ query, values: [email] }, conn);
const executedQueries = await queryTransactionWrapper<IUser>(
[findUserQueryFunction],
conn
);
if (!executedQueries) {
throw new Error();
}
const [[rows]] = executedQueries;
return rows[0];
}
if <IUser[]>, findByEmail returns rows
else returns rows

Problem handling Promises with Typescript

I have a small problem deleting with types. Here is my code snippet where I have the issue:
const prams = useParams<RouteParams>();
const [event, setEvent] = useState<EventType>();
const [error, setError] = useState<boolean>(false);
const [loading, setLoading] = useState<boolean>(false);
let id = prams.id;
console.log(id);
useEffect(() => {
setLoading(true);
if (id) {
getEventById(id)
.then((res: EventType) => { // the issue is here it says is not assignable to parameter of type '(value: unknown) => void | PromiseLike<void>'
setEvent(res);
console.log(res, "rs");
setLoading(false);
})
.catch((err) => {
setError(true);
setLoading(false);
});
}
}, [id]);
console.log(event, "events");
When I replace Event with any it works fine.
the function for request the Event
import axios from "axios";
import { Config, URL } from "./Api";
const getEventById = async (id: string) => {
const { data } = await axios.get(`${URL}getEvent/${id}`, Config);
return Object.values(data?.event)[0];
};
export { getEventById };
Here is the Event Type:
export type EventType = {
ADDRESS: string;
EVENT_DATE: string;
ID: string;
MIN_PRICE: number;
NAME: string;
PICTURE: string;
PLACE: string;
PREVIEW: string;
REGISTRATION_END_DATE: string;
TYPE: string;
DESCRIPTION: string;
RESTAURANT_LOGO: string;
RESTAURANT_ID?: string;
};
and here is the response from the getEventById():
Any clue where the problem is, or maybe the solution?
Well, your getEventById doesn't declare a proper return type, so it does not return a Promise<Event> but a Promise<any> because there is no way, the TS compiler can infer the correct type.
The simplest would probably be
const getEventById = async (id: string): Promise<Event> => {
const { data } = await axios.get(`${URL}getEvent/${id}`, Config);
return Object.values(data?.event)[0];
};
Here Event is representing the Keyboard event. You need a different interface relevant to the response from the service. Try renaming it and see if you are importing the right interface

Create Reusable Functions with Typescript

I have a function that returns other functions like so:
export const makeAudienceDb = () => {
async function insert({ ...params }: AudienceAttributes) {
const audience = await AudienceModel.create({ ...params })
const audienceToJson = audience.toJSON()
return audienceToJson
}
async function findById({ id }: { id: number }) {
const user = await AudienceModel.findByPk(id)
return user?.toJSON()
}
async function remove({ id }: { id: number }) {
return AudienceModel.destroy({ where: { id } })
}
async function update({
id,
...changes
}: { id: number } & AudienceAttributes) {
const updated = await AudienceModel.update(
{ ...changes },
{ where: { id } }
)
return updated
}
return Object.freeze({
insert,
findById,
remove,
update,
})
}
I have other models, e.g UserModel, PostModel which have the same database operations as makeAudienceDb. e.g
export const makeUsersDb = ({ hashPassword, createToken }: DBDeps) => {
async function insert({ ...params }: User) {
if (params.password) {
params.password = await hashPassword(params.password)
}
const newUser = await UserModel.create({ ...params })
const returnedUser = newUser.toJSON()
const { id } = newUser
const { name, username, email, password } = returnedUser as User
const payload = {
id,
email,
}
const token = createToken(payload)
const user = { id, name, username, password, email }
return { user, token }
}
async function findById({ id }: { id: number }) {
const user = await UserModel.findByPk(id)
return user?.toJSON()
}
async function findByEmail({ email }: { email: string }) {
const user = await UserModel.findOne({ where: { email } })
return user?.toJSON()
}
async function remove({ id }: { id: number }) {
return UserModel.destroy({ where: { id } })
}
async function update({ id, ...changes }: { id: number } & User) {
const updated = await UserModel.update({ ...changes }, { where: { id } })
return updated
}
return Object.freeze({
insert,
findByEmail,
findById,
remove,
update,
})
}
This leads to code duplication across various levels. I want to know how to create one major function that I can reuse for other database operations. So instead of me having makeAudienceDb, makeUsersDb, I could just have one major function e.g majorDatabaseOps where makeAudienceDb and makeUsersDb could just inherit from. I know this is possible with ES6 Classes and Interfaces but I was just wondering how I could implement the same in a functional way. Any contribution is welcome. Thank you very much!
It looks like what you are trying to do is bind shortcuts to particular sequelize methods. The shared functionality can be implemented using typescript generics. Overriding specific behaviors, like hashing a password for a User makes this a bit more complex.
My first instinct is to use a class-based approach. But you can do it with functions by copying all of the methods from the base and then overriding or adding specific ones, along these lines:
const userDb = Object.freeze({
...makeDb(UserModel),
findByEmail: async ({email}: {email: string}) => {
}
})
We want to create a function that takes the model as an argument. It will use typescript generics to describe the types associated with that model. The generics will be inferred from the model variable when calling the function.
A sequelize Model has two generic values: TModelAttributes and TCreationAttributes which is optional and defaults to TModelAttributes. We also want to require that all of our model attributes must include {id: number}.
You could potentially add additional typings to get better support for toJSON. The sequelize package just declares the return type from Model.toJSON as object which is vague and unhelpful.
Our general function looks like this:
import {Model, ModelCtor} from "sequelize";
export const makeDb = <TModelAttributes extends {id: number} = any, TCreationAttributes extends {} = TModelAttributes>(
model: ModelCtor<Model<TModelAttributes, TCreationAttributes>>
) => {
async function insert({ ...params }: TCreationAttributes) {
const created = await model.create({ ...params });
return created.toJSON();
}
async function findById({ id }: { id: number }) {
const found = await model.findByPk(id)
return found?.toJSON()
}
async function remove({ id }: { id: number }) {
return model.destroy({ where: { id } })
}
async function update({ ...changes }: { id: number } & Partial<TModelAttributes>) {
const { id } = changes; // I get a TS error when destructuring this in the args
const updated = await model.update(
{ ...changes },
{ where: { id } }
);
return updated;
}
return Object.freeze({
insert,
findById,
remove,
update,
});
}
For audience, you would simply call:
const audienceDb = makeDb(AudienceModel);
Or you could define it as a function if you wanted to:
const makeAudienceDb = () => makeDb(AudienceModel);
For the users database, we need to override insert, add findByEmail, and take additional arguments hashPassword and createToken.
This is not elegant, but it should work. I don't love that your return type for user insert is incompatible with the insert returned value from the general makeDb.
export const makeUsersDb = ({ hashPassword, createToken }: DBDeps) => {
// declaring this up top so that you could call methods on it in your overrides
const db = makeDb(UserModel);
async function insert({ ...params }: User) {
if (params.password) {
params.password = await hashPassword(params.password)
}
const newUser = await UserModel.create({ ...params })
const returnedUser = newUser.toJSON()
const { id } = newUser
const { name, username, email, password } = returnedUser as User
const payload = {
id,
email,
}
const token = createToken(payload)
const user = { id, name, username, password, email }
return { user, token }
}
async function findByEmail({ email }: { email: string }) {
const user = await UserModel.findOne({ where: { email } })
return user?.toJSON()
}
return Object.freeze({
...db,
insert,
findByEmail
})
}
How about defining the common functions outside of the majorDatabaseOps function? Will let you reuse them in different places.
//defined outside for reusability
function findById(model, id) {
return model.findByPk(id)
}
const majorDatabaseOps = model => {
function removeById(model, id) {
return model.remove(id);
}
return Object.freeze({
removeById: id => removeById(model, id),
findById: id => findById(model, id),
})
}
//mocking models for demonstration
const UserModel = {
modelName: "UserModel",
findByPk: function(id) {
return console.log(id + " was found in " + this.modelName)
},
remove: function(id) {
return console.log(id + " was removed from " + this.modelName)
}
}
const PostModel = {
modelName: "PostModel",
findByPk: function(id) {
return console.log(id + " was found in " + this.modelName)
},
remove: function(id) {
return console.log(id + " was removed from " + this.modelName)
}
}
const userDbOps = majorDatabaseOps(UserModel);
userDbOps.findById(1);
userDbOps.removeById(1);
const postDbOps = majorDatabaseOps(PostModel);
postDbOps.findById(2);
postDbOps.removeById(2);

Apollo: props/cache doesn't update in refetchQueries

I'm using apollo's HoCs to achieve my current mutation, but I'm a bit confused on a few errors I'm getting:
this is my query:
import gql from 'graphql-tag';
import { careTeamMember } from '../../fragments/careProvider';
const getFullMemberDetails = gql`
query getFullMemberDetails($id: ID!) {
member(id: $id) {
id
created_at
date_of_birth
first_name
last_name
name
email
phone
verified
informed_consent
// ....
}
}
${careTeamMember}`;
export default getFullMemberDetails;
these are my mutations:
import gql from 'graphql-tag';
const updateMember = gql`
mutation UpdateMember($input: UpdateMemberInput!) {
updateMember(input: $input) {
success
member {
id
name
first_name
last_name
email
phone
date_of_birth
informed_consent
hipaa_privacy_policy
check_in_frequency_days
}
}
}
`;
export default updateMember;
import gql from 'graphql-tag';
const updateMemberInsurancePolicy = gql`
mutation UpdateMemberInsurancePolicy(
$member_id: ID!,
$carrier_name: String,
$insurance_member_id: String,
$insurance_group_id: String,
$plan_name: String,
) {
updateMemberInsurancePolicy(
member_id: $member_id,
carrier_name: $carrier_name,
insurance_member_id: $insurance_member_id,
insurance_group_id: $insurance_group_id,
plan_name: $plan_name,
) {
member_insurance_policy {
id
carrier_name
insurance_group_id
insurance_member_id
plan_name
member {
id
previsit {
id
can_schedule
}
}
}
success
}
}
`;
export default updateMemberInsurancePolicy;
and being used like this
handleMemberSubmit = async (formData): Promise<any> => {
try {
const payload = {
id : this.props.data.member.id,
patch : {
first_name : formData.first_name,
last_name : formData.last_name,
date_of_birth : formData.date_of_birth,
phone : formData.phone,
// ....
payment_preference : formData.payment_preference ? 'OUT_OF_POCKET' : 'INSURANCE',
},
};
const insurancePayload = {
member_id : this.props.data.member.id,
carrier_name : formData.insurance_policy.carrier_name,
plan_name : formData.insurance_policy.plan_name,
insurance_group_id : formData.insurance_policy.insurance_group_id,
insurance_member_id : formData.insurance_policy.insurance_member_id,
};
const [
updateMemberResponse,
submitInsuranceResponse,
] = await Promise.all([
this.props.updateMember(payload),
this.props.submitInsurance(insurancePayload),
]);
const { data: { updateMember } } = updateMemberResponse;
const { data: { updateMemberInsurancePolicy } } = submitInsuranceResponse;
return this.props.addNotification('Member updated!', 'success');
} catch (err) {
return this.props.addNotification(getFirstError(err), 'error');
}
}
and the HoC
graphql(getFullMemberDetails, {
options: (ownProps): Object => ({
id: ownProps.id,
}),
}),
graphql(updateMember, {
props: ({ mutate }): {updateMember: (input: Object) => Promise<any> } => ({
updateMember: (input): Promise<any> => mutate({
variables : { input },
options : (props): Object => ({
refetchQueries: [{
query : getFullMemberDetails,
variables : { id: props.id },
}],
}),
}),
}),
}),
graphql(updateMemberInsurance, {
props: ({ mutate }): {submitInsurance: () => {}} => ({
submitInsurance: (input): Promise<any> => mutate({
variables: { ...input },
}),
}),
}),
This async method works fine, but the getFullMemberDetails query doesn't run after those two queries finish.
However, when I move refetchQueries outside of options in the HoC, like this:
graphql(updateMember, {
props: ({ mutate, ownProps }): {updateMember: (input: Object) => Promise<any> } => ({
updateMember: (input): Promise<any> => mutate({
variables : { input },
refetchQueries: [{
query : getFullMemberDetails,
variables : { id: ownProps.id },
}],
}),
}),
}),
my getFullMemberDetails does run, but i get an error like this:
{"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"getFullMemberDetails"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"id"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"ID"}}}}],"directives":[],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"member"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"id"},"value":{"kind":"Variable","name":{"kind":"Name","value":"id"}}}],"directives":[],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"},"arguments":[],"directives":[]},{"kind":"Field","name":{"kind":"Name","value":"created_at"},"arguments":[],"directives":[]},{"kind":"Field","name":{"kind":"Name","value":"date_of_birth"},"arguments":[],"directives":[]},{"kind":"Field","name":{"kind":"Name","value":"first_name"},"arguments":[],"directives":[]},{"kind":"Field","name":{"kind":"Name","value":"last_name"},"arguments":[],"directives":[]},{"kind":"Field","name":{"kind":"Name","value":"name"},"arguments":[],"directives":[]},{"kind":"Field","name":{"kind":"Name","value":"email"},"arguments":[],"directives":[]},{"kind":"Field","name":{"kind":"Name","value":"phone"},"arguments":[],"directives":[]},{"kind":"Field","name":{"kind":"Name","value":"verified"},"arguments":[],"directives":[]},{"kind":"Field","name":{"kind":"Name","value":"informed_consent"},"arguments":[],"directives":[]},{"kind":"Field","name":{"kind":"Name","value":"postal_address"},"arguments":[],"directives":[],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"street_address_1"},"arguments":[],"directives":[]},{"kind":"Field","name":{"kind":"Name","value":"street_address_2"},"arguments":[],"directives":[]},{"kind":"Field","name":{"kind":"Name","value":"city"},"arguments":[],"directives":[]},{"kind":"Field","name":{"kind":"Name","value":"state"},"arguments":[],"directives":[]},{"kind":"Field","name":{"kind":"Name","value":"zip_code"},"arguments":[],"directives":[]},{"kind":"Field","name":{"kind":"Name","value":"country"},"arguments":[],"directives":[]},{"kind":"Field","name":{"kind":"Name","value":"__typename"}}]}},{"kind":"Field","name":{"kind":"Name","value":"user"},"arguments":[],"directives":[],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"},"arguments":[],"directives":[]},{"kind":"Field","name":{"kind":"Name","value":"time_zone"},"arguments":[],"directives":[]},{"kind":"Field","name":{"kind":"Name","value":"calendar"},"arguments":[],"directives":[],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"},"arguments":[],"directives":[]},{"kind":"Field","name":{"kind":"Name","value":"__typename"}}]}},{"kind":"Field","name":{"kind":"Name","value":"__typename"}}]}},{"kind":"Field","name":{"kind":"Name","value":"cohort"},"arguments":[],"directives":[],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"},"arguments":[],"directives":[]},{"kind":"Field","name":{"kind":"Name","value":"name"},"arguments":[],"directives":[]},{"kind":"Field","name":{"kind":"Name","value":"customer"},"arguments":[],"directives":[],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"},"arguments":[],"directives":[]},{"kind":"Field","name":{"kind":"Name","value":"name"},"arguments":[],"directives":[]},{"kind":"Field","name":{"kind":"Name","value":"__typename"}}]}},{"kind":"Field","name":{"kind":"Name","value":"basic_visits_covered"},"arguments":[],"directives":[]},{"kind":"Field","name":{"kind":"Name","value":"specialist_visits_covered"},"arguments":[],"directives":[]},{"kind":"Field","name":{"kind":"Name","value":"dependents_allowed"},"arguments":[],"directives":[]},{"kind":"Field","name":{"kind":"Name","value":"contract_term"},"arguments":[],"directives":[],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"start_at"},"arguments":[],"directives":[]},{"kind":"Field","name":{"kind":"Name","value":"end_at"},"arguments":[],"directives":[]},{"kind":"Field","name":{"kind":"Name","value":"in_person_supported"},"arguments":[],"directives":[]},{"kind":"Field","name":{"kind":"Name","value":"__typename"}}]}},{"kind":"Field","name":{"kind":"Name","value":"__typename"}}]}},{"kind":"Field","name":{"kind":"Name","value":"care_team"},"arguments":[],"directives":[],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"},"arguments":[],"directives":[]},{"kind":"Field","name":{"kind":"Name","value":"care_navigator"},"arguments":[],"directives":[],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Frabundle.esm.js:63)
at bundle.esm.js:1246
at bundle.esm.js:1558
at Set.forEach (<anonymous>)
at bundle.esm.js:1556
at Map.forEach (<anonymous>)
at QueryManager.push.../../../node_modules/apollo-client/bundle.esm.js.QueryManager.broadcastQueries (bundle.esm.js:1554)
at bundle.esm.js:1160
and my cache/props doesn't get updated. Is there something I'm doing wrong?

How to resolve TS2345 compile error in React

I try to write setState method in React.
But compile error occurs.
I want to know how to solve this issue.
frontend: react/typesript
articleApi.tsx
import axios from 'axios';
import {Article} from '../articleData';
export const getSingleArticleFactory = (id: string) => {
const getSingleArticle = async (id: string) => {
try {
const response = await axios.get('/api/article/' + id);
if (response.status !== 200) {
throw new Error('Server Error');
}
const article: Article = response.data;
return article;
} catch (err) {
throw err;
}
};
return getSingleArticle;
};
Detail.tsx
//import
interface ArticleState {
article: Article;
redirect: boolean;
user: firebase.User | null;
}
class Detail extends React.Component<
RouteComponentProps<{id: string}>,
ArticleState
> {
constructor(props: RouteComponentProps<{id: string}>) {
super(props);
this.state = {
article: {
id: 0,
title: '',
content: '',
imageNames: [],
},
redirect: false,
user: null,
};
this.getArticle = this.getArticle.bind(this);
this.deleteArticle = this.deleteArticle.bind(this);
}
getArticle() {
this.setState({
//error occures here
article: api.getSingleArticleFactory(this.props.match.params.id);,
});
}
articleData.tsx
export interface Article {
id: number;
title: string;
content: string;
imageNames: ImageName[];
}
interface ImageName {
name: string;
}
I expect there is no compile error.
But the actual is not.
I want to know how to solve this issue.
const getSingleArticleFactory: (id: string) => (id: string) => Promise<Article>
Argument of type '{ article: (id: string) => Promise<Article>; }' is not assignable to parameter of type 'ArticleState | ((prevState: Readonly<ArticleState>, props: Readonly<RouteComponentProps<{ id: string; }, StaticContext, any>>) => ArticleState | Pick<ArticleState, "article"> | null) | Pick<...> | null'.
Type '{ article: (id: string) => Promise<Article>; }' is not assignable to type 'Pick<ArticleState, "article">'.
Types of property 'article' are incompatible.
Type '(id: string) => Promise<Article>' is missing the following properties from type 'Article': id, title, content, imageNamests(2345)
I fixed it in following way:
async getArticle() {
const getSingleArticle = api.getSingleArticleFactory();
const articleData = await getSingleArticle(this.props.match.params.id);
this.setState({
article: articleData,
});
}
export const getSingleArticleFactory = () => {
const getSingleArticle = async (id: string) => {
try {
const response = await axios.get('/api/article/' + id);
if (response.status !== 200) {
throw new Error('Server Error');
}
const article: Article = response.data;
return article;
} catch (err) {
throw err;
}
};
return getSingleArticle;
};

Categories

Resources