flowtype error on react-redux 'connect' - javascript

I'm trying to make my react native redux app strongly typed. I'm having difficulty to get rid of flow errors in connect
type State = {
get: (thingToGet: string) => (propertyOfThingToGet: string) => {}
}
const mapStateToProps = (state: State): Object => ({
title: state.get('alertModal').get('alertModalTitle'),
isOpen: state.get('alertModal').get('isAlertModalOpen'),
message: state.get('alertModal').get('alertModalMessage'),
viewHeight: state.get('alertModal').get('height'),
hasYesNo: state.get('alertModal').get('hasYesNo')
})
const mapDispatchToProps = (dispatch: Dispatch<*>): Object => {
return bindActionCreators(
{
updateAlertModalHeight,
updateAlertModalIsOpen
},
dispatch
)
}
type Props = {
isOpen: boolean,
title: string,
message: string,
viewHeight: number,
updateAlertModalHeight: () => {},
hasYesNo: boolean,
yesClicked: boolean,
updateAlertModalIsOpen: () => {}
}
class AlertModal extends Component<Props, State> {
render(): React$Element<*> {
console.log('AlertModal')
return (
<View style={alertModalStyle.container}>
<PresentationalModal
style={{ backgroundColor: 'transparent' }}
isOpen={this.props.isOpen}
title={this.props.title}
message={this.props.message}
updateAlertModalHeight={this.props.updateAlertModalHeight}
viewHeight={this.props.viewHeight}
hasYesNo={this.props.hasYesNo}
yesClicked={this.props.yesClicked}
updateAlertModalIsOpen={this.props.updateAlertModalIsOpen}
/>
</View>
)
}
}
export default connect(mapStateToProps, mapDispatchToProps)(AlertModal)
On the bottom line of that code I get this error:
[flow] call of call of connect (Function cannot be called on any
member of intersection type intersection Member 1: function type
Error: function type Callable signature not found in statics of
AlertModal Member 2: function type Error: React.Component Too many
type arguments. Expected at most 2 See type parameters of definition
here)
I've been looking online for solutions but a lot of them seem abstruse and involved for a beginner like me so I couldn't manage to implement them in my code without causing flow errors. How do I get rid of the flow error?

Related

Invalid number of arguments in useReducer

export function React.useReducer<(state: PasswordResetFormState, action: PasswordResetAction) => PasswordResetFormState,PasswordResetFormState>reducer: (state: PasswordResetFormState, action: PasswordResetAction) => PasswordResetFormState, initializerArg: PasswordResetFormState, initializer: (arg: PasswordResetFormState) => ReducerStateWithoutAction<(state: PasswordResetFormState, action: PasswordResetAction) => PasswordResetFormState>): [ReducerStateWithoutAction<(state: PasswordResetFormState, action: PasswordResetAction) => PasswordResetFormState>, DispatchWithoutAction]
An alternative to useState.
useReducer is usually preferable to useState when you have complex state logic that involves multiple sub-values. It also lets you optimize performance for components that trigger deep updates because you can pass dispatch down instead of callbacks.
Version:
16.8.0
See also:
https://reactjs.org/docs/hooks-reference.html#usereducer
react
`useReducer(reducer: R, initialState: ReducerState , initializer?: undefined): [ReducerState , Dispatch >]`, `useReducer(reducer: R, initializerArg: ReducerStateWithoutAction , initializer?: undefined): [ReducerStateWithoutAction , DispatchWithoutAction]`, `useReducer(reducer: R, initializerArg: I & ReducerState , initializer: (arg: (I & ReducerState )) => ReducerState ): [ReducerState , Dispatch >]`, `useReducer(reducer: R, initializerArg: I, initializer: (arg: I) => ReducerState ): [ReducerState , Dispatch >]`
I am getting the above typescript error in my usereducer hook.
const [state, dispatch] = useReducer(
passwordResetReducer,
initialPasswordResetFormState
);
Invalid number of arguments, expected 3.
export const passwordResetReducer = (
state: PasswordResetFormState,
action: PasswordResetAction
): PasswordResetFormState => {
switch (action.case) {
default:
return { ...state, [action.field]: action.value };
}
};
export const initialPasswordResetFormState: PasswordResetFormState = {
email: "",
password1: "",
password2: "",
auth: "",
};
export interface PasswordResetFormState {
email: string;
password1: string;
password2: string;
auth: string;
}
export enum PasswordResetActionCase {
GENERAL = "GENERAL",
}
export type PasswordResetAction = {
case: PasswordResetActionCase.GENERAL;
value: string;
field: string;
};
I have spent ages on this, google returns nothing and I'm at a complete loss. If anyone can notice anything obvious in the code that should be changed I would hugely appreciate the help. I have multiple forms setup in the same way, and the error is common across every single one of them. I first noticed the errors when someone else in the same codebase shared their screen, they weren't showing up in my webstorm. After a fresh installation they came up for me, and I have spent the entire night trying to fix them to no avail.
State also shows as type never in the usereducer initialisation
solved - running npm install --save #types/react removes the errors

Can't assign specific type as an argument to a generic

I am trying to set an interface where I can pass arguments without specifying its type.
// Wrapper.tsx
interface Props extends MenuProps, ChakraProps {
handleTagClick: <T>(args?: T) => void
}
export const Wrapper: FC<Props> = ({ handleTagClick }): JSX.Element => {
return(
<Button
{...(
handleTagClick && {
onClick: () => handleTagClick({ ...dummyQuestion, type }) })
}
/>
)
}
Then I call that component where the error comes:
export const Container = () => {
const { addNewThing } = useMyHook()
// The error comes from this line
return <Wrapper handleTagClick={addNewThing} />
}
The error:
Type '(question: VariableConfig) => void' is not assignable to type '<T>(args?: T | undefined) => void'.
Types of parameters 'question' and 'args' are incompatible.
Type 'T | undefined' is not assignable to type 'VariableConfig'.
Type 'undefined' is not assignable to type 'VariableConfig'.
Wrapper.tsx(10, 3): The expected type comes from property 'handleTagClick' which is declared here on type 'IntrinsicAttributes & Partial<Props> & { children?: ReactNode; }'
What am I doing wrong?
You designed the component so that it is accepting the function with the argument of type unknown among the props. That means, inside the component, it supposed that this function could be provided with such an argument. When you pass the function accepting an argument of only VariableConfig type it might result in an error if it would have an argument of another type (unknown). It is not expected. To fix that:
interface Props extends MenuProps, ChakraProps {
handleTagClick: (args: VariableConfig) => void
}
UPD. Since my answer seems not complete I added more details in the explanation. I am breaking them into paragraphs.
First of all. TypeScript is essentially a framework allowing you to mountain type safety. That means it tracks all possible (as possible) states of all your variables and says if some of them are not safe. It is when a variable could be bound to a value of a type that is not allowed. You know it on the stage of compilation. But if you ignore it could result in a runtime error.
I don't know the business logic of your app to judge what you really need. But if you have a component that is accepting the function that in its turn is accepting an argument of type unknown and then you give the function that is accepting the constrain of unknown it is a dangerous situation. Imagine, the component inside its logic gives this function a value outside the constrain. This will mean the possible runtime error.
For me it says you have a flaw in the design of the component. What you need is to audit the logic inside your app.
The generic
interface Props extends MenuProps, ChakraProps {
handleTagClick: <T>(args?: T) => void;
}
does not have a lot of sense. Actually, this is the same as to express
interface Props extends MenuProps, ChakraProps {
handleTagClick: (args?: unknown) => void;
}
Possible solutions. You can turn off the job that TypeScript is doing. You can do it by this:
return <Wrapper handleTagClick={addNewThing as any} />;
playground
But in this case, you will come to uncertainty and TypeScrip is no longer going to help you.
Second solution. Write two different components. One with handleTagClick accepting arguments of all types and the second accepting only VariableConfig.
interface Props extends MenuProps, ChakraProps {
handleTagClick: (args?: unknown) => void;
}
interface PropsVariableConfig extends MenuProps, ChakraProps {
handleTagClick: (args: VariableConfig) => void;
}
export const Wrapper: FC<Props> = ({ handleTagClick }): JSX.Element => {
return (
<Button
{...{
onClick: () => handleTagClick({ ...dummyQuestion, type }),
}}
/>
);
};
export const WrapperVariableConfig: FC<PropsVariableConfig> = ({
handleTagClick,
}): JSX.Element => {
return (
<Button
{...{
onClick: () => handleTagClick(dummyQuestion),
}}
/>
);
};
export const Container = () => {
const { addNewThing } = useMyHook();
return <WrapperVariableConfig handleTagClick={addNewThing} />;
};
playground
The last thing you can do is to add a kinda type guard wrapper for function. It will filter arguments throwing an error when the argument is of an unsupported type.
declare const isVariableConfigGuard = (x: unknown): x is VariableConfig;
const wrapInVariableConfigGuard = (
f: (question: VariableConfig) => void
): ((args?: unknown) => void) => (args?: unknown) => {
if (isVariableConfigGuard(args)) {
return f(args);
}
throw Error('An argument is of not supported type');
};
export const Container = () => {
const { addNewThing } = useMyHook();
return <Wrapper handleTagClick={wrapInVariableConfigGuard(addNewThing)} />;
};
playground
But in this case, you need to have an error boundary on the upper level in your app. This pattern is to make the logic less transparent and more confusing.

