Two onClick functions on a react component, doesn't fire up - reactjs

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>
)

Related

I want only one component state to be true between multiple components

I am calling components as folloews
{userAddresses.map((useraddress, index) => {
return (
<div key={index}>
<Address useraddress={useraddress} />
</div>
);
})}
Their state:
const [showEditAddress, setShowEditAddress] = useState(false);
and this is how I am handling their states
const switchEditAddress = () => {
if (showEditAddress === false) {
setShowEditAddress(true);
} else {
setShowEditAddress(false);
}
};
Well, it's better if you want to toggle between true and false to use the state inside useEffect hook in react.
useEffect will render the component every time and will get into your condition to set the state true or false.
In your case, you can try the following:
useEffect(() => { if (showEditAddress === false) {
setShowEditAddress(true);
} else {
setShowEditAddress(false);
} }, [showEditAddress])
By using useEffect you will be able to reset the boolean as your condition.
Also find the link below to react more about useEffect.
https://reactjs.org/docs/hooks-effect.html
It would be best in my opinion to keep your point of truth in the parent component and you need to figure out what the point of truth should be. If you only want one component to be editing at a time then I would just identify the address you want to edit in the parent component and go from there. It would be best if you gave each address a unique id but you can use the index as well. You could do something like the following:
UserAddress Component
const UserAddress = ({index, editIndex, setEditIndex, userAddress}) => {
return(
<div>
{userAddress}
<button onClick={() => setEditIndex(index)}>Edit</button>
{editIndex === index && <div style={{color: 'green'}}>Your editing {userAddress}</div>}
</div>
)
}
Parent Component
const UserAddresses = () => {
const addresses = ['120 n 10th st', '650 s 41 st', '4456 Birch ave']
const [editIndex, setEditIndex] = useState(null)
return userAddresses.map((userAddress, index) => <UserAddress key={index} index={index} editIndex={editIndex} setEditIndex={setEditIndex} userAddress={userAddress}/>;
}
Since you didn't post the actual components I can only give you example components but this should give you an idea of how to achieve what you want.

Problem with using different onClick events in one button component

I wanted to make my components as reusable as it possible but when I started adding events the problems occured. I am using one button component in a lot of places in my app and I just change its name. It worked fine when I passed one onClick event to it (to change menu button name) but when I wanted to do the same with another button (to change cycle name) and when I passed second onClick event to the same button component the menu button stopped working. I tried to find solution but found only different topics. I know I could make a wrapper around the button and make onClick on the wrapper, but I think I am doing something wrong and there must be more elegant way to handle this.
Button component
export const Button = ({text, changeButtonName, changeCycle}) => {
return (
<AppButton onClick={changeButtonName, changeCycle}>
{text}
</AppButton>
);
};
Navbar component where cycle and menuu buttons are placed
export const Navbar = () => {
const menuButton = 'Menu';
const closeButton = 'Zamknij';
const [menuButtonName, setMenuButtonName] = useState(menuButton);
const changeButtonName = () => {
menuButtonName === menuButton ? setMenuButtonName(closeButton) : setMenuButtonName(menuButton);
}
const interiorButton = 'Interior →';
const structuralCollageButton = 'Structural Collage →';
const [cycleButtonName, setCycleButtonName] = useState(interiorButton);
const changeCycle = () => {
cycleButtonName === interiorButton ? setCycleButtonName(structuralCollageButton) : setCycleButtonName(interiorButton);
}
return (
<Nav>
<AuthorWrapper>
<AuthorName>
Michał Król
</AuthorName>
<AuthorPseudonym>
Structuralist
</AuthorPseudonym>
</AuthorWrapper>
<CycleButtonWrapper >
<Button text={cycleButtonName} changeCycle={changeCycle} />
</CycleButtonWrapper>
<MenuButtonWrapper>
<Button text={menuButtonName} changeButtonName={changeButtonName} />
</MenuButtonWrapper>
</Nav>
)
}
this is not a really reusable approach for a Button. For every new method name you would have to include in the props params and you could face something like:
export const Button = ({text, changeButtonName, changeCycle, changeTheme, changeDisplay})
the proper way to make it reusable would be by passing only one handler to your button:
export const Button = ({text, clickHandler}) => {
return (
<AppButton onClick={clickHandler}>
{text}
</AppButton>
);
};
fwiw, the reason you have problem is because at this code onClick={changeButtonName, changeCycle} you are passing multiple expressions with comma operator where the last operand is returned.
You cannot pass two functions to onClick. Either do a conditional check that call that function which is passed or make a wrapper function.
export const Button = ({text, changeButtonName, changeCycle}) => {
return (
<AppButton onClick={changeButtonName || changeCycle}>
{text}
</AppButton>
);
};
or
export const Button = ({text, changeButtonName, changeCycle}) => {
return (
<AppButton
onClick={() => {
changeButtonName && changeButtonName();
changeCycle && changeCycle();
}
}>
{text}
</AppButton>
);
};
update your code like
<AppButton onClick={()=> {
changeButtonName && changeButtonName();
changeCycle && changeCycle();
}}>
{text}
</AppButton>

Can not invoke possibly undefined object after checking with &&

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

React PDFDownloadLink - call onclick event through code explicitly

PDFDownloadLink from react-pdf library downloads a pdf when someone clicks on it.
I want to trigger this click event via code based on some condition .
How do I explicitly invoke the click of PDFDownloadLink through code?
A bit late, but you can pass a ref to the render function's return value and use it to call click() on imperatively. For that to work you need to use a separate component wrapper:
const DownloadLink = memo(function () {
const linkRef = useRef(null)
const onLoadingFinished = useCallback(function () {
// When this function is called the first time it is safe to initiate the download
const elem = linkRef?.current
if (elem !== null) {
elem.click()
}
}, [])
return (
<PDFDownloadLink document={<MyDoc />} fileName={'my-file.pdf'}>
{({ blob, url, loading, error }) => (
// You shouldn't call setState() here, so we need to use a separate component to keep track of whether the document has finished rendering
<WorkaroundContainer ref={linkRef} loading={loading} onLoadingFinished={onLoadingFinished} />
)}
</PDFDownloadLink>
)
})
const WorkaroundContainer = forwardRef(function ({ loading, onLoadingFinished }, ref) {
useEffect(() => {
if (!loading) {
onLoadingFinished()
}
}, [loading])
// If you only want to initiate the download imperatively, hide the element via CSS (e.g. `visibility: hidden`)
return (
<div ref={ref}>
{loading ? 'Loading...' : 'Download PDF'}
</div>
)
})

React ES 6 Classes refs

I am very new in react native and I tried to refactor a source code from old react into react using ES 6 Class, but I got an error 'Cannot read property 'close' of undefined'. Can anyone help me why this.refs.drawer in closeDrawer is undefined?
closeDrawer = () => {
applicationActions.setDrawerStatus(false);
this.refs.drawer.close();
}
openDrawer = () => {
applicationActions.setDrawerStatus(true);
this.refs.drawer.open()
}
setDrawerState(value) {
this.setState({ isDrawerOpened: value });
}
render() {
return (
<Drawer ref="drawer"
type="static"
openDrawerOffset={DRAWER_OFFSET}
panOpenMask={.5}
onOpen={() => this.setDrawerState(true).bind(this)}
onClose={() => this.setDrawerState(false).bind(this)}
content={<DrawerScene closeDrawer={this.closeDrawer().bind(this)} />} >
<MainView
drawerStatus={this.isDrawerOpened}
closeDrawer={this.closeDrawer().bind(this)}
openDrawer={this.openDrawer().bind(this)}
/>
</Drawer>
);
}
Regards
EDIT I did not notice that you were using arrow functions in your component's member functions, so you do not need to bind them. There were some other issues, though
This is a binding issue. This should work:
closeDrawer = () => {
applicationActions.setDrawerStatus(false);
this.refs.drawer.close();
}
openDrawer = () => {
applicationActions.setDrawerStatus(true);
this.refs.drawer.open()
}
setDrawerState(value) {
this.setState({ isDrawerOpened: value });
}
render() {
return (
<Drawer ref="drawer"
type="static"
openDrawerOffset={DRAWER_OFFSET}
panOpenMask={.5}
onOpen={() => this.setDrawerState(true)}
onClose={() => this.setDrawerState(false)}
content={<DrawerScene closeDrawer={this.closeDrawer} />} >
<MainView
drawerStatus={this.isDrawerOpened}
closeDrawer={this.closeDrawer}
openDrawer={this.openDrawer}
/>
</Drawer>
);
}
The problem with your code is that you are applying bind to the result of a function call. For instance, when you do this.setDrawerState(true).bind(this), the function is called, returns the appropriate value, and then bind is applied to it. This usually would result in an error, but here you are also trying to access a ref that has not yet been set up (because before that happens all prop values have to be evaluated before passed to the new component, which is exactly the problem here, the function is called before the component is instantiated).
Just so you know a bit more about bind: it is a property of a function object, so you need to access it from the reference to that function (in this case, its name). The result of bind is a new function with the same behaviour of the original one, save for the new this value or any other parameters you pass.
Try to set ref like this instead of a string:
drawer = null;
closeDrawer = () => {
applicationActions.setDrawerStatus(false);
this.drawer.close();
}
openDrawer = () => {
applicationActions.setDrawerStatus(true);
this.drawer.open()
}
setDrawerState(value) {
this.setState({ isDrawerOpened: value });
}
render() {
return (
<Drawer ref={((component)=> this.drawer=component)}
type="static"
openDrawerOffset={DRAWER_OFFSET}
panOpenMask={.5}
onOpen={() => this.setDrawerState(true).bind(this)}
onClose={() => this.setDrawerState(false).bind(this)}
content={<DrawerScene closeDrawer={this.closeDrawer().bind(this)} />} >
<MainView
drawerStatus={this.isDrawerOpened}
closeDrawer={this.closeDrawer().bind(this)}
openDrawer={this.openDrawer().bind(this)}
/>
</Drawer>
);
}

Resources