react-intl, async translation import and SSR - javascript

I have a React app which uses react-intl to provide translations and it's working fine for browser DOM rendering. I have a component which deals with async locale/translation based on props it receives, using import() and then once locale + translation data are loaded, <IntlProvieder> is rendered. Like so:
// constructor
import(`react-intl/locale-data/${langCode}`).then((localeData) => {
addLocaleData(localeData.
this.setState({localeDataLoaded: true});
});
import(`../../translations/${locale}.json`).then((translations) => {
this.setState({translations: translations.default});
});
render() {
if (!this.state.translations || !this.state.localeDataLoaded) {
return null;
}
return (
<IntlProvider locale={this.props.locale} messages={this.state.translations} >
{this.props.children}
</IntlProvider>
);
}
However, when it comes to SSR, the setState() calls don't trigger render() thus blocking the rest of the app from rendering.
I'd like to know what others are doing with regards to i18n and server/DOM rendering. Ideally I'm not going to be sending extraneous locale data to users over the web and I'd like a single component to manage all of this for both renderers.
Alternatively I may end up baking translations into the output file and generating JS files per locale

For SSR you'd have to load things before rendering in the request lifecycle, then pass those down to react-intl

Related

React server side component - alternative to `response.readRoot()` function

In React server components official GitHub example repo at exactly in this line here they are using response.readRoot().
I want to create a similar app for testing something with RSC's and it seems like the response does not contain the .readRoot() function any more (because they have updated that API in the react package on npm and I cannot find anything about it!). but it returns the tree in value property like below:
This means that whatever I render in my root server component, will not appear in the browser if I render that variable (JSON.parse(value) || not parsed) inside of my app context provider.
How can I render this?
Basically, if you get some response on the client side (in react server components) you have to render that response in the browser which has the new state from server but since I don't have access to readRoot() any more from response, what would be the alternative for it to use?
I used a trick o solve this issue, but one thing to keep in mind is that they are still unstable APIs that react uses and it's still recommended not to use React server component in the production level, uses it for learning and test it and get yourself familiar with it, so back to solution:
My experience was I had a lot of problems with caching layer they are using in their depo app. I just removed it. My suggestion is to not use it for now until those functions and APIs become stable. So I Removed it in my useServerResponse(...) function, which in here I renamed it to getServerResponse(...) because of the hook I created later in order to convert the promise into actual renderable response, so what I did was:
export async function getServerResponse(location) {
const key = JSON.stringify(location);
// const cache = unstable_getCacheForType(createResponseCache);
// let response = cache.get(key);
// if (response) return response;
let response = await createFromFetch(
fetch("/react?location=" + encodeURIComponent(key))
);
// cache.set(key, response);
return response;
}
and then creating a hook that would get the promise from the above function, and return an actual renderable result for me:
export function _useServerResponse(appState) {
const [tree, setTree] = useState(null);
useEffect(() => {
getServerResponse(appState).then((res) => {
setTree(res);
});
}, [appState]);
return { tree };
}
and finally in my AppContextProvider, I used that hook to get the react server component tree and use that rendered tree as child of my global context provider in client-side like below:
import { _useServerResponse } from ".../location/of/your/hook";
export default function AppContextProvider() {
const [appState, setAppState] = useState({
...someAppStateHere
});
const { tree } = _useServerResponse(appState);
return (
<AppContext.Provider value={{ appState, setAppState }}>
{tree}
</AppContext.Provider>
);
}
I know that this is like a workaround hacky solution, but it worked fine in my case, and seems like until we get stable APIs with proper official documentation about RSCs, it's a working solution for me at least!

Best practice for Next.js data fetching inside a component

I have a menu component that appears globally. What is the best practice for getting data into that component?
I'm trying to take advantage of static generation that Next.js offers but all data fetching guidance from the Next.js team relates to pages. getStaticProps and getStaticPaths seem to pertain to page generation, not data for components. Is their SWR package the right answer, or Apollo Client?
Typically in hooks-based React, I'd just put my data call into useEffect. I'm not sure how to reason this out being that everything is rendered at build time with Next.
This is such a tricky problem, I think we need to lay out some background before a solution comes into focus. I'm focusing in the React.js world but a lot of this would apply to Vue/Nuxt I'd imagine.
Background / Static Generation Benefits:
Gatsby and Next are focused on generating static pages, which vastly improves performance and SEO in React.js sites. There is a lot of technical overhead to both platforms beyond this simple insight but let's start with this idea of a digital machine pumping out fancy HTML pages for the browser.
Data Fetching for Pages
In the case of Next.js (as of v9.5), their data fetching mechanism getStaticProps does most of the heavy lifting for you but it's sandboxed to the /pages/ directory. The idea is that it does the data fetching for you and tells the Next.js page generator in Node about it during build time (instead of doing it component-side in a useEffect hook - or componentDidMount). Gatsby does much the same with their gatsby-node.js file, which orchestrates the data fetching for page building in concert with a Node server.
What about Global Components that need data?
You can use both Gatsby and Next to produce any kind of website but a huge use case are CMS-driven websites, because so much of that content is static. These tools are an ideal fit to that use case.
In typical CMS sites, you will have elements that are global - header, footer, search, menu, etc. This is where static generation faces a big challenge: how do I get data into dynamic global components at build time? The answer to this question is... you don't. And if you think about this for a minute it makes sense. If you had a 10K page site, would you want to trigger a site-wide rebuild if someone adds a new nav item to a menu?
Data Fetching for Global Components
So how do we get around this? The best answer I have is apollo-client and to do the fetch client side. This helps us for a number of reasons:
For small size queries, the performance impact is negligible.
If we need to rebuild pages for changes at the CMS layer, this slides by Next/Gatsby's detection mechanisms, so we can make global changes without triggering gigantic site-wide rebuilds.
So what does this actually look like? At the component level, it looks just like a regular Apollo-enhanced component would. I usually use styled-components but I tried to strip that out so you can could better see what's going on.
import React from 'react'
import { useQuery, gql } from '#apollo/client'
import close from '../public/close.svg'
/**
* <NavMenu>
*
* Just a typical menu you might see on a CMS-driven site. It takes in a couple of props to move state around.
*
* #param { boolean } menuState - lifted state true/false toggle for menu opening/closing
* #param { function } handleMenu - lifted state changer for menuState, handles click event
*/
const NAV_MENU_DATA = gql`
query NavMenu($uid: String!, $lang: String!) {
nav_menu(uid: $uid, lang: $lang) {
main_menu_items {
item {
... on Landing_page {
title
_linkType
_meta {
uid
id
}
}
}
}
}
}
`
const NavMenu = ({ menuState, handleMenu }) => {
// Query for nav menu from Apollo, this is where you pass in your GraphQL variables
const { loading, error, data } = useQuery(NAV_MENU_DATA, {
variables: {
"uid": "nav-menu",
"lang": "en-us"
}
})
if (loading) return `<p>Loading...</p>`;
if (error) return `Error! ${error}`;
// Destructuring the data object
const { nav_menu: { main_menu_items } } = data
// `menuState` checks just make sure out menu was turned on
if (data) return(
<>
<section menuState={ menuState }>
<div>
{ menuState === true && (
<div>Explore</div>
)}
<div onClick={ handleMenu }>
{ menuState === true && (
<svg src={ close } />
)}
</div>
</div>
{ menuState === true && (
<ul>
{ data.map( (item) => {
return (
<li link={ item }>
{ item.title }
</li>
)
})}
</ul>
)}
</section>
</>
)
}
export default NavMenu
Set Up for Next to Use Apollo
This is actually really well documented by the Next.js team, which makes me feel like I'm not totally hacking the way this tool is supposed to work. You can find great examples of using Apollo in their repo.
Steps to get Apollo into a Next app:
Make a custom useApollo hook that sets up the connection to your data source (I put mine in /lib/apollo/apolloClient.js within Next's hierarchy but I'm sure it could go elsewhere).
import { useMemo } from 'react'
import { ApolloClient, InMemoryCache, SchemaLink, HttpLink } from '#apollo/client'
let apolloClient
// This is mostly from next.js official repo on how best to integrate Next and Apollo
function createIsomorphLink() {
// only if you need to do auth
if (typeof window === 'undefined') {
// return new SchemaLink({ schema })
return null
}
// This sets up the connection to your endpoint, will vary widely.
else {
return new HttpLink({
uri: `https://yourendpoint.io/graphql`
})
}
}
// Function that leverages ApolloClient setup, you could just use this and skip the above function if you aren't doing any authenticated routes
function createApolloClient() {
return new ApolloClient({
ssrMode: typeof window === 'undefined',
link: createIsomorphLink(),
cache: new InMemoryCache(),
})
}
export function initializeApollo(initialState = null) {
const _apolloClient = apolloClient ?? createApolloClient()
// If your page has Next.js data fetching methods that use Apollo Client, the initial state
// gets hydrated here
if (initialState) {
// Get existing cache, loaded during client side data fetching
const existingCache = _apolloClient.extract()
// Restore the cache using the data passed from getStaticProps/getServerSideProps
// combined with the existing cached data
_apolloClient.cache.restore({ ...existingCache, ...initialState })
}
// For SSG and SSR always create a new Apollo Client
if (typeof window === 'undefined') return _apolloClient
// Create the Apollo Client once in the client
if (!apolloClient) apolloClient = _apolloClient
return _apolloClient
}
// This is goal, now we have a custom hook we can use to set up Apollo across our app. Make sure to export this!
export function useApollo(initialState) {
const store = useMemo(() => initializeApollo(initialState), [initialState])
return store
}
Modify _app.js in the /pages/ directory of Next. This is basically the wrapper that goes around every page in Next. We're going to add the Apollo provider to this, and now we can globally access Apollo from any component.
import { ApolloProvider } from '#apollo/react-hooks'
import { useApollo } from '../lib/apollo/apolloClient'
/**
* <MyApp>
*
* This is an override of the default _app.js setup Next.js uses
*
* <ApolloProvider> gives components global access to GraphQL data fetched in the components (like menus)
*
*/
const MyApp = ({ Component, pageProps }) => {
// Instantiates Apollo client, reads Next.js props and initialized Apollo with them - this caches data into Apollo.
const apolloClient = useApollo(pageProps.initialApolloState)
return (
<ApolloProvider client={ apolloClient }>
<Component {...pageProps} />
</ApolloProvider>
)
}
export default MyApp
And now you can get dynamic data inside of your components using Apollo! So easy right ;) HA!
For global data fetching in NextJS, I use react-query and there is no need for a global state because it lets you to cache the data. Let's say you have a blog with categories and you want to put the category names in the navbar as a dropdown menu. In this case you can call the API to fetch the data with react-query from the navbar component and cache it. The navbar data will be available for all pages.

