Noobster
Noobster

Reputation: 188

Preact/Fresh Context causes error: Converting circular structure to JSON

I'm trying to create a simple Context object that will hold some global state in my Fresh / Preact app. The problem I am having is that when trying to render the app it throws an error: Converting circular structure to JSON. I understand what this error means and why it happens (Because AppContext.Provider has a reference to itself, which causes the error when trying to stringify it, as Fresh uses server-side-rendering). What I don't know is how to fix this issue. I've tried using only Preact signals for state instead, but this caused other issues. What I really want is some way to make the solution with Context to work, as I'm comfortable using it from Next.JS.

AppContext.tsx:

import { createContext } from "preact"
import { useContext, useState } from "preact/hooks"

export type Theme = "light" | "dark"
export type Language = "english" | "norsk"

interface IAppContext {
  theme: Theme,
  language: Language,
  toggleTheme: () => void,
  toggleLanguage: () => void
}

const AppContext = createContext<IAppContext>({
  theme: "light",
  language: "english",
  toggleLanguage: () => {},
  toggleTheme: () => {}
})

export const useAppContext = () => useContext(AppContext)

export default function AppContextProvider({ children }: any) {
  const [theme, setTheme] = useState<Theme>("light")
  const [language, setLanguage] = useState<Language>("english")
  const toggleTheme = () => setTheme(_theme => _theme === "light" ? "dark" : "light")
  const toggleLanguage = () => setLanguage(_language => _language === "english" ? "norsk" : "english")

  const context: IAppContext = {
    theme,
    language,
    toggleTheme,
    toggleLanguage
  }

  return <AppContext.Provider value={context}>{children}</AppContext.Provider>
}

Upvotes: 2

Views: 324

Answers (1)

Noobster
Noobster

Reputation: 188

I ended up abandoning the Context hook and going with signals. I now simply import AppContext wherever I need it:

import { Signal, signal } from "@preact/signals"

export type Theme = "light" | "dark"
export type Language = "english" | "norsk"

interface IAppContext {
  theme: Signal<Theme>,
  language: Signal<Language>,
  toggleTheme: () => void,
  toggleLanguage: () => void
}

const theme = signal<Theme>("light")
const language = signal<Language>("english")

const storedTheme = localStorage.getItem("theme") as Theme | null
if (storedTheme) {
  theme.value = storedTheme
  setCSSTheme(storedTheme)
}

function toggleTheme() {
  const newTheme = theme.value = theme.value === "light" ? "dark" : "light"
  theme.value = newTheme
  localStorage.setItem("theme", newTheme)
  setCSSTheme(newTheme)
}

function setCSSTheme(theme: Theme) {
  const classList = document.documentElement.classList
  classList.add(theme)
  classList.remove(theme === "light" ? "dark" : "light")
}

const toggleLanguage = () => language.value = language.value === "english" ? "norsk" : "english"

export const AppContext: IAppContext = {
  theme,
  language,
  toggleTheme,
  toggleLanguage
}

Upvotes: 3

Related Questions