Skip to main content

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 cisShowingSettings, 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!


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>


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>

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


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


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 nisShowingSettings, 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>
);
}

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 nisShowingSettings, 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>
);
}

Thank you, sir. Much appreciated!


Reply