How to create Custom Pages?

@hammad, I think you should use gutenberg blocks. Adding more styles in the content has usually been an anti pattern in WordPress, I think.


By the way, the other day I realized that there is another way to add a custom, non-WordPress page to Frontity.

Instead of creating a custom handler, like this:

const signUpHandler = {
  pattern: "/sign-up/",
  func: ({ state }) => {
    state.source.data["/sign-up/"].isSignUp = true;
  }
}

You can add the data to the state:

const myPackage = {
  state: {
    source: {
      data: {
        "/sign-up/": {
          isReady: true,
          isFetching: false,
          isSignUp: true
        }
      }
    }
  }
}

actions.source.fetch("/sign-up") won’t do the fetch because isReady is true.

The only case where it wouldn’t work is if someone does actions.source.fetch("/sign-up", { force: true }) but that should never happen with a custom page like this.

3 Likes

Can someone help me with adding a custom page to the frontity project.

I added the following …

const signUpHandler = {

  pattern: "/signup/",

  func: ({ route, state }) => {

    Object.assign(state.source.data[route], {

      type: "page",

      isSignUp: true,

    });

  },

};

…

actions: {

    theme: {

      actions: {

        init: ({ libraries }) => {

          libraries.source.handlers.push(signUpHandler);

        },

      },

      beforeSSR: before,

      beforeCSR: before,

      toggleMobileMenu: ({ state }) => {

        state.theme.isMobileMenuOpen = !state.theme.isMobileMenuOpen;

      },

      closeMobileMenu: ({ state }) => {

        state.theme.isMobileMenuOpen = false;

      },

    },

  },

…
frontity.settings

 ["Sign up", "/signup/"],

…

signup.js

import React, { useEffect } from "react";

//import { connect, styled } from "frontity";

const Signup = () => {

  return (

    <div>

      <h2>Sign up Page</h2>

    </div>

  );

};

export default Signup;

…

theme index.js

<Switch>

          <Signup when={data.isSignUp} />

          <Loading when={data.isFetching} />

          <List when={data.isArchive} />

          <Post when={data.isPostType} />

          <PageError when={data.isError} />

…

I keep getting 404 error. and react_devtools_backend.js:2273 ServerError: post type from endpoints ā€œposts,pages,mediaā€ with slug ā€œsignupā€ not found

Any guidance is much appreciated.

I think you forgot to add a priority to the handler. For example priority: 10.

By the way, instead of a handler, you can try adding that data to the state:

const myPackage = {
  state: {
    source: {
      data: {
        "/signup/": {
          isReady: true,
          isFetching: false,
          isSignUp: true,
        },
      },
    },
  },
};

Let me know if that works :slightly_smiling_face:

3 Likes

Yes adding the priority:10 to the handler fixed it!!! And also I added the init wrong, if you see code above, I inserterted the init in actions.theme.actions.init whilst it should have been actions.theme.init… Thank you so much!!!

2 Likes

Hi ! I created a new route to custom component when I will call an external API using your code. I can get a listing without any problem. Now I want to fetch to a specific id to fetch to a new component

data: {
        "/hello/:id": {
          isReady: true,
          isFetching: false,
          isHello: true,
        },
      },

However it won’t work… any suggestion ?

If you want to use a pattern you need to create a handler:

const helloHandler = {
  pattern: "/hello/:id",
  func: ({ state, link, params }) => {
    state.source.data[link].isReady = true;
    state.source.data[link].isHello = true;
    state.source.data[link].id = params.id;
  },
};

And then add it to libraries.source.handlers.

Hello @luisherranz, I tried your solution but when I am typing any id I get a 404 page :confused:

  const helloHandler = {
  pattern: "/hello/:id",
  func: ({ state, link, params }) => {
    state.source.data[link].isHello = true;
    state.source.data[link].id = params.id;
  },
};

And

