Invalid number of arguments in useReducer - javascript

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

Related

useQuery used in custom hook returns a response with type useQueryResults<unknown, unknown>

I have the following custom hook called useGetQuery
type Props = Parameters<typeof useQuery>;
const useGetQuery = (...params: Props) => {
const useQueryResults = useQuery(params);
useFocusEffect(
React.useCallback(() => {
useQueryResults.refetch();
}, [useQueryResults])
);
return useQueryResults;
};
Which I then call in useGetScreen like this:
export const useGetScreen = (screenId: string) => {
return useGetQuery(['getScreen', { screenId }], () => getScreen(screenId), { staleTime: 0 });
};
And useGetScreen is called like this
const { data: screen } = useGetScreen('vegetables');
My problem is that in this case, article has a type of unknown and I can't figure out why is this happening. My guess is that I have to somehow type what is being returned by useFocusRefetchQuery. Before I implemented the custom hook, Typescript automatically inferred the return type of useQuery
I don't recall typescript infering anything from the GQL request string, in my experience you need to provide useQuery the expected output and input (variables) types as mentioned here in details. The useQuery signature is as follow:
useQuery<TData = any, TVariables = OperationVariables>
In your case you would have to add both Props and your article type, say Article:
import { gql, QueryHookOptions, useQuery } from '#apollo/client';
const GET_ARTICLE_QUERY = gql`
query getArticle {
article: {
id,
articleId,
articleName,
}
}
`
interface ArticleContent {
__typename: "Article";
id: string;
articleId: string;
articleName: string;
}
interface Article {
article: ArticleContent | null;
}
interface ArticleInput {
articleId: string;
}
const useArticleQuery = (options: QueryHookOptions = {}) => {
return useQuery<Article, ArticleInput>(GET_ARTICLE_QUERY);
}
I think its also a good idea to keep the options (QueryHookOptions) in here so you can use your hook in different contexts.
You can use a tool like Graphql code generator to automatically generate the static types Article and ArticleInput for you (based on your schema and your JS gql requests).
If you want to have a generic hook for several useQuery that all look the same (for example they all use useFocusEffect), you will have to do something like this:
function useFocusRefetchQuery<TData, TVariables>(){
const useQueryResults = useQuery<TData, TVariables>();
// ...
}
This way you can use useFocusRefetchQuery instead of useQuery, but I really think you will have to pass both the input/output types, which means in the previous example you would have:
const useArticleQuery = (options: QueryHookOptions = {}) => {
return useFocusRefetchQuery<Article, ArticleInput>(GET_ARTICLE_QUERY);
}

Any change to redux store my causes component to re-render

I'm doing some testing on my UI and I've noticed that if any state changes in my redux store my component (shown below) re-renders and restarts with embedded video at 0. If I type in a redux-connected text field, it remounts, if a status notification hits the store, it remounts, etc.
I have no idea how to fix this and I could really use some help figuring out how to go after the bug.
tldr; How can I stop my VideoPlayer from re-rendering each time something changes in my redux store?
redux-toolkit
react
component
const MyComponent = () => {
...
// data used in the VideoPlayer is descructured from this variable:
const formState = useSelector(selectDynamicForm);
// renders output here in the same component
return (
...
{sourceContentFound === false ? (
<VideoPlayerDisabled />
) : (
<VideoPlayerController
title={title}
description={description}
sourceAddress={iframeURL}
author={authorName}
timeStamp={{ startTime: 0 }}
/>
)}
)
...
}
formSlice
export const dynamicFormSlice = createSlice({
name: 'dynamicForm',
initialState,
reducers: {
updateField: (state, action) => {
state = action.payload;
return state;
}
},
});
export const selectDynamicForm = createSelector(
(state: RootState): dynamicFormState => state.dynamicForm,
dynamicForm => dynamicForm
);
statusHandlerSlice
I don't think this component does anything crazy, per-say, but I have a notification appear when the video conditions are met. When it goes back down clearStatus the video player restarts.
export const statusHandlerSlice = createSlice({
name: 'statusHandler',
initialState,
reducers: {
setStatus: (state, action: PayloadAction<IStatusObject>) => {
const { type, display, message } = action.payload;
state.status = {
...action.payload,
message: message.charAt(0).toUpperCase() + message.slice(1),
};
if (display === 'internal-only' || display === 'support-both') {
statusLogger(type, message);
}
},
clearStatus: state => {
state.status = {
type: 'success',
data: {},
message: '',
display: 'internal-only',
key: '',
};
},
},
});
export const { setStatus, clearStatus } = statusHandlerSlice.actions;
export const selectStatus = (state: RootState): IStatusObject =>
state.statusHandler.status;
Your MyComponent is re-render every time redux store state change is because you have a selector in it
You could stop this to happen by, add an equalityFn to useSelector.
You can write your own equalityFn or use some existing function from a library that supports deep comparison.
Ex: Use lodash isEqual
import { isEqual } from 'lodash';
const MyComponent = () => {
...
// data used in the VideoPlayer is descructured from this variable:
const formState = useSelector(selectDynamicForm, isEqual);
By default, useSelector use a shallow compare which can't detect deep changes inside your object, change to a deep comparison function like isEqual will help you to do that, but It's not recommended for all selector since there will be a performance impact.
Live Example:
I suggest either creating a custom equalFn to compare the data you're using in the current component or do not select the whole slice, maybe some properties change is unnecessary for your component. like:
const { data } = useSelector(store => store.sliceA, shallowEq);
// console.log(data) => { a: "foo", b: "bar" }
// data.b is useless but once it is changed, the component will re-render as well
return <Typography>{data.a}</Typography>
You should install React Devtools, turn on profiler, remember to check Record why each component rendered while profiling in setting to see what is causing re-rendering. sometimes custom hooks in libraries trigger re-rendering.
whyDidYouRender
is a good choice too

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!

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.

flowtype error on react-redux 'connect'

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?

Categories

Resources