React SSR two pass rendering with renderToString and renderToNodeStream

I'm trying to do SSR with ReactDOMServer.renderToNodeStream(element) but just wanted to know if there would be any problem with using both ReactDOMServer.renderToString(element) and ReactDOMServer.renderToNodeStream(element) at each request?
What I have in my custom SSR setup is:
* React 16
* react-loadable
* styled-components v4
* react-helmet-async
* Redux
* Express JS
Previously with React, I could easily render a HTML document by first rendering the <head></head> tags that contains markup produced by react-helmet and then using ReactDOMServer.renderToString() to render my React elements.
However, by switching to ReactDOMServer.renderToNodeStream() I had to switch react-helmet for react-helmet-async, which supports renderToNodeStream() function. But then when I try to render the <head></head> tags with the markup by react-helmet-async it'll come back as undefined.
To get around this problem, I've had to use renderToString() first without actually writing that out to Express JS response. That way react-helmet-async can then see what meta tags to render and then proceed to use renderToNodeStream and stream that out to the response.
I've simplified my code as much as possible as I want to understand if this would have a negative impact (for performance, or if anyone can think of anything else)?
Before:
let html = ReactDOMServer.renderToString(stylesheet.collectStyles(
<Loadable.Capture report={moduleName => modules.push(moduleName)}>
<LocalStoreProvider store={store}>
<HelmetProvider context={helmetContext}>
<RouterContext {...renderProps} />
</HelmetProvider>
</LocalStoreProvider>
</Loadable.Capture>
));
const { helmet } = helmetContext;
response.write(
renderDocumentHead({
css: stylesheet.getStyleTags(),
title: helmet.title.toString(),
link: helmet.link.toString(),
meta: helmet.meta.toString()
})
);
response.write(html);
After:
let html = stylesheet.collectStyles(
<Loadable.Capture report={moduleName => modules.push(moduleName)}>
<LocalStoreProvider store={store}>
<HelmetProvider context={helmetContext}>
<RouterContext {...renderProps} />
</HelmetProvider>
</LocalStoreProvider>
</Loadable.Capture>
);
// do a first pass render so that react-helmet-async
// can see what meta tags to render
ReactDOMServer.renderToString(html);
const { helmet } = helmetContext;
response.write(
renderDocumentHead({
css: stylesheet.getStyleTags(),
title: helmet.title.toString(),
link: helmet.link.toString(),
meta: helmet.meta.toString()
})
);
const stream = stylesheet.interleaveWithNodeStream(
ReactDOMServer.renderToNodeStream(html)
);
// and then actually stream the react elements out
stream.pipe(response, { end: false });
stream.on('end', () => response.end('</body></html>'));
Unfortunately, the only way I could get react-helmet-async to work correctly, I have to do this two-pass render. My CSS styles, etc. resolves correctly and the client renders/hydrates correctly too. I've seen other examples where react-apollo was used and the getDataFromTree data rehydration method was used which allows react-helmet-async to see what was needed to render the head markup. But hopefully there are no issues with my two-pass rendering approach?

