This has probably been asked before (please refer me if true) but I've been trying to invoke a function after checking for its existence with &&
interface IProps {
primaryAction?: () => void;
}
const Comp: React.FC<IProps> = (props) => {
return (
<div>
{props.primaryAction && (
<Button onClick={() => props.primaryAction()}>
Click
</Button>
)}
</div>
);
};
The TS compiler complains:
Cannot invoke an object which is possibly 'undefined'.
Is there any way to work around this?
You did not check in the same scope where you use it. In theory, props could have had its content changed by the time your function runs.
Add this (terrible) code to your render function and you would get a runtime error on click:
setTimeout(() => props.primaryAction = undefined, 100)
This is the error that typescript is protecting you from. It's saying that it cannot guarantee the that your non-null check is still valid when you use that value.
This is why it's usually recommended to deconstruct props in functional components:
const Comp: React.FC<IProps> = ({ primaryAction }) => {
return (
<div>
{primaryAction && (
<Button onClick={() => primaryAction()}>
Click
</Button>
)}
</div>
);
};
Now you have direct reference to the value, and you know it can't be changed because no code outside could affect the assigned value without typescript noticing, because that would have to happen within this function.
Playground
Related
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.
I would like to pass a variable into a function that is in the className property (not anywhere else)
This function runs as soon as that specific jsx part is read (after the component function, before mount).
As a side effect this function returns a string for the className.
Then I want the same function to run after all the components are mounted = useEffect(() => { //here },[] with the same variable
const run = (val: any) => val
useEffect(() => {
const val = run()
console.log(val); // return 'go'
}, [])
return (
<>
<button className={ run('go') }>
run
</button>
</>
)
You didn't understand what's className role and how to use it.
You don't pass a function to className, but a CSS class name, which will define the styling of the element.
If I understood correctly, you want run() function to run automatically every time the components renders, with a specific input which is 'go'.
You can edit your useEffect hook like so:
const run = (val: any) => val
useEffect(() => {
const val = run('go')
console.log(val); // return 'go'
}, [])
return (
<>
<button>
run
</button>
</>
)
Another option will be, for example, call a function through onClick event of the button, like this:
return (
<>
<button onClick={ run('go') }>
run
</button>
</>
)
This way, the run function will be triggered every time the function rerenders.
You'll also have to remove the useEffect hook, as it will trigger the function on each rerender without any input, and it's not what you asked.
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.
I've got a component where I want 2 onClicks to happen,
I figured I need to get to out to a separate function but when I wrote that and tried giving it as an onClick I get errors, primary one being that I passed down an object.
Can you please guide me how would I write that properly?
The onCategoryChange function doesn't fire, and closeSideMenu doesn't matter on large screens since the menu is always open.
export function SingleNavLink(props){
const {url,name,iconPath,onCategoryChange,closeSideMenu}=props
const IsAllowedToCloseMenue=(closeSideMenu===undefined)? '':{ onClick: () => {closeSideMenu()}}
const linkProps= (onCategoryChange===undefined) ? {exact: true, to: url} : { onClick: () => {onCategoryChange(name.toLowerCase())}}
return(
<NavLink {...linkProps} {...IsAllowedToCloseMenue}>
{name}
</NavLink>
)
}
Make a onClick handler, as #Mikhal Sidorov says, and then check if each is undefined in a if with || to see if either closeSideMenu or onCategoryChange is undefined. If it is undefined, then execute the function.
You should make one onClick handler, and check conditions inside handler. Example:
const linkProps = (onCategoryChange === undefined) ? {exact: true, to: url} : null
const handleClick = () => {
if (closeSideMenu === undefined) {
closeSideMenu()
}
if (onCategoryChange === undefined) {
onCategoryChange(name.toLowerCase())
}
}
return (
<NavLink onClick={handleClick} {...linkProps}>
{name}
</NavLink>
)
In my application I have a list of "chips" (per material-ui), and on clicking the delete button a delete action should be taken. The action needs to be given a reference to the chip not the button.
A naive (and wrong) implementation would look like:
function MemberList(props) {
const {userList} = this.props;
refs = {}
for (const usr.id of userList) {
refs[usr.id] = React.useRef();
}
return <>
<div >
{
userList.map(usr => {
return <UserThumbView
ref={refs[usr.id]}
key={usr.id}
user={usr}
handleDelete={(e) => {
onRemove(usr, refs[usr.id])
}}
/>
}) :
}
</div>
</>
}
However as said this is wrong, since react expects all hooks to always in the same order, and (hence) always be of the same amount. (above would actually work, until we add a state/any other hook below the for loop).
How would this be solved? Or is this the limit of functional components?
Refs are just a way to save a reference between renders. Just remember to check if it is defined before you use it. See the example code below.
function MemberList(props) {
const refs = React.useRef({});
return (
<div>
{props.userList.map(user => (
<UserThumbView
handleDelete={(e) => onRemove(user, refs[user.id])}
ref={el => refs.current[user.id] = el}
key={user.id}
user={user}
/>
})}
</div>
)
}