Rename `source.get` and `source.data` to `source.info`

Description

We have seen that the state.source.get() function and the state.source.data object, while crucial to understand how source and Frontity works, are hard to understand because:

  1. It’s hard to know that state.source.get() returns things stored in state.source.data because their names don’t correlate.
  2. By just inspecting the state, it’s really hard to know that you should always use state.source.get() and never access state.source.data directly.
  3. Both the get and the data names don’t tell anything to the user about the type of data that they will return. Is it all the data or is it just a part?

User Stories

As a Frontity beginner
I want to quickly understand how to access information about URLs
so that I can write my own logic faster

Possible solution

We should:

  • Use a single name for both, so it’s clear that they are related.
  • Change the name to show that it doesn’t contain all the data, only some information about that link.

My proposal is:

  1. Use state.source.info():
state.source.info("/some-link");
  1. Use state.source.infoObjects or state.source.infoData to store the objects.

That way, when people inspect state.source in the console it will be easier to make a mental connection between them.

Backwards compatibility

To keep backward compatibility, we can:

  • Keep state.source.get() (but add a deprecation warning). We can even use derived state to return state.source.info.
  • Keep state.source.data for old handlers that write directly there. state.source.info should look first in state.source.infoObjects but if the info is not found there, it should still look in state.source.data.

To be completely honest, I don’t think these new names will ease the comprehension of these methods/properties

My suggestion is:

  • We rename state.source.data to state.source.links as what is really storing is a list of links with info on each one
  • We rename state.source.get() to state.source.getLinkInfo() as I think it’s a better way to name a method that is returning an object with some properties describing what this link is about

I like making a distinction between info and data:

  • info → description of the resource
  • data → data of the resource

Thanks for chiming in, Juanma.

I totally agree with this, we should use info for the description of the resource:

const info = state.source.info("/some-url");

and data to refer to the real data of the resource:

const info = state.source.info("/some-url");
const data = state.source[info.type][info.id];

in our code and our docs.

For that reason, I think state.source.info makes sense.


