Mar 09, 2020 04:45 PM
I’m trying to implement a settings menu in my custom block. I have the settings component properly showing, and the settings inside working as I want. The only missing piece that I can’t figure out is how to trigger closing the menu from a Button component’s onClick()
function, rather than having to click the settings icon again to close the menu.
I have this in my main block component:
const [isShowingSettings, setIsShowingSettings] = useState(false);
useSettingsButton(function() {
setIsShowingSettings(!isShowingSettings);
});
I tried calling the useSettingsButton()
function inside the onClick()
lambda, but got this error:
Error: Invalid hook call. Hooks can only be called inside of the body of a function component. This could happen for one of the following reasons:
1. You might have mismatching versions of React and the renderer (such as React DOM)
2. You might be breaking the Rules of Hooks
3. You might have more than one copy of React in the same app
See https://fb.me/react-invalid-hook-call for tips about how to debug and fix this problem.
at Object.throwInvalidHookError (https://localhost:9000/__runFrame/bundle.js:58579:13)
at useEffect (https://localhost:9000/__runFrame/bundle.js:73023:21)
at useSettingsButton (https://localhost:9000/__runFrame/bundle.js:29347:24)
at onClick (https://localhost:9000/__runFrame/bundle.js:130:46)
at HTMLUnknownElement.callCallback (https://localhost:9000/__runFrame/bundle.js:44021:14)
at HTMLUnknownElement.r._wrapped (https://devblock---z-m-ipekz2-j-bqi-ma--wom6q92.airtableblocks.com/__runFrame?blockId=blkVm56Qo0MkQRQWs&isDevelopment=1:81:440)
at Object.invokeGuardedCallbackDev (https://localhost:9000/__runFrame/bundle.js:44070:16)
at invokeGuardedCallback (https://localhost:9000/__runFrame/bundle.js:44125:31)
at invokeGuardedCallbackAndCatchFirstError (https://localhost:9000/__runFrame/bundle.js:44139:25)
at executeDispatch (https://localhost:9000/__runFrame/bundle.js:44222:3)
I read about the “Rules of Hooks” and suppose I must be breaking Rule #1 by calling the useSettingsButton()
hook from within a nested component (namely, my SettingsMenu
component – and then presumably double nested by being in a Button
component as well).
Would anybody be able to offer some guidance on how I could still trigger this action from a button in a nested component without using hook?
Thanks!
Solved! Go to Solution.
Mar 09, 2020 05:04 PM
If the state lives in the parent component of SettingsMenu
, then you’ll need to pass the setIsShowingSettings
function down as a prop.
Here’s an example:
function Block() {
const [isShowingSettings, setIsShowingSettings] = useState(false);
useSettingsButton(() => {
setIsShowingSettings(!isShowingSettings);
});
if (isShowingSettings) {
return (
<SettingsMenu
onSaveClick={() => {
setIsShowingSettings(!isShowingSettings);
}}
/>
);
} else {
// Render your block
return 'Hello world!';
}
}
function SettingsMenu(props) {
return (
<div>
{/* Render settings */}
<Button
variant="primary"
icon="settings"
marginLeft={2}
onClick={props.onSaveClick}
>
Save Settings
</Button>
</div>
);
}
Mar 09, 2020 04:52 PM
Hey @Jeremy_Oglesby,
You should call setIsShowingSettings
directly in the event handler — this doesn’t need to go through the useSettingsButton
hook:
<Button
variant="primary"
onClick={() => {setIsShowingSettings(false)}}
>
Done
</Button>
Mar 09, 2020 04:54 PM
I tried that too, @Stephen_Suen:
But I got this error:
ReferenceError: setIsShowingSettings is not defined
at onClick (https://localhost:9000/__runFrame/bundle.js:130:20)
at HTMLUnknownElement.callCallback (https://localhost:9000/__runFrame/bundle.js:44019:14)
at HTMLUnknownElement.r._wrapped (https://devblock---z-m-ipekz2-j-bqi-ma--wom6q92.airtableblocks.com/__runFrame?blockId=blkVm56Qo0MkQRQWs&isDevelopment=1:81:440)
at Object.invokeGuardedCallbackDev (https://localhost:9000/__runFrame/bundle.js:44068:16)
at invokeGuardedCallback (https://localhost:9000/__runFrame/bundle.js:44123:31)
at invokeGuardedCallbackAndCatchFirstError (https://localhost:9000/__runFrame/bundle.js:44137:25)
at executeDispatch (https://localhost:9000/__runFrame/bundle.js:44220:3)
at executeDispatchesInOrder (https://localhost:9000/__runFrame/bundle.js:44245:5)
at executeDispatchesAndRelease (https://localhost:9000/__runFrame/bundle.js:47109:5)
at executeDispatchesAndReleaseTopLevel (https://localhost:9000/__runFrame/bundle.js:47118:10)
Do I need to pass the setIsShowingSettings()
function as a prop into the SettingsMenu
component? (And if so, what does that look like?)
Mar 09, 2020 05:04 PM
If the state lives in the parent component of SettingsMenu
, then you’ll need to pass the setIsShowingSettings
function down as a prop.
Here’s an example:
function Block() {
const [isShowingSettings, setIsShowingSettings] = useState(false);
useSettingsButton(() => {
setIsShowingSettings(!isShowingSettings);
});
if (isShowingSettings) {
return (
<SettingsMenu
onSaveClick={() => {
setIsShowingSettings(!isShowingSettings);
}}
/>
);
} else {
// Render your block
return 'Hello world!';
}
}
function SettingsMenu(props) {
return (
<div>
{/* Render settings */}
<Button
variant="primary"
icon="settings"
marginLeft={2}
onClick={props.onSaveClick}
>
Save Settings
</Button>
</div>
);
}
Mar 09, 2020 05:08 PM
Thank you, sir. Much appreciated!