Using react context not working as expected in remix react? - reactjs

Despite having seen working examples of this in non remix projects, it doesn't seem to work in the way I'm implementing it?
I have the following in root.tsx:
export const MyContext = createContext("default");
function Document({ children }: { children: React.ReactNode }) {
return (
<html lang="en">
<head>
<Meta />
<Links />
</head>
<body className="root-body">
<MyContext.Provider value="bonjour!">
<Header />
</MyContext.Provider>
{children}
<ScrollRestoration />
<Scripts />
<LiveReload />
<Footer />
</body>
</html>
);
}
export default function App() {
return (
<Document>
<Outlet />
</Document>
);
}
In my <Header/> component I have:
import { useContext } from "react";
import { MyContext } from "~/root";
export const Header = () => {
const result = useContext(MyContext);
console.log(result);
return(null)
}
The result is then that "default" is printed to the console, but surely from my understanding it should be "bonjour"?
Where am I going wrong?

The output on the server console is from Remix SSR. It does appear that the context is not being applied during server rendering. However, it does show up correctly after hydration. Unfortunately it also results in hydration errors (see browser console).
Anyway, that does seem odd. My understanding is that you can use most hooks server side (except for useEffect and useLayoutEffect).
https://codesandbox.io/s/remix-react-context-mzexmt

Related

How to handle loading times when a user clicks a Link or NavLink in remix run

I am building an app with remix run and using nested components.
When you click a NavLink or Link that loads a component that has a loader function to load data from an api, it can be very slow to get the response and render to the user.
Ideally I would like the URL in the browser to change immediately on click and to load an animation while the component is loading.
I know how I could implement the loading animation with react and the useEffect hook, however I'm not sure how you'd do this with remix and the Link/NavLink tags.
Remix tries to emulate the browser behaviour when navigating, so it doesn't change the URL until the loader has resolved.
However, you can improve the UX by showing some loading UI with useNavigation.
export default function App() {
const navigation = useNavigation();
return (
<html lang="en">
<head>
<Meta />
<Links />
</head>
<body>
{navigation.state !== "idle" ? <div>Loading...</div> : null}
<Outlet />
<ScrollRestoration />
<Scripts />
<LiveReload />
</body>
</html>
);
}
If the data in your loader in extremely slow and you're unable to speed it up, you might want to show fallback UI such as a skeleton which can be done with defer.
export function loader({ params }: LoaderArgs) {
return defer({
// NOTE: `getFoo` isn't awaited
foo: getFoo()
});
}
export default function Component() {
const data = useLoaderData<typeof loader>();
return (
<main>
<h1>Foo</h1>
<Suspense fallback={<div>Skeleton UI...</div>}>
<Await
resolve={data.foo}
errorElement={
<div>Error loading foo</div>
}
>
{(foo) => (
<div>
{JSON.stringify(foo, null, 2)}
</div>
)}
</Await>
</Suspense>
</main>
);
}
The following seems to hold the answers:
import { useNavigation } from "#remix-run/react";
function SomeComponent() {
const navigation = useNavigation();
navigation.state;
navigation.location;
navigation.formData;
navigation.formAction;
navigation.formMethod;
}
You appear to be able to hook into the navigation.state which changes from idle to loading when a link/NavLink has been clicked.

React server component inside a client component in Next 13

I was testing how server and client component interact and came up with something that I don't understand. Here is the code:
// layout.js
"use client";
/* ... imports */
export const Test = createContext();
export default function RootLayout({ children }) {
return (
<html lang="en">
<head />
<body>
<Test.Provider value={"haha"}>
<Header />
{children}
<Footer />
</Test.Provider>
</body>
</html>
);
}
// Header
function Header() {
/* ...imports */
const haha = useContext(Test);
return (
<header>
{haha}
</header>
);
}
Header is a server component, so executed and rendered on the server. But it is still able to access the context which is being provided by the client layout component. How is this possible?

Pass page variables to layout in NextJS

