I have adopted a project that was built on this starter kit. This architecture employs App Shell and SSR. I am trying to add a simple search bar and this will mean passing the search keys from the search bar component to the post-list component so they can be filtered. I have found that this is nearly impossible with Context Providers and Consumers. I would like to use Context, but I do not know how to do it. It looks like this starter kit has this as a serious shortcoming and if it could be solved, it would make this kit more useful online.
If you look at the code below and in the link above, you can see that there is a header center and then thee are pages. I need a communication between the header and the pages. You can just use the code in the link to add the sibbling communication.
The use of Hydrate seems to preclude the simple application of a context provider. Hydrate adds components in a parallel way with no way to have the Context Provider above both of them. This pattern I am using here does not work. When I update the provider it does not cause a re-render of the context consumer.
If I have to use something other than Context, like say Redux, then I will accept that answer.
Here is the client entry point:
import { onPageLoad } from 'meteor/server-render';
import MeteorLoadable from 'meteor/nemms:meteor-react-loadable';
import { Switch, Route, Router, Redirect } from 'react-router-dom';
import { ApolloClient } from 'apollo-client';
import { ApolloProvider } from 'react-apollo';
import { InMemoryCache } from 'apollo-cache-inmemory';
import { ApolloLink } from 'apollo-link';
import { HttpLink } from 'apollo-link-http';
import { setContext } from 'apollo-link-context';
import { onError } from 'apollo-link-error';
import apolloLogger from 'apollo-link-logger';
import { onTokenChange, getLoginToken } from '/app/ui/apollo-client/auth';
import createBrowserHistory from 'history/createBrowserHistory';
import 'unfetch/polyfill';
// Initialise react-intl
import { primaryLocale, otherLocales } from '/app/intl';
// Need to preload of list of loadable components for MeteorLoadable
import '/app/ui/loadables';
// To get started, create an ApolloClient instance and point it at your GraphQL
// server. By default, this client will send queries to the '/graphql' endpoint
// on the same host.
// To avoid asynchronously accessing local storage for every GraphQL request,
// we cache the authorisation token, and update it using an onTokenChange callback
let authToken;
let authTokenInitialised = false;
onTokenChange(({ token }) => { authToken = token; authTokenInitialised = true; });
const withAuthToken = setContext(() => {
if (authTokenInitialised) {
return authToken ? { headers: { authorization: authToken } } : undefined;
}
return getLoginToken()
.then((token) => {
authToken = token;
authTokenInitialised = true;
return authToken ? { headers: { authorization: authToken } } : undefined;
});
});
const resetAuthToken = onError(({ networkError }) => {
if (networkError && networkError.statusCode === 401) {
// Remove cached token on 401 from the server
authToken = null;
authTokenInitialised = false;
}
});
const onErrorLink = onError(({ graphQLErrors, networkError }) => {
if (graphQLErrors) {
graphQLErrors.map(({ message, locations, path }) => console.log(
`[GraphQL error]: Message: ${message}, Location: ${locations}, Path: ${path}`,
));
}
if (networkError) console.log(`[Network error]: ${networkError}`);
});
const client = new ApolloClient({
link: ApolloLink.from([
apolloLogger,
withAuthToken,
resetAuthToken,
onErrorLink,
new HttpLink({
uri: '/graphql',
}),
]),
cache: new InMemoryCache().restore(window.__APOLLO_STATE__),
});
// Inject the data into the app shell.
// If the structure's changed, ssr.js also needs updating.
async function renderAsync() {
const [
React,
{ hydrate, render },
{ default: App },
{ default: HeaderTitle },
{ default: LanguagePicker },
{ default: Routes },
{ default: Menu },
] = await Promise.all([
import('react'),
import('react-dom'),
import('/app/ui/components/smart/app'),
import('/app/ui/components/smart/header/header-title'),
import('/app/ui/components/dumb/language-picker'),
import('/app/ui/routes'),
import('/app/ui/components/smart/menu'),
MeteorLoadable.preloadComponents(),
]);
// Given that we are implementing App Shell Architecture and, therefore,
// injecting (via reactDOM.render) the Header, Menu and Main components into
// different HTML elements, we need a way to share the router 'history' among
// all three mentioned components.
// As a default, for every invocation of 'BrowserRouter', there will be new
// 'history' instance created. Then, changes in the 'history' object in one
// component won't be available in the other components. To prevent this, we are
// relying on the 'Router' component instead of 'BrowserRouter' and defining our
// custom 'history' object by means of 'createBrowserHistory' function. Said
// 'history' object is then passed to every invocation of 'Router' and therefore
// the same 'history' object will be shared among all three mentioned components.
const history = createBrowserHistory();
// Inject react app components into App's Shell
const ClientApp = ({ component }) => (
<Router history={history}>
<ApolloProvider client={client}>
<Switch>
{/* Map our locales to separate routes */}
{ otherLocales.map(locale => (
<Route
key={locale}
path={`/${locale}/`}
render={props => <App component={component} {...props} locale={locale} section="app" />}
/>
))}
{ primaryLocale && (
<Route
key={primaryLocale}
path="/"
render={props => <App component={component} {...props} locale={primaryLocale} section="app" />}
/>
)}
{/* If no valid locale is given, we redirect to same route with the preferred locale prefixed */}
<Route render={({ location }) => <Redirect to={`/${window.__PREFERRED_LOCALE__ || otherLocales[0]}${location.pathname}`} />} />
</Switch>
</ApolloProvider>
</Router>
);
render(<ClientApp component={Menu} />, document.getElementById('menu'));
hydrate(<ClientApp component={HeaderTitle} />, document.getElementById('header-title'));
hydrate(<ClientApp component={LanguagePicker} />, document.getElementById('header-lang-picker'));
hydrate(<ClientApp component={Routes} />, document.getElementById('main'));
}
onPageLoad(() => {
const renderStart = Date.now();
const startupTime = renderStart - window.performance.timing.responseStart;
console.log(`Meteor.startup took: ${startupTime}ms`);
// Register service worker
import('/app/ui/register-sw').then(() => {});
renderAsync().then(() => {
const renderTime = Date.now() - renderStart;
console.log(`renderAsync took: ${renderTime}ms`);
console.log(`Total time: ${startupTime + renderTime}ms`);
});
});