Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Proposal] An alternate design #17

Open
nathan-alden-sr opened this issue Sep 1, 2022 · 0 comments
Open

[Proposal] An alternate design #17

nathan-alden-sr opened this issue Sep 1, 2022 · 0 comments

Comments

@nathan-alden-sr
Copy link

nathan-alden-sr commented Sep 1, 2022

I spent some time today coming up with a design that is closer to Helmet. It's a naive implementation, but it works well and can provide more flexibility than solid-meta's current design.

HeadProvider.tsx:

import _ from "lodash-es";
import { Component, createContext, createRenderEffect, ParentComponent, useContext } from "solid-js";

type TitleFormatter = (text: string | null) => string | null;

export interface HeadContextType {
  setMeta: (name: string, content: string) => void;
  setTitle: (text: string | null) => void;
  setTitleFormatter: (formatter: TitleFormatter) => void;
}

const HeadContext = createContext<HeadContextType>();

const HeadProvider: ParentComponent = props => {
  let titleFormatter: TitleFormatter = (text: string | null) => text;

  const value: HeadContextType = {
    setMeta(name, content) {
      const metaElements: NodeListOf<HTMLMetaElement> = document.head.querySelectorAll(`meta[name='${name}']`);

      if (metaElements.length > 0) {
        for (const metaElement of metaElements) {
          metaElement.content = content;
        }
      } else {
        const metaElement = document.createElement("meta");

        metaElement.name = name;
        metaElement.content = content;

        document.head.appendChild(metaElement);
      }
    },

    setTitle(text) {
      const titleElements = document.head.getElementsByTagName("title");

      if (titleElements.length > 0) {
        for (const titleElement of titleElements) {
          titleElement.textContent = titleFormatter(text);
        }
      } else {
        const titleElement = document.createElement("title");

        titleElement.textContent = titleFormatter(text);

        document.head.appendChild(titleElement);
      }
    },

    setTitleFormatter(formatter) {
      titleFormatter = formatter;
    }
  };

  return <HeadContext.Provider value={value}>{props.children}</HeadContext.Provider>;
};

export { HeadProvider };

export interface TitleProps {
  formatter?: TitleFormatter;
  text?: string | null;
}

export const Title: Component<TitleProps> = props => {
  const context = useContext(HeadContext);

  if (_.isNil(context)) {
    throw new Error("Must use within a HeadContext");
  }

  createRenderEffect(() => {
    if (!_.isNil(props.formatter)) {
      context.setTitleFormatter(props.formatter);
    }

    if (!_.isUndefined(props.text)) {
      context.setTitle(props.text);
    }
  });

  return null;
};

export interface MetaProps {
  name: string;
  content: string;
}

export const Meta: Component<MetaProps> = props => {
  const context = useContext(HeadContext);

  if (_.isNil(context)) {
    throw new Error("Must use within a HeadContext");
  }

  createRenderEffect(() => {
    context.setMeta(props.name, props.content);
  });

  return null;
};

Example usage:

<HeadProvider>
  <Title formatter={text => `${text} - My Site`} />
  <Title text="My Page" />
  <Meta name="og:title" content="The Title" />
</HeadProvider>

The <Title> component looks for a <title> elements inside <head> and then overwrites their textContent with the value of the text prop. It also allows the developer to provide a formatter function that is passed the desired text--say, a specific page's title--and, in my example, append the website name to the end of it. The code sets the <title> element's textContent property to My Page - My Site. I'm wondering if it would be better to change document.title directly, but I'm not sure of the pros and cons.

The <Meta> component is similar in that it looks for matching elements inside <head> and, if they exist, overwrites their content properties. If they don't exist, a new <meta> element is created.

The design of each head tag component could be improved to allow for all props similar to your existing code. I didn't do that for the sake of brevity.

I don't have any performance-related code in my example (e.g., no refs). I'm also not sure if my reactivity code is correct (I tried to write code similar to your current implementation). I am still very new to SolidJS.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant