I am trying to create a re-usable component that renders a 'select' form item and its associated options. It accepts redux prop that is an action creator that is responsible for passing the selected option into the redux store to be used throughout the application.
I have two action creators that are possible options:
The first is :
setCompany: (state, action: PayloadAction<string>) => {
state.selectedCompany = action.payload;
},
The second is:
setStatus: (state, action: PayloadAction<SelectStatus['status']>) => {
state.status = action.payload;
},
The JobState['status'] type is: status: 'success' | 'error' | 'default' | 'processing' | 'warning';
In the Select component, I attempt to do the following:
interface SelectsProps {
fetchOptionData?: () => void;
optionsArray?: [];
placeholder: string;
showSearch: boolean;
badges: boolean;
redux?: ActionCreatorWithPayload<string | SelectStatus['status']>;
// I also tried ActionCreatorWithPayload<string> | //ActionCreatorWithPayload<SelectStatus['status']> but neither is working
}
In my parent component, i try to pass in a redux action of setStatus, defined above. However, I receive an error of :
`Type 'ActionCreatorWithPayload<"success" | "error" | "default" | "processing" | "warning", string>' is not assignable to type 'ActionCreatorWithPayload<string, string>'.`
<Selects
redux={setStatus}
placeholder='Default'
showSearch={false}
badges={true}
optionsArray={badges}
/>
};
If I do it as a union, it seems to default to string and not accept a status to be passed if valid. Is there a way I can allow the correct payload type to be selected?
Thank you
Solution
You need a union of the two action creator types rather than an action creator of the union.
redux?: ActionCreatorWithPayload<string> | ActionCreatorWithPayload<SelectStatus["status"]>;
Explanation
ActionCreatorWithPayload<string | SelectStatus['status']> means an action creator that can be called with an argument of either string OR SelectStatus['status']. setCompany is ok because SelectStatus['status'] is a subset of string. But setStatus is an error because it can only accept SelectStatus['status']. It cannot accept the union.
You need the union of the two action creators which is ActionCreatorWithPayload<string> | ActionCreatorWithPayload<SelectStatus["status"]>. This means that you can have a function that accepts string or a function that accepts SelectStatus['status']. It no longer needs to accept the union of the two.
You may have problems when you call this union since you don't know which argument type it accepts.
Note: I don't know why you use SelectStatus["status"] is one place and JobState["status"] in the other. I am assuming that they are the same. I would extract that type to a named type:
export type JobStatus = JobState["status"]
Related
I have 3 mutations in a query. 6 input parameters. If (profile_status === true), then send the mutation. And so for each mutation. How to do it?
mutation updateProfile(
$input: UpdateProfileMutationInput!
$input2: UpdateUserEmailMutationInput!
$input3: UpdateUserPasswordMutationInput!
$profile_status: Boolean!
$email_status: Boolean!
$password_status: Boolean!
) {
#include(if: $profile_status) updateProfile(input: $input) {
...CoreUser
}
#include(if: $email_status) updateEmail(input: $input2) {
...CoreUpdateUserEmail
}
#include(if: $password_status) updatePassword(input: $input3) {
...CoreUpdateUserPassword
}
}
I use #apollo/client.
Include only works for fields. Is there a similar one for mutation?
From the specification:
The #skip directive may be provided for fields, fragment spreads, and inline fragments...
Implementation:
directive #skip(if: Boolean!) on FIELD | FRAGMENT_SPREAD | INLINE_FRAGMENT
As you can see, it doesn't have MUTATION location - it's not implemented for mutations. If you are the developer of the server, you can always create your own schema directive (guide for Apollo Server).
Let's say a service has a Subject, whose value you want to dynamically set from a component.
A.service.ts:
//ChartTools: zoom?: boolean, pan?: boolean
public ChartConfig$: BehaviorSubject<ChartTools> = new BehaviorSubject(this.defaultValues);
public changeChartConfig(config: ChartTools):void{
const currentValues = this.ptChartToolsConfig$.value;
this.ptChartToolsConfig$.next({
...currentValues,
pan: config.pan,
zoom: config.zoom
})
}
Now in component A,we want to set this subject dynamically through a function:
A.component.html:
<mat-checkbox
value="zoom"
[checked]= "ToolsConfig$.zoom"
(change) = changeChartConfig($event.value, $event.source._checked)
>
A.component.ts
ngOnInit():void{
private ToolsConfig$ = this.A_Service.ChartConfig$.subscribe();
}
//cTask['constant']= "zoom" | "pan"
changeChartConfig(component: cTask['constant'], checked: boolean):void{
this.A_Service.changeChartConfig({
component : checked
})
}
But, the input 'component' in changeChartConfig() is not even used and I get the error:
Argument of type '{ component: boolean; }' is not assignable to parameter of type 'ChartTools'.
Object literal may only specify known properties, and 'component' does not exist in type 'ChartTools'.
I understand It's saying 'component' doesn't exist in interface ChartTools, but the values of component exist in ChartTools, and I want to use them.
Can someone please help me with what can be done to resolve this?
You have a typescript issue here.
What your error is saying is that you are expecting params to be ChartTools, but you are sending in something that looks different. Check your interface to make sure everything fits in properly. Also if in interface you are lacking component property add it. If everything is fine reset your VScode
So lets say your ChartTools interface looks like this:
interface ChartTools {
property1: string;
property2: number;
component: boolean;
}
The error prevents you to send in a value that lacks property1 and property2, because it is not a ChartTool.
How to fix it:
either make property1 and property2 opitonal:
interface ChartTools {
property1?: string;
property2?: number;
component: boolean;
}
use TS Partials, or add the missing params to you component when you are sending data:
changeChartConfig(component: cTask['constant'], checked: boolean):void{
this.A_Service.changeChartConfig({
component : checked
missingProp1: 'Some value'
missingProp2: 'Another value'
})
}
EDIT
If you are trying to achieve dynamic property name do it this way:
this.A_Service.changeChartConfig({
[component] : checked
// [component] will be parsed instead of being sent in as a string
})
I am trying to define a type in flow such that you must specify either a Client or an Invoice. This is my attempt at doing that:
type Client = {
client: {
id: number,
},
invoice?: {
id: number,
},
};
type Invoice = {
client?: {
id: number,
},
invoice: {
id: number,
},
};
type Props = Client | Invoice;
If client is undefined, then invoice must be defined and vice-versa.
However, when I try to access properties based on this, the flow typechecker throws errors:
function getAssignedId({client, invoice}: Props) {
return client ? client.id : invoice.id;
}
results in:
22: return client ? client.id : invoice.id;
^ Cannot get `invoice.id` because property `id` is missing in undefined [1].
References:
21: function getAssignedId({client, invoice}: Asssignement) {
^ [1]
You can try it here.
Any idea why this is happening? Is there another way to achieve this with flow?
When you do deconstruct the type via:
{client, invoice}: Props
the types of those variables are resolved. client and invoice both are resolved as ?{ id: number } since they may or may not exist at the time. The typechecker does not remember that these two objects are linked, because you have separated them.
If you don't separate them, flow can keep track of the union type and do the right thing.
function getAssignedId(obj: Props) {
return obj.client ? obj.client.id : obj.invoice.id;
}
Try flow link
You are not checking if invoice is defined. If you add the check it will not throw the error
I want to create rootStore which contains others store. The problem is that the children contain properties like:
id: types.identifier(types.string),
And when I create the rootStore, I get an error from the child:
[mobx-state-tree] Error while converting {} to SomeModelStore: at path "/id" value undefined is not assignable to type: identifier(string) (Value is not a string), expected an instance of identifier(string) or a snapshot like identifier(string) instead.
I tried to use types.late but it did not help.
The solution I found is to wrap all properties into types.maybe
Examples:
error:
https://codesandbox.io/s/yvnznxyvyj?module=%2Fmodels%2FSomeModelStore.js
workaround:
https://codesandbox.io/s/0mv558yq50?module=%2Fmodels%2FSomeModelStore.js
Here https://codesandbox.io/s/yvnznxyvyj?module=%2Fmodels%2FSomeModelStore.js you create an empty object
.model("FirstStore", {
item: types.optional(SomeModelStore, {})
})
but type
SomeModelStore
didn't support empty fields. If you write like this
export const FirstStore = types
.model("FirstStore", {
item: types.optional(SomeModelStore, {
id: 'defaultId',
activate: false,
name: 'defaultName'
})
})
it will work. Or you can use "types.maybe" instead of "types.optional".
export const FirstStore = types
.model("FirstStore", {item: types.maybe(SomeModelStore)})
Also read about types.reference
I think it's a better way to use it in your case.
I'm trying to migrate an existing codebase to use Flow. Since this project started without Flow, I'm using a pretty typical JS pattern for enums and such.
Here are a few definitions I want to
export const LOAN_STATUS = {
PENDING: 'pending',
CURRENT: 'current',
DUE: 'due',
OVERDUE: 'overdue',
PENDING_PAYMENT: 'pending_payment',
CHARGED_OFF: 'charged_off',
VOIDED: 'voided',
DISPUTED: 'disputed',
REFUNDED: 'refunded',
SETTLED: 'settled',
}
export const ACTIVE_LOAN_STATUS = [
LOAN_STATUS.OVERDUE,
LOAN_STATUS.CURRENT,
LOAN_STATUS.DUE,
LOAN_STATUS.PENDING_PAYMENT,
]
Flow works fine until I import this file and it says I need to add type annotations. This seems odd -- why should I have to annotate objects that are entirely static and easily inferred?
Is there any way that define its type as "static" or "literal"?
So then I go about thinking how I'm going to add annotations to this. My first thought is just {[key: string]: string} and Array<string>. Flow works, but I'm realizing that these type definitions are totally worthless. So then I try this other approach:
type LoanStatusValues =
'pending' |
'current' |
'due' |
'overdue' |
'pending_payment' |
'charged_off' |
'voided' |
'disputed' |
'refunded' |
'settled'
type LoanStatusKeys =
'PENDING' |
'CURRENT' |
'DUE' |
'OVERDUE' |
'PENDING_PAYMENT' |
'CHARGED_OFF' |
'VOIDED' |
'DISPUTED' |
'REFUNDED' |
'SETTLED'
type ActiveLoanStatus =
"current" |
"due" |
"overdue" |
"pending_payment"
And I use the type annotations {[key: LoanStatusKeys]: LoanStatusValues} and Array<ActiveLoanStatus>. But even these annotations loose the fact that this is static!
It just seems so odd that I'm having to write this much duplicate code. And then if I want to convert just to Flow I can't actually use the types in JS. For example I might do this:
if (defs.ACTIVE_LOAN_STATUS.indexOf(loan.status) !== -1) {
}
Now if I want to use Flow types, I can't do anything like this:
type ActiveLoanStatus =
"current" |
"due" |
"overdue" |
"pending_payment"
if (loan.status isTypeOf ActiveLoanStatus) {
}
So how am I supposed to use these static enums? I must be doing this wrong!
To express an enum with flow you can use $Values utility in conjunction with frozen object type:
export const LOAN_STATUS = Object.freeze({
PENDING: 'pending',
CURRENT: 'current',
DUE: 'due',
OVERDUE: 'overdue',
PENDING_PAYMENT: 'pending_payment',
CHARGED_OFF: 'charged_off',
VOIDED: 'voided',
DISPUTED: 'disputed',
REFUNDED: 'refunded',
SETTLED: 'settled',
});
type LoanStatus = $Values<typeof LOAN_STATUS>;
export const ACTIVE_LOAN_STATUS: LoanStatus[] = [
LOAN_STATUS.OVERDUE,
LOAN_STATUS.CURRENT,
LOAN_STATUS.DUE,
LOAN_STATUS.PENDING_PAYMENT,
]
This works starting from 0.60.0 version.
Here is the most concise way to achieve this:
const activeLoanStatuses = {
current: 'current',
due: 'due',
overdue: 'overdue',
pending_payment: 'pending_payment'
};
const otherLoanStatuses = {
pending: 'pending',
charged_off: 'charged_off',
voided: 'voided',
disputed: 'disputed',
refunded: 'refunded',
settled: 'settled',
};
type ActiveLoanStatus = $Keys<typeof activeLoanStatuses>;
type LoanStatus = $Keys<typeof otherLoanStatuses> | ActiveLoanStatus;
const activeLoanStatusesMap: { [key: LoanStatus]: ?ActiveLoanStatus} = activeLoanStatuses;
if (activeLoanStatusesMap[loan.status]) {
}
While incredibly verbose, and non-scalable, this falls into Flow's "Disjoint Unions" case and such can be implemented using ===. As they mention on that page, Case Analysis is done via that operator, as javascript naturally does with switch-case statements.
In your case, that equates to:
switch(loan.status) {
'pending':
'current':
'due':
'overdue':
'pending_payment':
'charged_off':
'voided':
'disputed':
'refunded':
'settled':
// your behavior here
}
As I mentioned, this is highly verbose in code which uses your types, but to counter that, it has the benefit of defining your types without creating a boilerplate object- you simply define your literal options and union them together (your second implementation).
This has the obvious downside of coupling your type definition with your implementations of its consumers, so use with caution.