react-admin icon indicating copy to clipboard operation
react-admin copied to clipboard

Render Title with context instead of React Portal

Open elstgav opened this issue 2 years ago • 2 comments

Currently the <Title> is rendered to a React portal; either <TitlePortal/> or any component with the react-admin-title ID. This approach is fairly brittle, as you can only render the title once on the page, and there’s no way to access the current page title. This also makes it difficult to implement dynamic window titles (like in #5893)

Describe the solution you’d like:

Instead of <Title> rendering with a React portal, have it set the title in the global CoreAdminContext instead, and provide a helper to access the title, e.g. useTitle() // => { title, defaultTitle, ...etc }.

This would greatly simplify rendering the title in multiple/other places, and could simplify updating the window title with something like React Helmet:

const WindowTitle = () => {
  const { title } = useTitle()

  return (
    <Helmet>
      <title>{title} - My Admin App</title>
    </Helmet>
  )
}

Additional context I think this would simplify:

  • #5893

elstgav avatar May 02 '23 00:05 elstgav

I agree this would be better. We had this before contexts were a thing. Unfortunately, this would be a breaking change so it'll have to wait for v5

djhi avatar May 02 '23 16:05 djhi

@elstgav This would be great! Would allow me to delete a lot of code which is always nice

davidhenley avatar May 13 '23 21:05 davidhenley

This suggestion poses a challenge because we allow the user to customize the title from within the view context. Let me explain with an example.

Let's say the Post Edit view defines a custom title component that displays the post title.

const PostEdit = () => (
   <Edit title={<PostTitle />}>
      ...
   </Edit>
);

The custom title component must access the record with useRecordContext:

const PostTitle = () => {
    const translate = useTranslate();
    const record = useRecordContext();
    return (
        <>
            {record ? translate('post.edit.title', { title: record.title }) : ''}
        </>
    );
};

Currently, this component is currently rendered by <Edit>, so it has access to the record context.

// shortened for simplicity
const Edit = ({ resource, id, title, children }) => {
  const { data: record, isLoading } = useGetOne(resource, { id });
  if (isLoading) return null;
  return (
    <RecordContextProvider value={record}>
        <Title title={title} />
        {children}
    </RecordContextProvider>
  );
};

The <Title> component places the rendered title in the <TitlePortal>, which is rendered by the <AppBar>.

Now, suppose that <Edit> doesn't render <PostTitle>, but rather stores it in a TitleContext. The layout would render <AppBar>, and <AppBar> would watch the TitleContext value and render it. This can't work because the <PostTitle> component would render outside of <Edit>, and therefore wouldn't have access to the RecordContext.

The only solution would be to store in the TitleContext the rendered element instead of the element itself. But then how can we render an individual element, that may be composed of HTML tags ? How can we mix this with another render tree? React doesn't provide APIs for that. To update one part of the UI from another, React offers Portals, and that's why I think we already have the right implementation.

Now, your issue mentions two requirements:

  1. Being able to render the title at several places. I don't see a use case for that, would you mind explaining why you may need this?
  2. Using the view title to set the window title: the <Title> component can use Helmet (or React 19) to set it via the <title> element. This can be done in a backwards compatible way, without removing the portal.

fzaninotto avatar May 27 '24 17:05 fzaninotto

This can't work because the <PostTitle> component would render outside of <Edit>, and therefore wouldn't have access to the RecordContext.

I think portals actually solve this issue. Although it will render in another part of the DOM, it still will be in the Edit components tree

djhi avatar May 28 '24 07:05 djhi

@djhi Yes, that's what I wanted to say. The current implementation, using portals, works. An alternative implementation based on Contexts, as demanded in this issue, can't work.

So I'm closing this issue as it would be too much of a breaking change for a benefit that isn't apparent.

Feel free to add comments if you have use cases that require that change.

fzaninotto avatar May 28 '24 07:05 fzaninotto