How to create a widget with react for integration in other react app?

I would like to know if it is possible to write a widget with React and distribute it as a CDN to integrate it into another application react?
The idea:
I have several applications in writing with react and I would like to have a banner common to all applications without having to rewrite it in each of them. The goal is to facilitate the updates of this banner.
My widget named toolBar works perfectly when I do npm start.
I have build my toolBar and add the script generate in the folder build into an other app named myAppTest.
My toolBar work and the other app to. However, the css and toolBar images are not loaded properly when launching myAppTest.
What is the best way pleasure ?
If by "integrating a widget with a react application" you mean a stand-alone react application (your widget) that needs to get data and pass data back to another application (react application in your case) then it's totally doable.
I even posted an article with a tutorial on how to do just that.
Basically the gist of it is that most of us learned that each react application has an entry point that run this line:
ReactDOM.render(<App/>, myContainer);
As in fire and forget approach.
But actually we can run ReactDOM.render as much as we want, it won't re-mount our application but instead will trigger the diffing for the tree.
If the React element was previously rendered into container, this will perform an update on it and only mutate the DOM as necessary to reflect the latest React element.
So what we can do is wrap it in a function and expose it globally so other code on the page can run it.
A pattern i use is to accept props with that function and it will pass it on to the <App/> via ReactDOM.render.
For example:
window.CoolWidget = {
mount: (props, container) => {
ReactDOM.render(<CoolWidget {...props} />, container);
},
unmount: (container) => {
ReactDOM.unmountComponentAtNode(container);
}
}
And the consumers of your widget can run it:
window.CoolWidget.mount({widgetProp: someValue, onLogin: function(){...}}, myContainer)
The best thing with this approach is that they can convert this code to a react component (or maybe even you can do it for them!):
class CoolWidgetWrapper extends PureComponent {
// create a ref so we can pass the element to mount and unmount
widgetRef = React.createRef();
componentDidMount() {
// initial render with props
window.CoolWidget.mount(this.props, this.widgetRef.current);
}
componentDidUpdate(prevProps) {
if(prevProps !== this.props){
window.CoolWidget.mount(this.props, this.widgetRef.current);
}
}
componentWillUnmount(){
window.CoolWidget.unmount(this.widgetRef.current);
}
render() {
return <div ref={this.widgetRef}></div>
}
}
And this is how they will use it:
<CoolWidgetWrapper someProp={someValue} onLogin={someFunc} />
Hope that helps