packages: [
{
  name: "@awsmin/f1",
  state: {
    theme: {
      menu: [
        ["Nos occasions", "/"],
        ["L'atelier", "/category/nature/"],
        ["Nos services", "/category/travel/"],
        ["Qui sommes-nous", "/tag/japan/"],
        ["ActualitƩs", "/about-us/"],
        ["Nous contacter", "/about-us/"],
      ],
      featured: {
        showOnList: false,
        showOnPost: false,
      },
    },
  },
  actions: {
    theme: {
      init: ({ libraries }) => {
        // Use html2react to process the <img> tags inside the content HTML.
        // libraries.html2react.processors.push(image);

        // Add the handler to wp-source.
        libraries.source.handlers.push(helloHandler);
      },
    },
  },

and my index.js I create a component with sample h1 at that time

<Switch>
      <Loading when={data.isFetching} />
      <List when={data.isArchive} />
      <HomeScreen when={data.isHome} />
      <VehicleScreen when={data.isOffers} />
      <Entretien when={data.isEntretien} />
      <Hello when={data.isHello} />
      <Jobs when={data.isAwsmJobOpenings} />
      <Page when={data.isPage} />
      <Post when={data.isPostType} />
      <PageError when={data.isError} />
    </Switch>
1 Like

Hi @floriandupuis1996,

Can you please provide a repo or code-sandbox with your code? This is especially helpful to find solutions of technical issues with specific code

I use it, can fix this problem !

const profilePage = {
	name: 'member',
	priority: 1,
	pattern: '/member/:user',
	func: ({ state, link, params }) => {
		state.source.data[link].isProfile = true
		state.source.data[link].user = params.user
	},
}
]
	actions: {
		actions: {
			init: ({ libraries }) => {
				libraries.source.handlers.push(profilePage)
			},
		},
		theme: {
...
}

Yeah I succeed

Hey @floriandupuis1996 it’d be great if you can share with the rest how you finally solved and what was the issue you were having :smiley:

Ths thing is I had handler in frontity.settings.js instead of index… now I am looking to get the param.id display on my component but this is not easy task…

any idea ?

Hi there, I’m Pilar. I’m starting with Frontity and I’ve struggling with custom handlers, custom pages and custom queries, browsing this thread and many others, reading the docs, etc. I’ve finally made it work so I wanted to share the code in case it’s helpful for anyone, as yours has been to me :slight_smile:

The base of my project is a large set of Galician traditional sayings or ā€œrefranesā€. The main user interaction will be the search (and also copying the text of the quote, sharing it as an image, etc. but that’s another story). For SEO reasons I want some popular searches to be on the menu and have their own url, like ā€œsayings-about-winterā€ (instead of ā€œdomain.com/?s=winterā€), without having to categorize them. So I created a custom handler that takes the last word of that url pattern, whatever it is, (in this case, ā€œwinterā€) and shows all the posts that contain that specific word.

That’s how I achieved it, I don’t know if it’s a better way to to do it? It would be simpler with the future method described here, but for now it’s working :slight_smile:!

In mars-theme/src/index.js:

import Theme from "./components";
import image from "@frontity/html2react/processors/image";
import iframe from "@frontity/html2react/processors/iframe";

const searchUrls = {
  priority: 1,
  pattern: "/sayings-about-:search",
  func: async ({ link, params, state, libraries, force }) => {
    
    const response = await libraries.source.api.get({
      endpoint: "posts",
      params: { search: params.search }
    });

    const items = await libraries.source.populate({
      response,
      state,
      force,
    });    

    Object.assign(state.source.data[link], {
      isSearchUrl: true,
      //add queried posts to data
      items,
    });

  }
}
const marsTheme = {
  name: "@frontity/mars-theme",
  roots: {
    theme: Theme,
  },
  state: {
    theme: {

      },
    },
  },
  actions: {
    theme: {   
      init: ({ libraries }) => {
        libraries.source.handlers.push(searchUrls);
      },    
    },
  },
};

export default marsTheme;

In src/components/index.js:

<Switch>
  <Loading when={data.isFetching} />
  <List when={data.isArchive} />
  <Post when={data.isPostType} />
  <ListSearch when={data.isSearchUrl} />
</Switch>

In components/list/list-search.js:

const ListSearch = ({ state, libraries, link }) => {
    const data = state.source.get(state.router.link);
    return (
      <Container>
        {data.items.map(({ type, id }) => {
          const item = state.source[type][id];
          // Render one Item component for each one.
          return <Item key={item.id} item={item} />;
        })}
        <Pagination />
      </Container>
    );
};

Now if I go to /sayings-about-winter I see all the posts that contain the word ā€œwinterā€, and /sayings-about-spring shows all the posts containing ā€œspringā€, etc.

And I can add these links to the menu in frontity.settings.js (or anywhere else):

      "menu": [
        [
          "Home",
          "/"
        ],
        [
          "Sayings about winter",
          "/sayings-about-winter/"
        ],
        [
          "Sayings about spring",
          "/sayings-about-spring/"
        ],
      ],
5 Likes

Hi @decrecementofeliz

Welcome to the community. It looks like you’re doing great on your journey with Frontity! :tada:

Thanks for presenting this solution. I’m sure other Frontity users will find it useful.

We’d love to know more about what you’re building with Frontity. We’re always looking for projects to feature in our showcase page.

2 Likes

Hi, here is my frontity page: http://api-tech.pl/ but I have problem with routes,

I can’t get to the site directly from the link for example http://api-tech.pl/kontakt give me 404;
also when I’m for example first render FOTOGRAFIA > PODLASIE (PHOTOGRAPHY > PODLASIE)
looks good, and after refresh my gallery disappear.
I thought something wrong in Theme index.js and with handlers

 <Main>

 {data.isPodlasiePage && <PodlasiePage />} 
      {data.isFrontPage && <Home />}  
      {data.isGoryPage && <GoryPage />} 
      {data.isTimelapsePage && <TimelapsePage />}
      {data.isCommercialPage && <ComercialMoviesPage />}
      {data.isGoPodlasiePage && <GoPodlasiePage />}
      {data.isColaboPage && <CollaborationPage />}
      {data.isContactPage && <ContactPage />} 
      {data.isPolitykaPage && <PrivacyPolicyPage />}

<Switch>
  <Loading when={data.isFetching} />
  <List when={data.isArchive} />
  <PageError when={data.isError} />
</Switch>

and here:

const homeHandler = {

  pattern: "/",

  func: ({ state }) => {

    state.source.data["/"].isFrontPage = true;

    state.source.data["/"].isArchive = false;

  }

}

const fotografiaHandler = {

  pattern: "/fotografia/",

  func: ({ state }) => {

    state.source.data["/astrofoto/"].isShovvPage = true;

    state.source.data["/astrofoto/"].isArchive = false;

  }

}

const podlasieHandler = {

  pattern: "/podlasie/",

  func: ({ state }) => {

    state.source.data["/podlasie/"].isPodlasiePage = true;

    state.source.data["/podlasie/"].isArchive = false;

  }

}

// const astroHandler = {

//   pattern: "/astrofoto/",

//   func: ({ state }) => {

//     state.source.data["/astrofoto/"].isAstroPage = true;

//     state.source.data["/astrofoto/"].isArchive = false;

//   }

// }

const goryHandler = {

  pattern: "/gory/",

  func: ({ state }) => {

    state.source.data["/gory/"].isGoryPage = true;

    state.source.data["/gory/"].isArchive = false;

    // state.source.data["/gory/"].isPostType = false;

  }

}

const wadirumHandler = {

  pattern: "/wadirum/",

  func: ({ state }) => {

    state.source.data["/wadirum/"].isWadirumPage = true;

    state.source.data["/wadirum/"].isArchive = false;

    console.log('inside handler')

  }

}

const colaboHandler = {

  pattern: "/wspolpraca/",

  func: ({ state }) => {

    state.source.data["/wspolpraca/"].isColaboPage = true;

    state.source.data["/wspolpraca/"].isArchive = false;

  }

}

const timelapseHandler = {

  pattern: "/timelapse/",

  func: ({ state }) => {

    state.source.data["/timelapse/"].isTimelapsePage = true;

    state.source.data["/timelapse/"].isArchive = false;

  }

}

const commercialHandler = {

  pattern: "/komercyjne/",

  func: ({ state }) => {

    state.source.data["/komercyjne/"].isCommercialPage = true;

    state.source.data["/komercyjne/"].isArchive = false;

  }

}

const goPodlasieHandler = {

  pattern: "/go-podlasie/",

  func: ({ state }) => {

    state.source.data["/go-podlasie/"].isGoPodlasiePage = true;

    state.source.data["/go-podlasie/"].isArchive = false;

  }

}

const contactHandler = {

  pattern: "/kontakt/",

  func: ({ state }) => {

    state.source.data["/kontakt/"].isContactPage = true;

  }

}

const privacyPolicyHandler = {

  pattern: "/polityka-prywatnosci/",

  func: ({ state }) => {

    state.source.data["/polityka-prywatnosci/"].isPolitykaPage = true;

    state.source.data["/polityka-prywatnosci/"].isArchive = false;

  }

}

const marsTheme = {

  name: "@frontity/mars-theme",

  roots: {

    /**

     * In Frontity, any package can add React components to the site.

     * We use roots for that, scoped to the `theme` namespace.

     */

    theme: Theme,

  },

  state: {

    /**

     * State is where the packages store their default settings and other

     * relevant state. It is scoped to the `theme` namespace.

     */

    theme: {

      autoPrefetch: "in-view",

      menu: [],

      isMobileMenuOpen: false,

      featured: {

        showOnList: false,

        showOnPost: false,

      },

    },

  },

  /**

   * Actions are functions that modify the state or deal with other parts of

   * Frontity like libraries.

   */

  actions: {

    theme: {

      toggleMobileMenu: ({ state }) => {

        state.theme.isMobileMenuOpen = !state.theme.isMobileMenuOpen;

      },

      closeMobileMenu: ({ state }) => {

        state.theme.isMobileMenuOpen = false;

      },

      init: ({ libraries }) => {

        // Use html2react to process the <img> tags inside the content HTML.

        libraries.html2react.processors.push(image);

        // Add the handler to wp-source.

        libraries.source.handlers.push(homeHandler);

        libraries.source.handlers.push(fotografiaHandler);

        libraries.source.handlers.push(podlasieHandler);

        // libraries.source.handlers.push(astroHandler);

        libraries.source.handlers.push(goryHandler);

        libraries.source.handlers.push(wadirumHandler);

        libraries.source.handlers.push(colaboHandler);

        libraries.source.handlers.push(timelapseHandler);

        libraries.source.handlers.push(commercialHandler);

        libraries.source.handlers.push(goPodlasieHandler);

        libraries.source.handlers.push(contactHandler);

        libraries.source.handlers.push(privacyPolicyHandler);

        console.log('init')

      },

      

      beforeSSR: async ({actions}) => {

         await actions.source.fetch("/contact");

         await actions.source.fetch("/gora");

        }

      },

    },

also in my index.js I used the approach below with same effect.

 <ContactPage when={data.isContactPage}/>
  <Home when={data.isFrontPage}/>
  <PodlasiePage when={data.isPodlasiePage}/>

Can anybody tell me how to get it right?

Looks like it’s working now, maybe you fixed it already

Hi @rborowski.shovv,

As @furrysmile2 says, your categories links seem to be working fine from both Server side and Client Side navigation

Is it working now for you?

Yes, it’s working well. Last thing is that after refreshing page for exmaple http://api-tech.pl/kontakt in Firefox, losing styles of material ui icons, and get huge

3 posts were split to a new topic: 404 page since updating to latest Frontity

A post was split to a new topic: Custom path is leading to the 404 page