The way I see it, state.source.getLinkInfo doesn’t add much value over state.source.info and it’s longer to write and to remember (“Was getLinkInfo or getInfoLink?”").

And the problem I see with using something like state.source.links for the info objects is that it’s not clear that that is a “store” only, and shouldn’t be accessed directly.

// This is wrong, but it's not obvious:
const info = state.source.links["/some-url"];
// This is right:
const info = state.source.info("/some-url");

That’s the reason I propose the use something like state.source.infoObjects, so people is not tempted to access it directly and they stick to state.source.info.

// This is wrong. I think it's a bit more obvious.
const info = state.source.infoObjects["/some-url"];
// This is right:
const info = state.source.info("/some-url");

I don’t mean this is perfect, it’s just my reasoning behind it.

Now that Juanma mentioned this:

I think that infoData is a worse option than infoObjects because data is going to be the data of the resource.

My reasoning here is:

  • Methods’ names should be verbs → getSomehing(), doSomething() so it’s clear that is a method that should be called → That’s why I think a getInfo name is cleared than just info (as it add the perception of being a method)
  • But just getInfo is not enough as we need to know from which resource are we getting that resource → getInfoLink or getInfoFromLink.

The second point (getInfoLink vs getLinkInfo vs getInfoFromLink) I agree it adds some complexity but this could be solved by just naming the method getInfo and then pass an object as the only parameter where you can define the resource you want to get the info from by setting some properties

getInfo({link: "/some-url"})

What is the problem of accesing this data directly instead of through the current method state.source.get?

Also if you need to have data in the state that is not supossed to be accesed directly maybe another approach like naming it like state.source.__links or state.source.__links__ would be a better way


TBH when I think the process through with the existing names and then with the proposed names (info, infoObjects), I find that thinking it through is easier and the process makes more sense with the existing names.

Since what is currently stored in state.source.data is meta-information about the data relating to data stored in state.source.attachment, state.source.page, etc…, how about changing state.source.data to state.source.meta and state.source.get to state.source.getMeta ?

The term meta also implies that it’s data that shouldn’t be altered or accessed directly.

1 Like
// This doesn't work:
state.source.data["/some-url"];
// Because the info object keys contain a trailing slash:
state.source.data["/some-url/"];

// But with the function it works:
state.source.get("/some-url");

In the future, it’s going to be even more important when we add Source Converters, which are similar to Router Converters but to make possible that two or more links point to the same info object.

Right now, these point to different info objects:

state.source.get("/some-url");
// => points to state.source.data["/some-url/"];
state.source.get("/some-url?utm_campaign=xxx");
// => points to state.source.data["/some-url?utm_campaign=xxx"];

but both should point to the same one: state.source.data["/some-url/"];

I think the question is: If I am a Frontity newbie and I’m inspecting the state in the console, and I see state.source.meta with all the information about my links, what would prevent me from going back to my code and use?

state.source.meta["/some-url"];

That’s what I’m trying to avoid. Of course they can do with whatever name we choose, but I think we can choose one that is not so obvious.

I like @juanma’s idea of using state.source._something and state.source.something(). They are clearly related and using the _something version will feel wrong in JavaScript.

Also, I don’t know if this is because I’m not a native speaker but meta remembers me more to the meta tags of the <head> or the post_meta of WordPress than to actual information about an entity. But that may be just me.

The term “meta” means “information about an entity”. See definition 2 here: https://www.merriam-webster.com/dictionary/meta

So in the case of post_meta that you reference, post_meta is information about the post.

1 Like

Great conversation guys :slightly_smiling_face:

There’s yet another consideration. We usually say that this is for links:

state.source.get("/some-link");

but this will also be used to fetch and access other entities, not related to specific URLs of your site. For example, we are about to add support for comments:

// Fetch comments for the post with id 123:
actions.source.fetch("@comments/123");

// Get information about the comments of the post 123:
const info = state.source.get("@comments/123");

// Get the individual comments:
info.items.map((comment) => {
  const comment = state.source.comment[comment.id];
  // ...
});

This is similar to accessing an archive, for example, but "@comments/123" is not really a link.

So we’ve always had a bit of a debate with this to be honest, because link feels right to make it clear when you first starts using Frontity, but later on it may be confusing when you realize that these APIs can be used for more things than just links.

To solve that I think that a better design of the API is that methods receive an object so it can be escalated

// [1] get MetaData from a link
state.source.getMeta({ link: '/category/voices-and-views/' })

// [2] get MetaData from a resource that is not specifically a link
state.source.getMeta({ resource: '@comments/123' })

// other options for [2]
state.source.getMeta({ handler: '@comments/123' })
state.source.getMeta({ post: 123, resource: 'comments' })

Receiving named arguments is a pattern that I strongly recommend as:

  • It crearly let you see the type of data you’re sending to the method
  • It’s very scalabale as you can increase the properties of this “argument” object managed by the method without affecting existing code
  • With destructuring assignment and some object features is really easy to work with objects in javascript

Thanks for the feedback guys, I’ll take it into consideration :slightly_smiling_face:


I’ve just had an idea to make this even simpler and at the same time solve the nested derived state problem in Frontity Connect, using the RES hooks.

The API is just a draft. It won’t be like that in Frontity if this ever makes it. And the way it is right now it won’t work for object overwrites, so I have to think about that. But the PoC is working: https://codesandbox.io/s/react-easy-state-object-filter-keys-and-nested-derived-state-8vny4

The idea would be to use object key filters instead of my proposal of Source Converters and avoid the need for an additional get or getSomething function:

export default {
  state: {
    hooks: {
      access: {
        source: {
          dataTrailingSlash: {
            path: "source.data.*",
            library: "source.dataTrailingSlash",
            priority: 5,
          },
          dataRemoveQueries: {
            path: "source.data.*",
            library: "source.dataRemoveQueries",
            priority: 6,
            options: {
              // Whitelist the search query ("s").
              whitelist: {
                s: true
              }
            }
          }
        },
      },
    },
  },
  libraries: {
    hooks: {
      access: {
        source: {
          dataTrailingSlash: ({ key }) => ({
            key: key.replace(/\/($|\?)/, ""),
          }),
          dataRemoveQueries: ({ key, options }) => ({
            // You can access options and act accordingly.
            key: key.replace(/\?.*$/, "");
          })
        },
      },
    },
  },
};

Of course, any package can add these type of filters or modify the options of a filter using the state.

// frontity.settings.js
const settings = {
  state: {
    hooks: {
      access: {
        source: {
          dataRemoveQueries: {
            options: {
              // Whitelist other query.
              whitelist: {
                photo: true,
              },
            },
          },
        },
      },
    },
  },
};

Then the object can be accessed normally, but it’ll return the correct object:

const state = store({
  data: {
    "/url": {
      link: "/url",
    },
  },
});

state.data["/url"].link; // <- returns "/url"
state.data["/url/"].link; // <- returns "/url"
state.data["/url?query=true"].link; // <- returns "/url"

The same approach can also be used for derived state and nested derived state:

export default {
  state: {
    source: {
      author: {
        1: {
          name: "Jon",
          surname: "Snow",
        },
      },
    },
    hooks: {
      access: {
        users: {
          path: "source.author.*.fullname",
          library: "source.authorFullname",
        },
      },
    },
  },
  libraries: {
    hooks: {
      access: {
        source: {
          authorFullname: ({ state, path }) => ({
            value:
              state.source.author[path[2]].name +
              " " +
              state.source.author[path[2]].surname,
          }),
        },
      },
    },
  },
};

It’s probably not the best API so maybe it’s better to add an additional one for derived, but I’m glad this is possible.

Hey guys, I’ve been thinking a lot about this, and after hearing to talk about “Post Metadata” in one of your videos, I think you have to go with state.info.

I agree it may be a more precise term, but for me, and I guess for anyone else with their brain heavily influenced by years of WordPress development, hearing about “Post Metadata” always shifts my attention to this:

This is really confusing because we would have to things named “Post Metadata”:

// Post Metadata.
const meta = state.source.meta["/some-post"];
// Also Post Metadata, but a different one.
const meta = state.source.post[meta.id].meta;

So this is much more clear:

// Post Info.
const info = state.source.info["/some-post"];
// Post Metadata.
const meta = state.source.post[info.id].meta;

Also, the word “information” is used to describe the meta word itself, so I guess it’s really close in meaning:


I have also updated my previous post to reflect the latest proposal for hooks (yet to be written as a proper Feature Discussion).

Thanks to those hooks, we can avoid the getter and go directly with the object:

const info = state.source.info["/some-post"];

For me, it is very strange having a property called info where inside there’s a list of links (even where every link contains an object with info or metadata for every link)

For me, it would be much more comprehensive to rename data to link

// Link Info.
const link = state.source.link["/some-post"];

if (link.type === "post") {
  const postData = state.source.post[link.id]
  const postMeta = postData.meta
}

or directly…

const link = state.source.link["/some-post"];
const data = state.source[link.type][link.id]

Link is also confusing because links would also contain a link property.

const link = state.source.link["/some-post"];

link; // <- an object containing info about "/some-post".
link.link; // <- the canonical link of "/some-post".

Another option would be to keep it as state.source.data. As we are going to get rid of state.source.get(), we won’t have the confunsion between both anymore:

const data = state.source.data["/some-post"];

I don’t have a preference between info and data.

This link property can be renamed to canonical to get a structure like this…

link: {
  ...,
  '/2016/the-beauties-of-gullfoss/': {
    id: 60
    isFetching: false
    isPost: true
    isPostType: true
    isReady: true
    canonical: "/2016/the-beauties-of-gullfoss/"
    query: Proxy {}
    type: "post"
   },
...
}

So, your example could be something like this…

const link = state.source.link["/some-post"];

link; // <- an object containing info about "/some-post".
link.canonical; // <- the canonical link of "/some-post".

or we could directly things like…

state.source.link["/some-post"].canonical
state.source.link["/some-post"].isReady
state.source.link["/some-post"].type

IMHO this is less confusing than the current generic data

Hey, thanks for the follow-up Juanma :slightly_smiling_face:

The problem of changing the link name to canonical in just this place is that link is used across the whole Frontity framework because it’s the way WordPress names it in the REST API (post.link) so we decided to stick to it. It’s short for “permalink”, which is a WordPress term.

I don’t using link for anything different than the string that refers to the URL/permalink (post.link) or renaming it to canonical just in source.data is a good idea.

I think the change could only be done in the property referring to this link under the current data (suggested link)

link: {
  ...,
  '/2016/the-beauties-of-gullfoss/': {
    id: 60
    isFetching: false
    isPost: true
    isPostType: true
    isReady: true
    permalink: "/2016/the-beauties-of-gullfoss/"
    query: Proxy {}
    type: "post"
   },
...
}

In the rest of the entities in the state it could remain bein called link

post: {
  1: {
    acf: []
    author: 3
    categories: (8) [6, 57, 59, 7, 56, 3, 8, 58]
    comment_status: "closed"
    ...
    link: "/2016/iceland/"
    meta: {spay_email: ""}
    modified: "2019-05-17T10:33:42"
    modified_gmt: "2019-05-17T08:33:42"
    ping_status: "closed"
  }
}

So we can have

state.source.link["/some-post"].permalink
state.source.link["/some-post"].isReady
state.source.link["/some-post"].type

and

state.source.post[1].link

Another option could be using _link instead of data

_link: {
  ...,
  '/2016/the-beauties-of-gullfoss/': {
    id: 60
    isFetching: false
    isPost: true
    isPostType: true
    isReady: true
    link: "/2016/the-beauties-of-gullfoss/"
    query: Proxy {}
    type: "post"
   },
...
}

So, we could be something like this…

const { link } = state.source._link["/some-post"];

or

state.source._link["/some-post"].link
state.source._link["/some-post"].isReady
state.source._link["/some-post"].type

I’m very in favour with using _ for properties of the state that should not be accessed directly. Maybe _link is a good option.