eleventy-plugin-react icon indicating copy to clipboard operation
eleventy-plugin-react copied to clipboard

Feature: page-level data getter for components

Open waspeer opened this issue 3 years ago • 6 comments

Hi!

First of all, I love the idea and direction of this library. The inclusion of partial hydration is so awesome! As soon as I get around it I will try to contribute to the tests.

I have a question about the page templates. Is it possible to provide a data method, as documented in the javascript template documentation of Eleventy? This would open up the possibility of using Eleventy's pagination and permalink features. Or is the intention of this library more focussed towards using the same file-based type routing like next has?

waspeer avatar Apr 10 '21 09:04 waspeer

Any contributions are most welcome!

We could definitely do this. Global/directory-level data already works (I run a blog generated using MD files with frontmatter), and it would be nice to be able to do this on a per-page basis.

A few thoughts/questions to help flesh out a game plan:

  1. I think we would have to limit this to "top-level" components (in our case, the component that represents a page), so we can run all the data getting logic before calling ReactDOM.render(). Is that how you envisioned this?
  2. If so, I think it makes sense to have a reserved named export in top level component files (currently, the component is the default export). Maybe something like getData() or getPageData()? We can make it a function that can optionally be async in case that's useful for folks.
  3. The alternative I've thought of is adding support for YAML-style frontmatter in JS comments, but that's probably more complex and error prone. Plus there's already precedence for exporting data schemas/getters from component modules in Eleventy, Gatsby, etc.

kaicataldo avatar Apr 11 '21 22:04 kaicataldo

Awesome! Is your blog open source? I would love to go through the code to see how that works.

I like the option of a reserved named export. It was intuitively the first thing I tried out. Limiting this to the top-level components sounds good. It would feel like an abstraction on top of the 11ty.js template type.

It would be really awesome of we could retain the huge flexibility of the 11ty.js template type as much as possible, so the data method/object would go beyond just data fetching. For example, pagination:

module.exports = {
  data: {
    layout: 'page.njk',
    pagination: {
      data: 'pages', // eg. from a global data file
      size: 1,
      alias: 'page',
    },
    permalink: ({ page }) => `/${page.location.sectionSlug}/${page.location.pageSlug}`,
  },

  render: (data) => {
    // ... //
  }
};

I'm not yet fully familiar with your implementation here, but do you think it would be possible to be able to provide a reserved named export that is a 1:1 mapping to the 11ty.js data function/object? We could maybe deliberately exclude a few options that might not make sense here like templateEngineOverride.

waspeer avatar Apr 12 '21 07:04 waspeer

Can you explain what the benefit of a render() method would be here, as opposed to understanding that in your component (function MyComponent(data) {}), data would include the specific data defined in the data property? Also, it looks like data() is a method in this case, so I'd want to keep that signature.

As far as implementation goes, I would expect that we would call data() if it exists, and then ensure that it's merged last into the data cascade so it overrides data higher up in the data hierarchy. The one thing I'm not sure about is how functions will work, as we'll probably have to call them individually with the correct data as well (seems doable, just have to look at how the JS templating engine does this currently).

kaicataldo avatar Apr 12 '21 17:04 kaicataldo

My blog unfortunately isn't open source, sorry! It's structured like this:

- .eleventy.js
- src/
  - layouts/
    - Post.jsx
  - pages/
    - index.js
    - about.js
    - posts/
      - posts.json
      - post1.md
      - post2.md 

where .eleventy.js contains

module.exports = function (eleventyConfig) {
  // Add collection of blog posts
  eleventyConfig.addCollection('posts', (collectionApi) =>
    collectionApi.getFilteredByGlob('src/pages/posts/*.md').reverse(),
  );
}

posts.json contains

{
  "layout": "Post",
  "permalink": "writing/{{ page.date | dateFormat: \"yyyy-MM-dd\" }}-{{ page.fileSlug }}/"
}

and Post.jsx is a top-level page React component. This just works out of the box for me with Eleventy's existing configuration methods.

LMK if I can clarify anything for you :)

kaicataldo avatar Apr 12 '21 17:04 kaicataldo

Thanks for the example! :) that's really helpful.

I don't think the render method is necessary for this plugin. I just included an example of how I use the data method in a javascript template now. Something like this would be perfect:

export default function MyComponent(data) {
  // ... //
}

export async function data() {
  // ... //
}

My point was that the data method should have 1:1 mapping to that in a 11ty.js template (keeping the signature a function also sounds good), but based on your last comment I think we're on the same page here.

waspeer avatar Apr 13 '21 09:04 waspeer

Cool, sounds like it! Yeah, I think this would be a great addition. Will try to find some time to add it in myself, but also welcome a PR from the community if someone beats me to it :)

kaicataldo avatar Apr 13 '21 17:04 kaicataldo