Help

Re: Close Settings Menu with Button

Solved
Jump to Solution
668 0
cancel
Showing results for 
Search instead for 
Did you mean: 

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!

1 Solution

Accepted Solutions

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

See Solution in Thread

4 Replies 4
Stephen_Suen
Community Manager
Community Manager

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:
image

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 [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>
    );
}

Thank you, sir. Much appreciated!