Typescript allows to call a function with incorrect parameter's type - reactjs

There is a function with next signature:
const verify = (address?: string) => void
There is a Component with props type:
type VerifyButtonProps = { onClick: () => void; }
There is a Component with props type:
type TButtonProps = { onClick?: React.MouseEventHandler<HTMLButtonElement>; children: React.ReactNode; };
[Codesanbox example]
(https://codesandbox.io/s/react-ts-playground-forked-v24gs7?file=/src/index.tsx/)
I'm getting the runtime error when click on the button and expect typescript points out to it, but compilation passes without any errors.
How can I prevent runtime error with help of typescript on the compiling step?

Your issue is basically following case (playground):
const verify = (address?: string) => address?.toLowerCase()
const verifyEmpty: () => void = verify
const onClick: (event: object) => void = verifyEmpty
onClick({ this: 'is not a string'})
Typescript allows each of these steps, however combined it produces a runtime error. This unsoundness is known, however Typescript does not guarantee soundness (no runtime errors if if there are no type errors) and this is one case where they decided to leave it unsound.
This means it is up to you to catch such errors. In your case, you could use onClick={() => verify()} to fix the error.

To avoid this situation you can replace
() => void
with
(...args: undefined[]) => void;
With that replacement you'll explicitly tell to your component, that function doesn't allow any number of arguments.
So, you can still pass verify function to your component. But inside of the component you can't pass it down to any function props with optional arguments, e.g. <Button onClick={verify} />

From the index.tsx file, the problem with your code is that your trying to run .toLowerCase() on an event.
Here is your code:
const verify = (address?: string) => { console.log("address = ", address?.toLowerCase());};
const App = (props) => {
return <VerifyButton onClick={verify} />;
};
I suggest you look into handlers but passing your function as you have in the onClick handler means that you get every argument passed to the verify function as address.
Log the address to console and see what I mean.
You may write your change handlers this way:
onClick={(e) => yourFunction(e)}
This is useful if you need something from the event, for example a value from an input.
OR
onClick={() => yourFunction()}
This will prevent you from passing unwanted arguments to your functions. Hope this helps.

u need to correctly type the verify function to match the expected onClick prop type in each component.
For VerifyButtonProps, the verify function can be passed like:
const VerifyButton: React.FC<VerifyButtonProps> = ({ onClick }) => (
<button onClick={onClick}>Verify</button>
);
const App = () => {
const handleVerify = () => {
verify();
};
return (
<div>
<VerifyButton onClick={handleVerify} />
</div>
);
};
For TButtonProps, the verify function needs to be converted to a proper React.MouseEventHandler:
const TButton: React.FC<TButtonProps> = ({ onClick, children }) => (
<button onClick={onClick}>{children}</button>
);
const App = () => {
const handleVerify = (event: React.MouseEvent<HTMLButtonElement>) => {
verify();
};
return (
<div>
<TButton onClick={handleVerify}>Verify</TButton>
</div>
);
};
when u make these changes, TypeScript will catch the type mismatch and display an error during the compilation step, rather than at runtime.

Related

TypeScript correct way to type onChange handler

I have a simple controlled input that looks like the following:
<textarea
name="description"
onChange={updateEdit}
value={edit}
placeholder={edit}
/>
my updateEdit handler
const updateEdit = (evt: React.ChangeEventHandler<HTMLTextAreaElement>) => {
const updatedEdit: Edit = { id: proposalId, value: evt.target.value };
dispatch(setEditForProposal(updatedEdit));
};
Property 'target' does not exist on type 'ChangeEventHandler<HTMLTextAreaElement>'
What's the correct way to type this?
Use
const updateEdit = (evt: React.ChangeEvent<HTMLTextAreaElement>) => {
The reason is you typed it as a ChangeEventHandler, which means a function that takes in a ChangeEvent. Or you could type the function as the handler
const updateEdit: React.ChangeEventHandler<HTMLTextAreaElement> = (evt) => {
I think you are typing the React.ChangeEventHandler in wrong place. should be
const updateEdit: React.ChangeEventHandler<HTMLTextAreaElement>=(event){}
it is for handler itself but you are using for the argument of the function

Test my react(button) component, mock function received 0 calls

Below is my script:
export interface InputProps {
value: string;
onSubmit: (value: string) => void;
onChange: (value: string) => void;
}
const InputBox: React.FC<InputProps> = (props: InputProps) => {
return (
<InputWrapper>
<InputField
value={props.value}
onSubmit={() => {
props.onSubmit;
console.log('onsubmit function is being called.');
}}
onChange={props.onChange}
button={true}
/>
</InputWrapper>
);
};
Below is my test:
describe('<InputBox />', () => {
const onSubmitMock = jest.fn();
const onChangeMock = jest.fn();
const Test = <InputBox onSubmit={onSubmitMock} onChange={onChangeMock} value="test" />;
it('called onSubmitMock when click submit', () => {
const { getAllByRole } = render(Test);
fireEvent.click(getAllByRole('button'));
expect(onSubmitMock).toHaveBeenCalled();
});
but it gives me error: Number of calls: 0
However, my onChange test is passed:
it('called onChangeMock when click submit', () => {
const { getAllByRole } = render(Test);
fireEvent.click(getAllByRole('button'));
expect(onChangeMock).toHaveBeenCalled();
});
The only difference b/t onChange and onSubmit is I have an arrow function for the onSubmit property.
Question one, why can't I call the onSubmitMock like the onChangeMock?
Question two (deleted)
Any idea of this? Huge thank in advance
Question one, why can't I call the onSubmitMock like the onChangeMock?
For a function to get called, someone must call it. If you write the code props.onChange with no parentheses, then you are just referencing a function, not calling it. So with both props.onSubmit; and onChange={props.onChange}, the function will not get called. Not on this line of code anyway...
But the onChange prop is designed so that if you pass a reference of a function into it, then react guarantees that it will call the function for you, if and when the change event happens. So later on when the event happens, react calls props.onChange for you.
With the onSubmit function, you've also passed a function into it: the anonymous arrow function you wrote, who's text is:
() => {
props.onSubmit;
console.log('onsubmit function is being called.');
}
When the event happens, react will call that function. Inside your custom function you can do whatever you like, including calling props.onSubmit. But you will be calling it yourself, not asking react (or anything else) to call it for you, so to call it yourself you need props.onSubmit()
Question two [...]
It's not clear to me what code you had that was causing the type error. If you show the code that caused the error, i'll edit this question to answer that
It seems you forgot to call the onSubmit callback function. Just add the missing parenthesis like this:
onSubmit={() => {
props.onSubmit();
}}
If the console.log is not needed, then this should work:
onSubmit={props.onSubmit}

Cannot invoke an object which is possibly 'undefined'. ReactTypescript by passing state setter as props

I am passing a state prop from parent to child:
Parent:
const [refresh, setRefresh] = React.useState<boolean>(true);
passing into Component as
<Chart setRefresh={setRefresh}/>
Child
interface IChart {
refreshChart?: boolean;
setRefresh?: (newRefresh: boolean) => void;
}
const Chart: React.FunctionComponent<IChart> = ({refreshChart, setRefresh}) => {
const handleRefresh = () => {
setRefresh(false);
}
}
Obviously it's to do with the type in the IChart interface as if I use any or not make it optional it works, but I need it optional and want to avoid any.
Obviously it's to do with the type in the IChart interface as if I use any or not make it optional it works, but I need it optional and want to avoid any.
If you want to keep the property optional, then you have to deal with the fact it's optional, either by making the call to it check first:
const Chart: React.FunctionComponent<IChart> = ({refreshChart, setRefresh}) => {
const handleRefresh = () => {
if (setRefresh) { // ***
setRefresh(false);
}
};
}
...or by having a default:
const Chart: React.FunctionComponent<IChart> = ({refreshChart, setRefresh = (b: boolean) => {}}) => {
// ^^^^^^^^^^^^^^^^^^^^^
const handleRefresh = () => {
setRefresh(false);
};
}
You might even make creating handleRefresh conditional based on whether setRefresh exists, and possibly even change what the component renders (e.g., no refresh UI) if setRefresh isn't provided.
it's because in IChart interface you're saying that this property is optional, so it is "possibly undefined". Just remove "?" from setRefresh if the value is required.
I do with Similar cases a simple if condition basically because if tweak it a teammate may come in the future and integrate it wrongly, or add a default value to it
const handleRefresh = () => {
if(setRefresh)
setRefresh(false);
}

How to update an array using setState with React Hooks on Strict Mode

I have this, every time addEmail is called, the updater callback for setEmails is called twice and therefore the new email is added twice:
const Container = (props: Props) => {
const [emails, setEmails] = useState<Array<string>>([]);
const addEmail = (email: string) => {
setEmails((prevState: string[]) => [...prevState, email]);
};
return (
<Wrapper>
{emails.map((email, index) => {
return (
<Email
key={index}
text={email}
onRemoveClicked={() => {}}
/>
);
})}
<Input onSubmit={addEmail} />
</Wrapper>
);
};
How am i supposed to do this on strict mode ?
You need to provide a lot more context here, This component doesn't look like a native html form. Email and Input both are custom components.
I would suggest you to try the below steps to debug this
Check if Input component is calling onSubmit twice.
React's useState setter method takes the new state as argument, you are passing a function here, that might be the problem. Try changing the addEmail function definition to below and see if it works
const addEmail = (newEmail: string) => setEmails([...emails, newEmail]);
Read More about React state hook.

Custom Unsaved Data Warning for react-router-dom BrowserRouter

I'm trying to listen to URL changes in my Redux app. If you have unsaved data on your form when attempting to change the route, I'd like a custom warning message to appear. If the user selects cancel, I'd like it to NOT change the route. How is this accomplished?
I see getUserConfirmation. The examples show that it takes a message and a callback. But my code is demanding zero parameters and a void return type.
How do I intercept routing events?
The problem was that TypeScript's definition did not match the underlying code.
So I used my magical skeleton key for all things TypeScript: any!
Set it up like this:
<Router getUserConfirmation={getUserConfirmation}>
Then I did something similar to the following:
const getUserConfirmation: any = (message: string, callback: (answer: boolean) => void) => {
ReactDOM.render(
<MyCustomDialog
onOkClicked={buildOkClicked(callback)}
onClose={buildCloseModal(callback)}
onClose={buildCloseModal(callback)}
/>,
someDiv
)
}
const buildCloseModal = (callback: (answer: boolean) => void) => {
return () => {
closeModal()
callback(false)
}
}
const buildOkClicked = (callback: (answer: boolean) => void) => {
return () => {
closeModal()
callback(true)
}
}
const closeModal = () => {
ReactDOM.render(
<div />,
someDiv
)
}
The key here is when declaring getUserConfirmation, use ": any" to get past any compilation issues

Resources