Apollo + React Router 4 SSR issue - javascript

Not sure if this is an issue with React Router v4, the React Apollo client or my implementation.
But with <ApolloProvider> as the top-level HOC, i.e:
const ComponentsWithData = await getDataFromTree(
<ApolloProvider client={apolloClient}>
<StaticRouter location={ctx.request.url} context={route}>
<App />
</StaticRouter>,
</ApolloProvider>,
);
const html = ReactDOMServer.renderToString(ComponentsWithData);
... I get:
Warning: Failed prop type: Invalid prop children of type array supplied to ApolloProvider, expected a single ReactElement.
in ApolloProvider
Error React.Children.only expected to receive a single React element child.
And flipped around, with React Router's <StaticRouter> as the top, i.e.:
const ComponentsWithData = await getDataFromTree(
<ApolloProvider client={apolloClient}>
<StaticRouter location={ctx.request.url} context={route}>
<App />
</StaticRouter>,
</ApolloProvider>,
);
... I then get:
A <Router> may have only one child element
Rendering works fine in the browser (with React Router's <BrowserRouter>), but fails on the server.
It also works well in React Router v3 due to doing all of the route matching outside of the React hierarchy, and not declaratively inside of it.

This was actually a user error. I expected getDataFromTree() to return a Promise that resolved to the original component chain (with the correctly injected GraphQL data props), but it actually just waits for the data to be ready.
The correct format is:
const Components = (
<StaticRouter location={ctx.request.url} context={route}>
<ApolloProvider client={client}>
<App />
</ApolloProvider>
</StaticRouter>
);
await getDataFromTree(Components);
const html = ReactDOMServer.renderToString(Components);

Related

How does React.suspense "handle" i18next backend loading?

I'm working with some pretty standard create-react-app boilerplate, which uses lazy loading and react-i18next as a translation library. This library uses i18next-http-backend to fetch the translation files from a remote API.
What i'm trying to understand, is how exactly React.suspense is able to "recognize" this asynchronous call, and show the fallback UI until it's done.
Index.ts file:
import "./i18n";//Notice this
const container = document.getElementById("root");
ReactDOM.render(
<StylesProvider jss={jss}>
<ThemeProvider theme={theme}>
<React.StrictMode>
<BrowserRouter>
<Router />
</BrowserRouter>
</React.StrictMode>
</ThemeProvider>
</StylesProvider>,
container
);
i18n file:
i18n
.use(Backend)
.init({
backend:{
loadPath: 'https://someRemoteApi/dictionary',
})
Router:
const Home = lazy(() => import("../../modules/home/Home"));
const Router: React.FC = (props) => {
return (
<>
<ErrorBoundary>
<Suspense fallback={<div className={styles.loader}><Loader /></div>}>
<Switch>
<ProtectedRoute exact component={Home} path="/"/>
...more routes
</Suspense>
</ErrorBoundary>
</>
);
};
With this setup, to my amazement, the fallback is rendered on the screen, until this backend plugin finishes its job. I'm trying to understand the mechanics of it, and whether this can be leveraged for other async operations.
the React docs clearly state:
React.Suspense lets you specify the loading indicator in case some
components in the tree below it are not yet ready to render. Today,
lazy loading components is the only use case supported by
<React.Suspense>:
Any clarification will be greatly appreciated.
The idea of Suspense is when a component throws a Promise (or anything that is called during the component’s render), React looks for the closest Suspense in order to display the fallback UI.
In your case, your components are using the useTranslate hook. When namespaces are not yet loaded, it throws a Promise and loads the namespaces. During the render phase, React catches the thrown Promise and looks for the closest Suspense component up the tree in order to display fallback UI.
This is a snippet from the hook useTranslation:
// not yet loaded namespaces -> load them -> and trigger suspense
throw new Promise((resolve) => {
loadNamespaces(i18n, namespaces, () => {
resolve();
});
});
You can check how the hook useTranslation works from here

React redux - dispatch in App.js outside provider?

Let me explain my problem. I have an app reacting on an RBAC model using Redux. To do this, I need to call my dispatch() in the useEffect when loading my app to check if the user is authenticated or not. If yes, I would need to dispatch its information contained in the jwt token.
So I did something like this (in App.jsx) :
const App = () => {
const [isAuthenticated, setIsAuthenticated] = useState(false);
const dispatch = useDispatch()
const authentication = () =>
isAuthenticated ? (
<Redirect to="/app" />
) : (
<PublicRoutes />
);
useEffect(() => {
setIsAuthenticated(Auth.isAuth())
if(isAuthenticated){
store.dispatch(setConnectedUser({name:"Jude"}))
}
}, [isAuthenticated])
const store = createStore(rootReducer, composeWithDevTools(
applyMiddleware(thunk),
));
return (
<Provider store={store}>
<HashRouter>
<Switch>
<Route path="/app" component={PrivateRoutes} />
<Route path="" render={authentication} />
</Switch>
</HashRouter>
</Provider>
);
}
export default App;
ReactDOM.render(<App />, document.getElementById("root"))
Auth.isAuth() just fetches the token, checks if the token is still valid, and if so it returns true.
But here's the thing, by doing it this way I'm making an error :
Uncaught Error: could not find react-redux context value; please ensure the component is wrapped in a
I understand that the mistake comes from the fact that I want to dispatch() outside the provider, but is it possible to do so? or would I do it wrong? This is my first project with Redux, maybe I didn't fully understand how it works?
Knowing that the dispatch() works very well, it was called at login time before, but I have an error on my header that will retrieve the info from the reducer, it tries to display the store information before it is there, that's why I would like to do the control at application loading, and not only at login.
With your code you aren't able to use redux in this component, just in all his children.
You can just set the provider outside, maybe in the index.js (or wherever you do the ReactDOM.render() or any superior call).
Or if you wish, you can create any new element that will be used in App.js, like 'router.js' or similar where you can check your logic and redirect for where you want.

React Router V4 protected private route with Redux-persist and React-snapshot

I'm implementing private route like so using React Router Route Component:
function PrivateRoute({component: Component, authed, emailVerified, ...rest}) {
return (
<Route
{...rest}
render={props =>
authed === true
? <Component {...props} />
: <Redirect to={{pathname: '/', state: {from: props.location}}} />}/>
)
}
Expected Behavior:
authed is persisted through page refresh through using redux-persist
So on page refresh or reload, if authed prop is true then router should render <Component /> without ever going to path "/"
Actual Behavior which is the Problem:
With authed === true (persisted)
reloading the page or refreshing it leads to the following actions taking place(checked redux devtools)
the action:
"##router/LOCATION_CHANGE" runs and takes it to the correct secure route but then "##router/LOCATION_CHANGE" runs again and it redirects to "/" for a moment and finally
"##router/LOCATION_CHANGE" runs again and directs route back to the secure path, even though authed === true through all this in the redux devtools
Then: My guess was that this error has something to with my main App Component rendering before redux-persist has time to re-hydrate the Redux store.
So I tried doing the following:
I tried delaying my main App component render until my store is re-hydrated using redux-persist like so:
render() {
const {authed, authedId, location, rehydrationComplete} = this.props
return (
<div>
{ rehydrationComplete
? <MainContainer>
<Switch key={location.key} location={location}>
<Route exact={true} path='/' component={HomeContainer} />
<Route render={() => <h2> Oops. Page not found. </h2>} />
</Switch>
</MainContainer>
: <div>...Loading </div> }
</div>
)
}
This effectively fixes the issue above of the path changing when "##router/LOCATION_CHANGE" action runs(only Changes the path keys), However this leads to another Issue with React-snapshot Now: all the static generated html files from react-snapshot Now contain only ...Loading. I tried to set snapshotDelay of 8200 in the react-snapshot options but that didnt solve the issue.
Then:
I tried the following to delay React-snapshot call so that it renders html after the store has been rehydrated:
import {render as snapshotRender} from 'react-snapshot'
import {ConnectedRouter} from 'react-router-redux'
async function init() {
const store = await configureStore()
snapshotRender(
<Provider store={store}>
<ConnectedRouter history={history}>
<App />
</ConnectedRouter>
</Provider>,
document.getElementById('root')
)
registerServiceWorker()
}
init()
But now i get the error: that 'render' from react-snapshot was never called. Did you replace the call to ReactDOM.render()?
I know this is a loaded question, but I want to effectively use these 3 libs(React-Router V4, Redux-persist, React-snapshot) together to serve protected routes without the mentioned errors.
I have something similar to you. Here I use React-Router V4 and a persist-like library.
Your router/routes doesn't need to be aware of the persist. They should rely on your redux's store. The persist should rehydrate your store with all the data.
I didn't see where you are using the PrivateRoute component in your example. Where is it?

Passing props from react router to children on the server

I'm building an Isomorphic app using React, react-router v3 and material-ui. One of the requirements of material-ui in server-side rendering is to pass to the theme the client's user agent, so MUI will be able to prefix their inline styles accordingly.
Originally the root component of the app was the router, i.e on the server side:
<RouterContext {...renderProps}/>
And on the client:
<Router children={routes} history={browserHistory} />
Now, since I didn't know how to pass the user agent to the RouterContext on the server, I came up with an (ugly?) solution: I've created a useless component named Root, to whom I passed the user agent, and Root had the router as his children, i.e. on the server side:
<Root userAgent={userAgent}>
<RouterContext {...renderProps}/>
</Root>
and on the client:
<Root>
<Router children={routes} history={browserHistory} />
</Root>
Now, everything works well but I don't really like creating that useless element if I don't have to, so my question is - is there a better way?
Can I somehow pass the user agent to RouterContext on the server, which will in turn pass it to the Index Route which will create the theme?
Here's the code of Root if someone's interested:
class Root extends React.Component {
constructor(props){
super();
this.muiTheme = getMuiTheme(customTheme, { userAgent: props.userAgent });
}
render () {
return (
<MuiThemeProvider muiTheme={this.muiTheme}>
{this.props.children}
</MuiThemeProvider>
);
}
}
You can use createElement on RouterContext to achieve this.
<RouterContext
{...renderProps}
createElement={(Component, props) => <Component {...props} userAgent={data.userAgent} />}
/>
This will make userAgent available in the props of all route components.

Multiple child routes in React-router

I am trying to implement a router where a Route has two parallel/independent sub-routes and I wonder if this is possible with react-router. The image below illustrates what I want to achieve. It represents an event-editor with two components: (1) a list of events (2) an editor for one event. I think their states should both be part of the URL somehow.
One extra constraint: [child1] should be aware of the state of [child2], since it needs to show the item that is currently edited in a different color or so.
If this is not possible with react-router, how would you implement this? Thanks!
Look this. By doing this you can create a parallel control of routes, without harming root.
/**at index.html*/
< div id="root"></div>
< div id="helper"></div>
/**at index.js*/
ReactDOM.render(
(
<BrowserRouter>
<App/>
</BrowserRouter>
),
document.getElementById('root')
);
ReactDOM.render(
(
<BrowserRouter>
<Helper />
</BrowserRouter>
),
document.getElementById('helper')
);
/**at Component Helper*/
render() {
const location = { pathname: '/2' }
return (<Switch location={location}>
<Route exact path='/2' component={Welcome} />
</Switch>);
}
const Welcome = () => <h3>welcome helper</h3>
//I'm using router V4 to switch of the routers.

Categories

Resources