Typescript doesn't complain when misusing a function passed as a prop in React - reactjs

I have one React component defined as follows:
const BirthdaySearch: FC<{ onSearch: (year: string, month: string) => void }> =
(props) => {
const monthInputRef = useRef<HTMLInputElement>(null);
const dayInputRef = useRef<HTMLInputElement>(null);
const submitHandler = (e: FormEvent<HTMLFormElement>) => {
e.preventDefault();
const selectedDay = dayInputRef!.current!.value;
const selectedMonth = monthInputRef!.current!.value;
props.onSearch(selectedYear, selectedMonth);
};
return (
<form onSubmit={submitHandler}>
<div>
<div>
<label htmlFor="month">Month</label>
<input name="month" id="month" ref={monthInputRef} />
</div>
<div>
<label htmlFor="month">Day</label>
<input name="day" id="day" ref={dayInputRef} />
</div>
</div>
<Button>Find People By Birthday</Button>
</form>
);
};
export default BirthdaySearch;
The onSearch function is typed in the props of the element, but when it's misused in the parent component by mentioning fewer arguments than are defined in the child component, TypeScript doesn't mind:
const Birthdays: NextPage = () => {
const findBdHandler = () => {
// do things...
};
return <BirthdaySearch onSearch={findBdHandler} />
};
export default Birthdays;
TypeScript only complains if the types are wrong or there are more arguments than defined in the child. Is there a way to make this more strict?

The reason that typescript doesn't complain is that the code is perfectly valid: it is perfectly fine and common practice to ignore parameters for a function. For example, onClick handlers often get the event as a parameter, but if you don't care about the event you can just pass in a handler that doesn't take parameters.

Typescript or for that matter any other type checker would not throw an error when you use lesser params than specified (They would show an error if you mention a param as variable and then leave it unused).
If you want to make it strict to check the values then you should type it at the parent level then use the function in the child. This should throw an error when the child does not pass the required params.
Definitions flow from parent to child and not the other way.
Hopefully I've explained it clearly

Related

How to set state using useState and do something else as well in onChange in React?

