React Intl: async loading just one specific locale data in a Universal App - reactjs

I'm making a multi language universal app using React and I'm having a hard time to find out the best way to deal with locale data.
The app is going to be available in 16 languages and the amount of translated messages are quite big, so I can't load all the messages in one big json (as it is used in most react-intl examples) and I can't import those messages in the webpack generated bundle, I just need to load the user language messages on demand.
I was able to do it when the app is just running on the client side, but I also need it to be working on the server side too. I'm using express for server side rendering and webpack for bundling. Could anyone help to find out the best way to deal with this?

I've been working on something like this lately, although I don't have SSR in my project. I found that pairing dynamic import syntax with React's Suspense component seems to achieve the desired result in my case. Your milage may vary since you need SSR as well, but here's a rough overview of what I found to work for me:
// wrap this around your JSX in App.js:
<React.Suspense fallback={<SomeLoadingComponent />}>
<AsyncIntlProvider>
{/* app child components go here */}
</AsyncIntlProvider>
</React.Suspense>
// the rest is in support of this
// can be placed in another file
// simply import AsyncIntlProvider in App.js
const messagesCache = {};
const AsyncIntlProvider = ({ children }) => {
// replace with your app's locale getting logic
// if based on something like useState, should kick off re-render and load new message bundle when locale changes
const locale = getLocale();
const messages = getMessages(locale);
return (
<IntlProvider locale={locale} messages={messages}>
{children}
</IntlProvider>
);
};
function getMessages(locale) {
if (messagesCache[locale]) {
return messagesCache[locale];
}
// Suspense is based on ErrorBoundary
// throwing a promise will cause <SomeLoadingComponent /> to render until the promise resolves
throw loadMessages(locale);
}
async function loadMessages(locale) {
// dynamic import syntax tells webpack to split this module into its own chunk
const messages = await import('./path/to/${locale}.json`);
messagesCache[locale] = messages;
return messages;
}
Webpack should split each locale JSON file into its own chunk. If it doesn't, something is likely transpiling the dynamic import syntax to a different module system (require, etc) before it reaches webpack. For example: if using Typescript, tsconfig needs "module": "esnext" to preserve import() syntax. If using Babel, it may try to do module transpilation too.
The chunk output for a single locale will look something like this:
(window["webpackJsonp"] = window["webpackJsonp"] || []).push([[0],{
/***/ "./path/to/en-US.json":
/*!*************************************!*\
!*** ./path/to/en-US.json ***!
\*************************************/
/*! exports provided: message.id, default */
/***/ (function(module) {
eval("module.exports = JSON.parse(\"{\\\"message.id\\\":\\\"Localized message text\\\"}\");//# sourceURL=[module]\n//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiLi9zcmMvbG9jYWxpemF0aW9uL2VuLVVTLmpzb24uanMiLCJzb3VyY2VzIjpbXSwibWFwcGluZ3MiOiIiLCJzb3VyY2VSb290IjoiIn0=\n//# sourceURL=webpack-internal:///./path/to/en-US.json\n");
/***/ })
}]);
Hope this helps. Best of luck internationalizing your project! 😁

Related

process is undefined in React

I am building a simple React app that generates a QR code from data. I am interested in inspecting the memory usage when the QR code is generated. I am using the built process.memoryUsage() function but the app throws and exception
Uncaught TypeError: process__WEBPACK_IMPORTED_MODULE_1__.process is undefined
I have tested some different solution, i tried to rollback the react script version to "4.0.3" i tried to download the npm polyfill webpack but there is no success.
I am currently using these imports
import React, { useState, useEffect } from 'react';
import process from 'process';
import './App.css';
const QRCode = require('qrcode');
The function looks like this
let stringData = JSON.stringify(qrData);
console.log("Number of chars in data" + " " + stringData.length);
QRCode.toDataURL(stringData, function (err, url) {
if(err) return console.log("error occured")
//window.location.href = url;
})
const used = process.memoryUsage();
for (let key in used) {
console.log(`${key} ${Math.round(used[key] / 1024 / 1024 * 100) / 100} MB`);
}
}
process is a Node.js API and is not available in the browser where your React app is running, which is why you see that error. If there is an available global process object, it is being polyfilled by something in your build tools, and will not have the memoryUsage method.
There is no equivalent API for the browser, but some related APIs do exist. Note that this is an evolving space and some are non-standard, so be sure to read both the spec and documentation before considering any usage:
Device Memory API (MDN)
Performance.memory (MDN)
You are using a built in nodejs package in a react app. Node executes on the server and has access to system level resources. React runs in the browser and does not. See this article for some tips measuring performance in React.

