I’m working on a simple component which should display last 5 posts’ images from particular category (in my case category id=91). When user clicks on image, related post should be opened.
I had a similar requirement when working with menus. @luisherranz pointed out to me manually stripping the domain wasn’t the correct way to do it and pointed me to the URL helper in Frontity.
Instead of using item.link, run it through URL and use url.pathname.
Thank you Chris, this is exactly what I was looking for.
Do you have an idea, how to refactor this component to allow server side rendering?
Currently it’s only working on client side, becasue I’m using useEffect.
edit:
so far, I was able to achive this with those changes:
I’ve added beforeSSR action to my theme config:
actions: {
theme: {
beforeSSR: async ({ state, actions, libraries }) => {
//fetch only for home page
if (state.router.link == "/") {
await actions.source.fetch("/category/slider-images/");
}
}
}
},
That’s exactly right, you should use actions.source.fetch("/category/slider-images/") instead of doing the fetch yourself.
Actually, when you use actions.source.fetch we strip the domain from item.link so you don’t need to use URL and do it yourself.
And the beforeSSR action you posted is the perfect way to tell Frontity: “don’t do the React rendering in the server until this data is on the state”.
Apart from that, I’d add a useEffect to your <FeaturedPosts> component to populate it when the initial URL was not "/":
const FeaturedPosts = ({ state, actions }) => {
const data = state.source.get("/category/slider-images/");
useEffect(() => {
// Fetch the category in the client if it hasn't been fetched yet.
actions.source.fetch("/category/slider-images/");
}, []);
// Return a loader if data is not ready.
if (!data.isReady) return <div>loading...</div>;
// Once the data is ready, return the items.
const posts = data.items.map(
({ type, id }) => state.source[type][id]
);
return (
<>
{posts.map(p => <a href={p.link}>{p.title.rendered}</a>)}
</>
);
}
I’ve found some weird behavior with my handler, why in state.source.data[“sliderMenu/”] this additional slash is needed? slider menu handler is registered with pattern “sliderMenu”, but when I’ve tried without this slash, state.source.data[“sliderMenu”] is undefined. Here is my handler, which works fine, but to be honest I would expect this should work well without this slash suffix.
export const sliderManuHandler = {
name: "sliderMenu",
priority: 10,
pattern: "sliderMenu",
func: async ({ state, libraries }) => {
const response = await libraries.source.api.get({
endpoint: "posts",
params: { _embed: true, categories: '91', per_page: 5 }
});
const data = await response.json();
//can I use populate, what is the difference?
//const data = await libraries.source.populate({ response, state });
//why "sliderMenu" doesn't work? why this additional slash is needed?
Object.assign(state.source.data["sliderMenu/"], {
data,
isSliderMenu: true,
});
}
}
The ending slash is not needed, it is just added when normalizing the link or whatever you pass to actions.source.fetch(), in order to replicate the same behavior as WordPress.
I agree with not adding the slash for non-URL data, though. Maybe it is better to normalize only URLs, and leave the rest at it is, right @luisherranz?
Anyway, @LiamMcKoy, in case you want to get some object from state.source.data you can also use state.source.get() (it normalizes the input). In your case, for example, it could be