NodeJS HTML rendering strategy

We want to build a production-ready social network website (Facebook or Instagram style). We plan to use Node on the server side and are looking for the best technology for rendering view components on the server side.
SEO friendliness is a must, so Angular doesn’t seem like a good fit (unless anyone can advocate for Prerender.io as a solid choice).
We also wish to support AJAX so that most of the rendering would be done on the server, but updates would be done on the client.
Having looked at React, it seems like a good choice, but there’s one thing I’m worried about - the fact that out of the box, you would need to load all data at the route level as opposed to the component level (since renderToString is synchronous - see the discussion here
I don't really understand what would be a robust alternative for server side rendering if we're passing on React.
If I understnd correctly, the basic way (which does allow async loading from within sub components) would be something like:
// The main page route
app.get('/',function(){
var html = renderBase();
renderHeader(html)
.then(
renderFeed(html)
).then(
renderFooter(html)
);
})
renderBase = function(){
return = "<html><body>..."
}
renderHeader = function(){
someIoFunction(function(){
// build HTML based on data
})
}
Seemingly using a template engine such as Jade might relieve us of some of the burden, but I can't seem to find anything on this "React vs. Templating engine" so-called issue, so probably I'm not seeing it correctly.
Thanks.
Basically the solutions come down to using a convention where each component has a static function which returns the data it needs, and it calls the function of the same name on the components it needs. This is roughly how it looks:
class CommentList {
static getData = (props) => {
return {
comments: api.getComments({page: props.page})
};
}
}
class CommentApp {
static getData = (props) => {
return {
user: api.getUser(),
stuff: CommentList.getData({page: props.commentPage})
};
}
render(){
return <CommentList comments={this.props.stuff.comments} />
}
}
Or you can figure out all of the data requirements at the top of the tree, but while simpler to start out, it's more fragile. This is what you do with templates.

Categories