NGRX entity updateOne not working: id undefined

I decided to ask for help, I just cannot get my head around NGRX Entity! (This code was created initially by NX ).
I have followed the NGRX Entity guide, I have also looked at loads of tutorial videos but I still cannot get NGRX Entity updateOne to work.
Getting this error below - I can load the entities into the store with no issue, and these are building my UI fine.
I have an Entity collection of buttons and want to update the Store State of a button when clicked - that's all!
(any ideas why this is not working??)
ERROR TypeError: Cannot read property 'id' of undefined
at http://localhost:4200/vendor.js:83815:26
at Array.filter (<anonymous>)
at updateManyMutably (http://localhost:4200/vendor.js:83811:27)
at updateOneMutably (http://localhost:4200/vendor.js:83801:16)
at Object.operation [as updateOne] (http://localhost:4200/vendor.js:83622:27)
at http://localhost:4200/main.js:1169:28
at http://localhost:4200/vendor.js:88532:26
at reducer (http://localhost:4200/main.js:1173:12)
at http://localhost:4200/vendor.js:87072:20
at combination (http://localhost:4200/vendor.js:86960:37)
This is the code I have so far:
// state
export interface QuickButton {
id: number;
isSelected: boolean;
title: string;
linkUrl: string;
}
// in component
this.store.dispatch( actions.setQuickFilter( evt ) );
// evt = {id: 1, isSelected: true, linkUrl: "", title: "Video"}
// in actions
export const setQuickFilter = createAction(
'[QuickBar] setQuickFilter',
props<{update: Update<QuickButton>}>()
);
// in reducer
export const QUICKBAR_FEATURE_KEY = 'quickBar';
export interface State extends EntityState<QuickButton> {
selectedId?: string | number; // which QuickBar record selected
loaded: boolean; // has the QuickBar list been loaded
error?: string | null; // last none error (if any)
}
export interface QuickBarPartialState {
readonly [QUICKBAR_FEATURE_KEY]: State;
}
export const quickBarAdapter: EntityAdapter<QuickButton> = createEntityAdapter<QuickButton>();
export const initialState = quickBarAdapter.getInitialState({
// set initial required properties
loaded: false,
});
const quickBarReducer = createReducer(
initialState,
on(QuickBarActions.loadQuickBarSuccess, (state, action) =>
quickBarAdapter.addAll( action.quickBar, state )
),
on(QuickBarActions.loadQuickBarFailure, (state, { error }) => ({
...state,
error,
})),
on(QuickBarActions.setQuickFilter, (state, {update}) => {
/// **** This is NOT Working *****
return quickBarAdapter.updateOne( update, state);
}
)
);
export function reducer(state: State | undefined, action: Action) {
return quickBarReducer(state, action);
}
export const {
selectIds,
selectEntities,
selectAll,
selectTotal,
} = quickBarAdapter.getSelectors();
You're dispatching your action incorrectly.
this.store.dispatch(actions.setQuickFilter(evt));
should be
this.store.dispatch(actions.setQuickFilter({ update: evt }));
Yay!! finally.
This was a real dumb error - from not understanding Entity.
Lots of trial and error & logging to solve this!
Solution:
Change the dispatch call in component from:
this.store.dispatch( actions.setQuickFilter( {update: evt} } ) );
to:
this.store.dispatch( actions.setQuickFilter( {update: {id: evt.id, changes: evt} } ) );
Now all my subscribed features will be able to use the updated values in the buttons to control their own UI elements. Finally!

TypeScript React Context API

I created a User Context API (code here: https://pastebin.pl/view/e0b5e0f4), then I can read it like this:
const { state: { user }, dispatch} = useUserContext();
but I want to do it like this (array instead of object):
const [{ user }, dispatch] = useUserContext();
So I change UserProvider to:
export const UserProvider: React.FC<UserProviderProps> = ({
reducer,
initialState,
children,
}) => (
<UserContext.Provider value={useReducer(reducer, initialState)}>
{children}
</UserContext.Provider>
);
And I got tip error message:
Type '[UserProps, Dispatch<UserActions>]' is not assignable to type 'UserContextProps'.ts(2322)
index.d.ts(337, 9): The expected type comes from property 'value' which is declared here on type 'IntrinsicAttributes & ProviderProps<UserContextProps>'
I'm a bit new at TypeScript and I'm stuck here 🤔

What is the correct to create a interface for action object with react hooks and typescript

I am working with react hooks and typescript. I used useReducer() for global state. The action of the reducer function contains two properties name and data. name means the name of event or change and data will be particular data required for that particular name.
There are four value for name till now. If name "setUserData" then data should IUserData(interface). If name is setDialog then data should DialogNames(type containing two strings). And if its something else then data is not required.
//different names of dialog.
export type DialogNames = "RegisterFormDialog" | "LoginFormDialog" | "";
//type for name property in action object
type GlobalStateActionNames =
| "startLoading"
| "stopLoading"
| "setUserData"
| "setDialog";
//interface for main global state object.
export interface IGlobalState {
loading: boolean;
userData: IUserData;
dialog: DialogNames;
}
interface IUserData {
loggedIn: boolean;
name: string;
}
//The initial global state
export const initialGlobalState: IGlobalState = {
loading: false,
userData: { loggedIn: false, name: "" },
dialog: ""
};
//The reducer function which is used in `App` component.
export const GlobalStateReducer = (
state: IGlobalState,
{ name, data }: IGlobalStateAction
): IGlobalState => {
switch (name) {
case "startLoading":
return { ...state, loading: true };
case "stopLoading":
return { ...state, loading: false };
case "setUserData":
return { ...state, userData: { ...state.userData, ...data } };
case "setDialog":
return { ...state, dialog: data };
default:
return state;
}
};
//The interface object which is passed from GlobalContext.Provider as "value"
export interface GlobalContextState {
globalState: IGlobalState;
dispatchGlobal: React.Dispatch<IGlobalStateAction<GlobalStateActionNames>>;
}
//intital state which is passed to `createContext`
export const initialGlobalContextState: GlobalContextState = {
globalState: initialGlobalState,
dispatchGlobal: function(){}
};
//The main function which set the type of data based on the generic type passed.
export interface IGlobalStateAction<
N extends GlobalStateActionNames = GlobalStateActionNames
> {
data?: N extends "setUserData"
? IUserData
: N extends "setDialog"
? DialogNames
: any;
name: N;
}
export const GlobalContext = React.createContext(initialGlobalContextState);
My <App> component looks like.
const App: React.SFC = () => {
const [globalState, dispatch] = React.useReducer(
GlobalStateReducer,
initialGlobalState
);
return (
<GlobalContext.Provider
value={{
globalState,
dispatchGlobal: dispatch
}}
>
<Child></Child>
</GlobalContext.Provider>
);
};
The above approach is fine. I have to use it like below in <Child>
dispatchGlobal({
name: "setUserData",
data: { loggedIn: false }
} as IGlobalStateAction<"setUserData">);
The problem is above approach is that it makes code a little longer. And second problem is I have to import IGlobalStateAction for not reason where ever I have to use dispatchGlobal
Is there a way that I could only tell name and data is automatically assigned to correct type or any other better way. Kindly guide to to the correct path.
Using useReducer with typescript is a bit tricky, because as you've mentioned the parameters for reducer vary depending on which action you take.
I came up with a pattern where you use classes to implement your actions. This allows you to pass typesafe parameters into the class' constructor and still use the class' superclass as the type for the reducer's parameter. Sounds probably more complicated than it is, here's an example:
interface Action<StateType> {
execute(state: StateType): StateType;
}
// Your global state
type MyState = {
loading: boolean;
message: string;
};
class SetLoadingAction implements Action<MyState> {
// this is where you define the parameter types of the action
constructor(private loading: boolean) {}
execute(currentState: MyState) {
return {
...currentState,
// this is how you use the parameters
loading: this.loading
};
}
}
Because the state update logic is now encapsulated into the class' execute method, the reducer is now only this small:
const myStateReducer = (state: MyState, action: Action<MyState>) => action.execute(state);
A component using this reducer might look like this:
const Test: FunctionComponent = () => {
const [state, dispatch] = useReducer(myStateReducer, initialState);
return (
<div>
Loading: {state.loading}
<button onClick={() => dispatch(new SetLoadingAction(true))}>Set Loading to true</button>
<button onClick={() => dispatch(new SetLoadingAction(false))}>Set Loading to false</button>
</div>
);
}
If you use this pattern your actions encapsulate the state update logic in their execute method, which (in my opinion) scales better, as you don't get a reducer with a huge switch-case. You are also completely typesafe as the input parameter's types are defined by the action's constructor and the reducer can simply take any implementation of the Action interface.

Categories

Resources