How to test custom React-query hook that is based on Firebase methods?

I came across react-query-firebase which are hooks that are built on React-query for firebase.
I also found library called mock service worker https://mswjs.io/ however it is based on REST and GraphQL.
Here is an example code how I would use these hooks:
import React from "react";
import { useFirestoreDocument } from "#react-query-firebase/firestore";
import {
doc
} from "firebase/firestore";
import { firestore } from "../firebase";
function GetUser() {
const id = "pW5CizOJOpXezr5lGGshDmKdVpP3";
const ref = doc(firestore, "users", id);
const user = useFirestoreDocument(["users", id], ref);
return (
<div>
{user.isLoading && <div>Loading...</div>}
{user.data && <div>{user.data.data()?.name}</div>}
</div>
);
}
export default GetUser;
I am new to testing and I have no idea how would I have to execute this test, since I am mocking requests can I use random url anyways or does it have to be firebase related methods?
react-query-firebase library is an abstraction over Firebase that encapsulates resource paths (it's an SDK, I believe). Since the paths (URLs) are abstracted from, you have at least two options for how to mock requests issued by such libraries.
Option 1: Use explicit paths
Although the exact resource paths are hidden away, requests still reference existing absolute paths. You can observe those in the "Network" tab of your browser or by enabling a simple request introspection with msw:
// my.test.js
import { setupServer } from 'msw/node'
const server = setupServer(/* no handlers */)
beforeAll(() => server.listen())
afterAll(() => server.close())
Since we're using setupServer with no handlers, all requests that happen in my.test.js will be printed as warnings to stderr. You can observe those warnings to see what resource paths your SDK requests.
The benefit of using an SDK is that it guarantees you a certain resource path structure. Most likely, you will be able to replicate that structure in your mocks:
// src/mocks.js
import { rest } from 'msw'
export const handlers = [
rest.get('https://some-resource.:checksum.firebase.app/path', (req, res, ctx) => res(ctx.text('hello)))
]
Utilize dynamic path segments like :checksum to match a broader range of paths.
The downside of this approach is that your mock definition becomes dependent on the SDK internal details (resource paths). Any updates to the SDK may break your mocks, since they may update the path structure internally without denoting it as a breaking change (abstracted paths are not public API).
Option 2: Spy on the SDK
Alternatively, you can spy on the SDK you're using. An example of such a spy would be using jest.spyOn on the react-query-firebase directly:
// my.test.js
import * as reactQueryFirebase from 'react-query-firebase'
it('creates a user', () => {
jest.spyOn(reactQueryFirebase, 'SOME_METHOD_NAME')
.mockReturnValue(/* mock */)
})
The downside of this method is that you're stubbing the functions from a third-party library, which means your code under test never calls those functions. This decreases the reliability of such a test, and you should, generally, avoid resorting to this approach.
I do recommend you research Firebase, since major SDK providers often have guidelines on mocking their API. Often such a guidance would include using a dedicated third-party package authored by the SDK provider that allows mocking their internal resources.

Recoil Duplicate atom key when using with Webpack Module Federation

I'm using Webpack Module Federation to create 2 React applications: host and child.
In the host, I create atoms.ts and selector.ts filed and I expose them via the plugin under the expose section:
exposes: {
"./atoms": "./src/recoil/atoms.ts",
"./selectors": "./src/recoil/selectors.ts",
}
Inside the child, I just consume that via the remotes section:
remotes: {
host: "host#http://localhost:3000/remoteEntry.js",
}
Then, in the code of the child I use that like that:
import {someSelector} from "host/selectors"
const val = useRecoilValue(someSelector);
It's working fine but I got this warning in the console:
Duplicate atom key "userAuthState". This is a FATAL ERROR in
production. But it is safe to ignore this warning if it occurred because of
hot module replacement.
Does anyone face that issue and know if it's really a problem or how we could hide the warning?
Another related q:
Is it ok that the host will contain <RecoilRoot> and also the child will contain <RecoilRoot> ? because I want both will manage their own state but also share atom/selectors.
Thanks!
Regarding your second question:
Yes, this is totally fine. The nested <RecoilRoot> will create its own context and every atom referenced below the second root will be independent from the upper root. This is also explained in the docs.
Regarding the first question: As the log states this is fine as long as it occurs during development. Sometimes during the hot module replacement recoil throws away atoms and reinstantiates them causing this duplication to happen internally.
But as long as this warning doesn't pop up in your production code, everything is fine.
Are you importing the atoms or the selectors in your host application using a local path?
You need to include in your host webpack config its own entrypoint as remote and import your atoms from 'host/atoms'
I think this could solve your issue.
You can ignore the warning output (if you can bare it), functionality is not affected.
Also, you can install the intercept-stdout package and add the following to next.config.js (outside of the exported configuration):
const intercept = require("intercept-stdout")
// safely ignore recoil warning messages in dev (triggered by HMR)
function interceptStdout(text) {
if (text.includes("Duplicate atom key")) {
return "";
}
return text;
}
if (process.env.NODE_ENV === "development") {
intercept(interceptStdout);
}
This way can omit the annoying warning in console.

How to use the new #Shopify/app-bridge with #Shopify/polaris-react

Shopify recently released their new #shopify/app-bridge, but it is unclear to me how it should be used alongside #shopify/polaris.
For example, I have tried to make a React component that will use the app-bridge and polaris to display a toast.
import React, { Component } from "react";
import * as PropTypes from "prop-types";
import { Toast } from "#shopify/app-bridge/actions";
import { Page } from "#shopify/polaris";
class Start extends Component {
static contextTypes = {
polaris: PropTypes.object
};
showToast() {
console.log("SHOW TOAST");
console.log(this.context.polaris.appBridge);
const toastNotice = Toast.create(this.context.polaris.appBridge, {
message: "Test Toast",
duration: 5000
});
toastNotice.dispatch(Toast.Action.SHOW);
}
render() {
this.showToast();
return (
<Page title="Do you see toast?">
<p>I do not see toast.</p>
</Page>
);
}
}
export default Start;
But it does not seem to dispatch the action. Any ideas on why not? Note that my app is wrapped in the AppProvider and app-bridge is initialized.
ReactDOM.render(
<AppProvider
apiKey={process.env.REACT_APP_SHOPIFY_API_KEY}
shopOrigin={queryString.parse(window.location.search).shop}
>
<Start />
</AppProvider>,
document.getElementById("root")
);
Any suggestions?
So after a lot of debugging, I found out from Shopify that inside App Bridge, before taking any action, they check that the localOrigin matches the appURL (one that's entered in the partners dashboard). In my case, I have a backend (node.js on heroku used for authentication) and a frontend (react bundle on firebase) my app starts by hitting the backend, and then if authentication checks out, it redirects to the front end. And hence the localOrigin does not match... hmmm, I'm very glad to have figured this out since I lost a lot of sleep over it. Now the question is what to do about it... maybe this is something that could be updated with AppBridge? Or is there a better design I should be considering?
There is now #shopify/app-bridge-react,
https://www.npmjs.com/package/#shopify/app-bridge-react
Shopify supposedly doesn't have docs for it yet though... But, someone can update my answer when they come out with them. :)
NOTE:
Be sure to have, static contextType = Context; to get access to this.context for dispatching actions/etc in your components.
(Hopefully this saves you days of suffering haha I'm not a React developer, so, yeah... this was not marked as "crucial" or anything in the examples).
I also wanted to address #SomethingOn's comment, but I don't have enough reputation to comment...
You actually can debug an iframe. In chrome dev tools, on top where it says "top", you can actually select a frame that you want to debug.
https://stackoverflow.com/a/8581276/10076085
Once you select the Shopify App iframe, type in "window.location" or whatever you want!
Shopify's docs and examples are limited and I'm running into a bunch of issues myself working on a Shopify App, so I just want to spread help as much as possible!

Import ES6 module in browser from string in browser, instead of file

I am writing an offline-first coding education app that uses Babel, Webpack and React. One of my goals is to allow students to write ES6 compliant javascript code with imports, in the browser. I would like to let students write ES6 modules in a text editor, give them a name and treat them as modules that can be called from other ES6 modules.
I would prefer that they do not have to upload the code to a server, since this is an offline component. I also would prefer to avoid Service Workers in the implementation, so that this works in more than just Chrome and Firefox.
I'm imaginig a component like this:
// React Component
import Babel from 'babel';
class JSEditor extends Component {
render(){
module1; // contains code from first module
module2; // contains code from second module
return (
<textarea>
let module1 = "const foo = 42; export default foo;';
</textarea>
<textarea>
let module2 = "import foo from 'module1'; console.log(foo);"
</textarea>
<button onclick={this.evalCode} />
)
// what goes here?
evalCode() {
let code1 = Babel.transform(this.module1);
let code2 = Babel.transform(this.module2);
eval(code2);
}
}
When I do Babel.transform in this way, it does not return or understand the module format I need. How do I convince Babel to let me use modules without actual files involved?

Resources