Search code examples
reactjsreact-hookstogglepolaris

How can I implement not only one but multi setting toggles?


I use Shopify Polaris's setting toggle.https://polaris.shopify.com/components/actions/setting-toggle#navigation

And I want to implement not only one but multi setting toggles.But I don't want to always duplicate same handleToggle() and values(contentStatus, textStatus) like below the sandbox A,B,C...

import React, { useCallback, useState } from "react";
import { SettingToggle, TextStyle } from "@shopify/polaris";

export default function SettingToggleExample() {
  const [activeA, setActiveA] = useState(false);
  const [activeB, setActiveB] = useState(false);

  const handleToggleA = useCallback(() => setActiveA((active) => !active), []);
  const handleToggleB = useCallback(() => setActiveB((active) => !active), []);

  const contentStatusA = activeA ? "Deactivate" : "Activate";
  const contentStatusB = activeB ? "Deactivate" : "Activate";
  const textStatusA = activeA ? "activated" : "deactivated";
  const textStatusB = activeB ? "activated" : "deactivated";

  const useHandleToggle = (active, setActive) => {
    const handleToggle = useCallback(() => setActive((active) => !active), []);

    const contentStatus = active ? "Disconnect" : "Connect";
    const textStatus = active ? "connected" : "disconnected";
    handleToggle();
    return [contentStatus, textStatus];
  };

  useHandleToggle(activeA, setActiveA);

  return (
    <>
      <SettingToggle
        action={{
          content: contentStatusA,
          onAction: handleToggleA
        }}
        enabled={activeA}
      >
        This setting is <TextStyle variation="strong">{textStatusA}</TextStyle>.
      </SettingToggle>
      <SettingToggle
        action={{
          content: contentStatusB,
          onAction: handleToggleB
        }}
        enabled={activeB}
      >
        This setting is <TextStyle variation="strong">{textStatusB}</TextStyle>.
      </SettingToggle>
    </>
  );
}

https://codesandbox.io/s/vigorous-pine-k0dpib?file=/App.js

So I thought I can use a custom hook. But it's not working. So it would be helpful if you give me some advice.


Solution

  • Using simple Booleans for each toggle

    If you combine your active state objects into a single array, then you can update as many settings as you would like dynamically. Here's an example of what that might look like:

    import React, { useCallback, useState } from "react";
    import { SettingToggle, TextStyle } from "@shopify/polaris";
    
    export default function SettingToggleExample() {
      // define stateful array of size equal to number of toggles
      const [active, setActive] = useState(Array(2).fill(false));
    
      const handleToggle = useCallback((i) => {
        // toggle the boolean at index, i
        setActive(prev => [...prev.slice(0,i), !prev[i], ...prev.slice(i+1)])
      }, []);
    
      return (
        <>
          {activeStatuses.map((isActive, index) =>
            <SettingToggle
              action={{
                content: isActive ? "Deactivate" : "Activate",
                onAction: () => handleToggle(index)
              }}
              enabled={isActive}
            >
              This setting is <TextStyle variation="strong">{isActive ? "activated" : "deactivated"}</TextStyle>.
            </SettingToggle>
          }
        </>
      );
    }
    

    Of course, you will likely want to add a label to each of these going forward, so it may be better to define a defaultState object outside the function scope and replace the Array(2).fill(false) with it. Then you can have a string label property for each toggle in addition to a boolean active property which can be added next to each toggle in the .map(...).

    With labels added for each toggle

    Per your follow up, here is the implementation also found in the CodeSandbox for a state with labels for each toggle (including here on the answer to protect against link decay):

    import React, { useCallback, useState } from "react";
    import { SettingToggle, TextStyle } from "@shopify/polaris";
    
    const defaultState = [
      {
        isActive: false,
        label: "A"
      },
      {
        isActive: false,
        label: "B"
      },
      {
        isActive: false,
        label: "C"
      }
    ];
    
    export default function SettingToggleExample() {
      const [active, setActive] = useState(defaultState);
    
      const handleToggle = useCallback((i) => {
        // toggle the boolean at index, i
        setActive((prev) => [
          ...prev.slice(0, i),
          { ...prev[i], isActive: !prev[i].isActive },
          ...prev.slice(i + 1)
        ]);
      }, []);
    
      return (
        <div style={{ height: "100vh" }}>
          {active?.map(({ isActive, label }, index) => (
            <SettingToggle
              action={{
                content: isActive ? "Deactivate" : "Activate",
                onAction: () => handleToggle(index)
              }}
              enabled={isActive}
              key={index}
            >
              This {label} is 
              <TextStyle variation="strong">
                {isActive ? "activated" : "deactivated"}
              </TextStyle>
              .
            </SettingToggle>
          ))}
        </div>
      );
    }