I have a component which includes another component (from headlessui/react) defined as follows:
export default function MyComponent(props) {
const [selectedState, setState] = useState('');
return (
<div>
<RadioGroup value={selectedState} onChange={setState}>
...
</RadioGroup>
</div>
)
}
In the onChange I would like to first call a function which does something and then calls setState. However, nomatter what I try, I can't get this to work.
I've tried:
onChange={() => {
doSomethingFirst();
return setState;
}
onChange={() => {
doSomethingFirst();
// This requires an argument and I'm not sure what the argument should be
setState();
}
// Even this fails
onChange={() => setState;}
What do I do to get this working?
When you pass onChange directly to RadioGroup it will invoke your setState with any arguments the RadioGroup supplies. Because setState only takes one argument that's thereby equal to doing onChange={arg => setState(arg)} which already shows how to accomplish what you're trying to do. Just emulate this exact behaviour and add in your function call:
onChange={arg => {
doSomethingHere()
return setState(arg)
}}
This should works.
export default function MyComponent(props) {
const [selectedState, setState] = useState('');
const updateState = (ev)=> {
doSomethingHere();
...
setState()
}
return (
<div>
<RadioGroup value={selectedState} onChange={updateState}>
...
</RadioGroup>
</div>
);
}
ev object passed to updateState function contains <RadioGroup> element. You can inspect it with console.log to see what values it holds.
If you are trying to update the state according to the RadioGroup value, you must be able to read that value inside ev object.

How can I fix this Unit Test?

I'm fairly new to unit testing my .tsx files and I am currently having trouble testing this (sorry if the format is off)
//this is Banner.tsx
import React, {useCallback} from "react";
type Properties = {
close: () => void;
text: string;
const Banner: React.FC<Properties> = ({close, text}) => {
const onClick = useCallback(() => {
close();},
[close, text]);
return (
<div className = "BannerBox">
<div className = "banner">
<span className = "popup"> onClick={onClick}[x]
</span>
{text}
</div>
</div>
);
};
export default Banner;
//this is App.tsx
import Banner from "./Components/Banner";
function App(): JSX.Element {
const [isOpen, setIsOpen]=useState(false);
const toggleBanner = () => {
SetIsOpen(!isOpen);
};
return (
<div>
<input type = "button"
value = "popup"
onClick={toggleBanner}/>
<p>hi</p>
{isOpen && <Banner text = {"hello"} close={() => isOpen(false)}/>}
</div>
export default App;
this is what i have so far
//Banner.test.tsx
test("Check that all type Properties are being used", () => {
render(<Banner />);
})
it gives this error -> "type {} is missing the following properties from type Banner: close and text"
"type {} is missing the following properties from type Banner: close and text"
Read this error message carefully.
Banner is a functional component. That means it's a function that that takes it's props as an object. And it's typed to receive two props, close and text. These props are required.
But you are providing no props in your test. Since the props argument is always an object, and you have no props, then the props argument is an empty object.
So now that error tells you that your function expects an object, but the one you provided is missing the close and text props.
You need to satisfy the required props of your component. Whether you are in a test or not, the contract of those types must must be fulfilled.
That means you want something like this:
//Banner.test.tsx
test("Check that all type Properties are being used", () => {
render(<Banner text="Hello, World!" close={() => null} />);
})
In additional there several syntax errors in your components. And your code will be much easier to understand if you use proper indenting to inform you of the structure of your code.

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.

React Native: Reset children states from grandfather?

This is a tricky question. Lets say I have a Component <GrandFather /> with a <Form /> Component inside. The <Form /> has Multiple <Input /> Components. The <Input /> has an internal useState. At the <GrandFather />, there are some functions like loadForm(loading some fields) and unloadForm(removes these fields). On the unloadForm I wanna reset the internal State of the Inputs. The only solution I found was to have a key on the <Form /> and increment it on unload, so thats forces the rest. Is there a better good way to do this without change the logic? P.S I 'm using Typescript.
function GrandFather (props: Props) {
const loadForm = () => // load some fields to the formData
const unloadForm = () => // unload these fields to the formData
return <Form formData={formData}/>
}
function Form (formData: FormData) {
return (
<>
<Input /> // with some props
<Input /> // with some props
<Input /> // with some props
</>
)
}
function Input (props: Props) {
const [state, setState] = useState(false);
// the state here is being used for styling and animations, at
// somepoint it will became true
return <TextInput {...props}/>
}
any way here to reset this state to all the inputs on the function unloadForm?
I see two different methods of achieving this.
Method #1
As an example, I'll use a simple login form.
So you could define the following state variables on GrandParent:
const [username, setUsername] = useState('');
const [password, setPassword] = useState('');
Then, pass all username, setUsername, password, and setPassword as props to Form and then on to the Input components.
Inside of the Input component, you could turn the inputs into controlled inputs by adding the following:
<input type="text" value={username} onChange={setUsername} />
If you ever need to clear the inputs, you could call right directly from GrandParent or Form (or anywhere where you have access to the setters) the following:
setUsername('');
setPassword('');
Method #2 (hacky)
You could define, on GrandParent, a state variable that, as the above method, you would pass both the variable and setter as props to Form and then to each Input:
const [clear, setClear] = useState(false);
Then, inside the Input component, assuming you have a value state variable on it (with the equivalent setter setValue), you could set up a listener for a change on the state variable clear:
useEffect(() => {
if (clear === true) {
setValue('');
}
}, [clear]);
Then, whenever you wanted to clear all the input values, you could call, from anywhere:
setClear(true);
setTimeout(() => { // the setTimeout might not be required
setClear(false);
}, 1);

Using state setter as prop with react hooks

I'm trying to understand if passing the setter from useState is an issue or not.
In this example, my child component receives both the state and the setter to change it.
export const Search = () => {
const [keywords, setKeywords] = useState('');
return (
<Fragment>
<KeywordFilter
keywords={keywords}
setKeywords={setKeywords}
/>
</Fragment>
);
};
then on the child I have something like:
export const KeywordFilter: ({ keywords, setKeywords }) => {
const handleSearch = (newKeywords) => {
setKeywords(newKeywords)
};
return (
<div>
<span>{keywords}</span>
<input value={keywords} onChange={handleSearch} />
</div>
);
};
My question is, should I have a callback function on the parent to setKeywords or is it ok to pass setKeywords and call it from the child?
There's no need to create an addition function just to forward values to setKeywords, unless you want to do something with those values before hand. For example, maybe you're paranoid that the child components might send you bad data, you could do:
const [keywords, setKeywords] = useState('');
const gatedSetKeywords = useCallback((value) => {
if (typeof value !== 'string') {
console.error('Alex, you wrote another bug!');
return;
}
setKeywords(value);
}, []);
// ...
<KeywordFilter
keywords={keywords}
setKeywords={gatedSetKeywords}
/>
But most of the time you won't need to do anything like that, so passing setKeywords itself is fine.
why not?
A setter of state is just a function value from prop's view. And the call time can be anytime as long as the relative component is live.

Resources