I'm migrating my blog from Jekyll to NextJS and want to achieve the same functionality with the site layout.
The idea is to have metatags defined in a single layout file and fill values with variables that are defined on a page level.
I saw several solutions, one of them is to define metatags in _app.js as described here:
NextJS pass class from page to layout component
but from my understanding so far as a newbie in React/NextJS, it's better to utilize pageProps, but I can't figure out how to code it.
So far I have
_app.js
import 'bootstrap/dist/css/bootstrap.css'
import Layout from '../components/layout/layout';
export default function Blog({ Component, pageProps }) {
return (
<Layout>
<Component {...pageProps} />
</Layout>
);
}
layout.js
import { Fragment } from 'react';
import Image from 'next/image';
import Head from 'next/head';
import MainNavigation from './main-navigation';
export default function Layout({ children }) {
return (
<Fragment>
<Head>
<meta name='viewport' content='width=device-width, initial-scale=1' />
<title>{children.title}</title>
</Head>
<MainNavigation />
<main>
{children}
</main>
</Fragment>
);
}
HomePage.js
export default function HomePage() {
return <div>HomePage</div>
}
I wanted to stick to the original code examples from the official documentation, so left layout as described in question, just
<Layout>
not
<Layout metas={pageProps.metas} ...>
So I just had to define props using getStaticProps:
export async function getStaticProps() {
return { props: { title: 'HomePage' } }
}
export default function HomePage() {
return <div>HomePage</div>
}
and then call it in layout as
{children.props.title}
not
{children.title}
The latter one would work if I just define a regular js variable in HomePAge.js as described in another SO thread I referenced. But I'm not sure if this approach somehow affects static website generation, so I decided to use NextJS built-in feature getStaticProps.
You can take the props you want in pageProps and pass them to the layout component:
<Layout metas={pageProps.metas} ...>
And use them in the <Layout />:
export default function Layout({ metas, children }) {
return (
<Fragment>
<Head>
<title>{metas.title}</title>
</Head>
...
</Fragment>
);
}

React Intl Could not find required `intl` object. <IntlProvider> needs to exist in the component ancestry

I have used latest version of React-intl(^5.20.2). Trying to achieve Enzyme Unit testing in React hook component. but throwing this error "[React Intl] Could not find required intl object. needs to exist in the component ancestry." on UseIntl() inside functional component while running tests
{intl.formatMessage({ id: "Welcome" })}
You need to wrap the component under test with the IntlProvider component. You can do this by creating a wrapper to pass in the options with the render utility.
Wrapper
Pass a React Component as the wrapper option to have it rendered
around the inner element. This is most useful for creating reusable
custom render functions for common data providers.
Example:
import { IntlProvider } from 'react-intl';
export const IntlWrapper = ({ children }) => (
<IntlProvider locale="en">{children}</IntlProvider>
);
Test code
import { render } from '#testing-library/react';
import { IntlWrapper } from '../path/to/testUtils';
...
render(
<ComponentUnderTest />,
{
wrapper: IntlWrapper,
},
);
Okay so in my case it was due to SSR.
In addition of the "locale" Provider which allow language switching.
I've added to my _document.jsx a Provider which get the locale from the router.
class MyDocument extends Document<TDocumentInitialProps> {
static async getInitialProps(ctx: DocumentContext): Promise<TDocumentInitialProps> {
const messages = (await importMessages(ctx.locale)) as Record<string, string> | Record<string, MessageFormatElement[]>;
return {
locale: ctx.locale,
messages,
};
}
render(): JSX.Element {
const { locale, messages } = this.props;
return (
<IntlProvider locale={locale} messages={messages}>
<Html lang={locale}>
<Head>
<link rel="icon" href="/images/layout/favicon.png" />
</Head>
<body>
<Main />
<NextScript />
</body>
</Html>
</IntlProvider>
);
}
}
export default MyDocument;

Error: Target container is not a DOM element ( React/Nextjs)

I have created a context component in React to return a message across all my React pages.
I am getting the error in the subject. Looked at other questions in stackoverflow but none could help me.
Here is the code:
message.js
function Message(props) {
const messageCtx = useContext(MessageBannerContext);
return ReactDOM.createPortal(
<div>
<p>Message</p>
<button>More info</button>
<button onClick={messageCtx.hideMessage}>Ok</button>
</div>,
useEffect(() => {
document.getElementById('message');
}, [])
);
}
export default Message;
_document.js
class MyDocument extends Document {
render() {
return (
<Html>
<Head />
<body>
<Main />
<DeferNextScript />
</body>
<div id="message"></div>
</Html>
);
}
}
export default MyDocument;
Any ideas why I am getting the error in the subject?
Nextjs is run in the server, so the Document Object isn't defined. I've always struggled with that, however you can try a package named jsdom.

Resources