Skip to main content

Hooks

If you want to build a new component or you want to re-implement the code editor, for example, you can still rely on the sandpack state and create your UI from scratch.

The sandpack-react package exports a set of hooks, that give you access to the sandpack context in your own components.

useSandpack

The main hook is called useSandpack and gives you the entire context object to play with.

note

Keep in mind that the useSandpack hook only works inside the <SandpackProvider>.

Let's build a code viewer component that renders a standard pre tag:

import { useSandpack } from "@codesandbox/sandpack-react";

const SimpleCodeViewer = () => {
  const { sandpack } = useSandpack();
  const { files, activeFile } = sandpack;

  const code = files[activeFile].code;
  return <pre>{code}</pre>;
};

The sandpack object is available in any component and exposes all the internal state:

  • the files including all the setup/template files
  • the activeFile / visibleFiles fields
  • the error object, if any
  • multiple functions for changing the state of sandpack: updateCurrentFile, setActiveFile, etc.

In the component above, you get the active code string by calling files[activeFile].code, so any change of state will trigger a re-render of the component and an update of the code.

We can test this with the CustomSandpack we implemented at the previous step.

<SandpackProvider template="react">
  <SandpackLayout>
    <SandpackCodeEditor />
    {/* This will render the pre on the right side of your sandpack component */}
    <SimpleCodeViewer />
  </SandpackLayout>
</SandpackProvider>

If you run this, you will notice that the SimpleCodeViewer is in sync with the state of the SandpackCodeEditor.

useSandpack also exports dispatch and listen, you can levarage these functions for communicating directly with the bundler. However, at this point, you'd have understood all the different types of messages and payloads that are passed from the sandpack manager to the iframe and back.

import { useSandpack } from "@codesandbox/sandpack-react";

const CustomRefreshButton = () => {
  const { dispatch, listen } = useSandpack();

  const handleRefresh = () => {
    // sends the refresh message to the bundler, should be logged by the listener
    dispatch({ type: "refresh" });
  };

  useEffect(() => {
    // listens for any message dispatched between sandpack and the bundler
    const stopListening = listen((msg) => console.log(msg));

    return () => {
      // unsubscribe
      stopListening();
    };
  }, [listen]);

  return (
    <button type="button" onClick={handleRefresh}>
      Refresh
    </button>
  );
};

Plus, useSandpack exposes a bunch of methods that you can use to manage the current state of the Sandpack instance:

MethodDescription
closeFileClose the given path in the editor
deleteFileDelete the given path in the editor
dispatchSends a message to the bundler
listenListens for messages from the bundler
openFileOpen the given path in the editor
resetAllFilesReset all files for all paths to the original state
resetFileReset the code for a given path
setActiveFileSet a specific file as active in a given path
updateFileUpdate the content of a file in a given path or multiple files
updateCurrentFileUpdate the content of the current file

useSandpackNavigation

Some of the common functionalities of sandpack are also extracted into specialized hooks. These all use useSandpack under the hood, but abstract away the shape of the state object and the dispatch/listen functions.

The refresh button can be built with the useSandpackNavigation hook:

import { useSandpackNavigation } from "@codesandbox/sandpack-react";

const CustomRefreshButton = () => {
  const { refresh } = useSandpackNavigation();
  return (
    <button type="button" onClick={() => refresh()}>
      Refresh Sandpack
    </button>
  );
};

useActiveCode

We implemented the SandpackCodeEditor on top of codemirror/next, but it is super easy to switch to your favorite code editor. Let's connect the sandpack state to an instance of AceEditor. You can use the useActiveCode hook, which gives you the code value and the updateCode callback.

import { useActiveCode } from "@codesandbox/sandpack-react";
import AceEditor from "react-ace";
import "ace-builds/src-noconflict/mode-javascript";
import "ace-builds/src-noconflict/theme-textmate";

const CustomAceEditor = () => {
  const { code, updateCode } = useActiveCode();

  return (
    <AceEditor
      mode="javascript"
      defaultValue={code}
      onChange={updateCode}
      fontSize={14}
      height="300px"
      width="100%"
    />
  );
};

Now, let's put all of these custom components together:

export const CustomSandpack = () => (
  <SandpackProvider template="react">
    <CustomAceEditor />
    <SandpackPreview showRefreshButton={false} showOpenInCodeSandbox={false} />
    <CustomRefreshButton />
    <CustomOpenInCSB />
  </SandpackProvider>
);

It's not pretty, but with just a few lines of code, you can create a whole new component that uses the power of sandpack, but has all the UI and functionality you need for your specific use case.

Congrats!

You can now build your own sandpack-aware components on top of the sandpack custom hooks. The final piece of the puzzle is to understand the sandpack-client, the framework agnostic library that we use to manage the access to the bundler.