React router with Gatsby - reactjs

I currently have an application built with CRA which uses React Router. I'm now trying to have a part of my application be rendered via Gatsby so that non technical users can control the content of this part of the application via a CMS.
What I have currently works in the dev server, but dies when I try and build.
This is my gatsby-node
exports.onCreatePage = async ({ actions, page }) => {
const { createPage } = actions;
return new Promise((resolve, reject) => {
if (page.path === "/") {
createPage({
...page,
matchPath: "/*"
});
}
resolve();
});
};
I have a static page that is being rendered at pages/study/index.js. It's quite large, so assume it looks like this
export default function Home({ data }) {
if (!data) return null;
const { mdx } = data;
const { frontmatter: cmsData } = mdx;
return (
<StaticRouter location="/study">
<Provider>
<HomeTemplateWithRouter cmsData={cmsData} data={data} />
</Provider>
</StaticRouter>
);
}
I also have an index.js which looks like this
import React from "react";
import { Provider } from "components/general/Provider";
import { BrowserRouter } from "react-router-dom";
import AppContainer from "containers/AppContainer";
import { StaticRouter } from "react-router";
const Application = () => {
return (
<BrowserRouter>
<Provider>
<AppContainer />
</Provider>
</BrowserRouter>
);
};
export default Application;
Everything works when I use the development server. However when I build I get this error
Building static HTML failed for path "/"
See our docs page for more info on this error: https://gatsby.dev/debug-html
7 |
8 | if (isProduction) {
> 9 | throw new Error(prefix);
| ^
10 | } else {
11 | throw new Error(prefix + ": " + (message || ''));
12 | }
WebpackError: Invariant failed
- tiny-invariant.esm.js:9 invariant
node_modules/tiny-invariant/dist/tiny-invariant.esm.js:9:1
- history.js:250 createBrowserHistory
node_modules/history/esm/history.js:250:115
- BrowserRouter.js:29 new BrowserRouter
node_modules/react-router-dom/es/BrowserRouter.js:29:176
Why is this happening only when I build a production version of the website?
Why is this happening given that I've defined a client side route and the code here should only be rendered client side?
What is the way around this without changing my dependency on react-router?
Thanks!

I have the same problem. I search on the Internet and find only one solution is to avoid react-router-dom.

Related

ProviderFunction not exporting my new functions

My Problem :
I expect my FirebaseProvider function to provide an object containing all functions, through the app. The problem is that all functions are well provided through my files, except my last new function : fetchTest.
Explainations :
If I click the TestPage.js button I get Uncaught TypeError: fetchTest is not a function.
I saw many posts on stackoverflow about this type of error, but none did help me. -> I think the original problem is the index.js is not called. The console.log("firebaseprovider") (in index.js) does not appear in console, yet the other files of the project in web-app/src/views/ have the same imports and exports than TestPage.
Since App.js code worked fine on all the other files, I don't know how console.log("firebaseprovider") is never displayed in the navigator console. (edit: no matter which page I go, this console.log never appears)
<FirebaseProvider> seems to not provide TestPage.js.
Do you have an idea ?
What I've tried :
placing a console.log in TestPage.js : it shows every function written in index.js but not fetchTest. It seems to not be properly exported through api object.
in TestPage.js trying console.log("api.fetchTest") : console displays undefined.
add a second testing function in index.js, whithout parameters, which just does console.log("test")
compare imports/exports and api declarations with other files in web-app/src/views/
create a handleSubmit() function in TestPage.js to not put the functions directly in return
delete node_modules and then yarn install
yarn workspace web-app build and then relaunch yarn workspace web-app start
(This is a Yarn Workspaces project containing a common/ and a web-app/ folders)
common/src/index.js:
import React, { createContext } from 'react';
import {FirebaseConfig} from 'config';
const FirebaseContext = createContext(null);
const FirebaseProvider = ({ children }) => {
console.log("firebaseprovider"); // is not displayed in the console
let firebase = { app: null, database: null, auth: null, storage:null }
if (!app.apps.length) { // I tried to comment out this line (and the '}') -> no difference
app.initializeApp(FirebaseConfig); // no difference when commented out
firebase = {
app: app,
database: app.database(),
auth: app.auth(),
storage: app.storage(),
// [ ... ] other lines of similar code
api : { // here are functions to import
fetchUser: () => (dispatch) => fetchUser()(dispatch)(firebase),
addProfile: (details) => (dispatch) => addProfile(userDetails)(dispatch)(firebase),
// [ ... ] other functions, properly exported and working in other files
// My function :
fetchTest: (testData) => (dispatch) => fetchTest(testData)(dispatch)(firebase),
}
}
}
return (
<FirebaseContext.Provider value={firebase}>
{children}
</FirebaseContext.Provider>
)
}
export { FirebaseContext, FirebaseProvider, store }
web-app/src/views/TestPage.js:
import React, { useContext } from "react";
import { useDispatch } from "react-redux";
import { FirebaseContext } from "common";
const TestPage.js = () => {
const { api } = useContext(FirebaseContext);
console.log(api); // Displays all functions in api object, but not fetchTest
const { fetchTest } = api;
const dispatch = useDispatch();
const testData = { validation: "pending" };
return <button onClick={ () => {
dispatch(fetchTest(testData)); // Tried with/without dispatch
alert("done");
}}>Test button</button>
}
export default TestPage;
web-app/src/App.js:
import React from 'react';
import { Router, Route, Switch } from 'react-router-dom';
// ... import all pages
import { Provider } from 'react-redux';
import TestPage from './views/CreateSiteNeed'; // written same way for the other pages
import { store, FirebaseProvider } from 'common';
function App() {
return (
<Provider store={store}>
<FirebaseProvider>
<AuthLoading>
<Router history={hist}>
<Switch>
<ProtectedRoute exact component={MyProfile} path="/profile" />
<!-- [ ... ] more <ProtectedRoute /> lines, form imported Pages line 3. -->
<ProtectedRoute exact component={TestPage} path="/testpage" />
</Switch>
</Router>
</AuthLoading>
</FirebaseProvider>
</Provider>
);
}
export default App;
I hope some people will find this post helpful, thanks
Here was the problem :
Firstly :
I'm using Redux, so fetchTest() has its testActions.js and testReducer.js files, which are functionnal. But I did forget to update my store.js :
// [ ... ] import all reducers
import { testReducer as testData } from '../reducers/testReducer'; // was'nt imported
const reducers = combineReducers({
auth,
usersdata,
// [ ... ] other imported reducers
testData // My test reducer
}
// The rest is a classic store.js code
Secondly :
As I'm using Yarn Workspaces, I had to compile the code in common/dist/index.js to make it accessible through the whole entire code (even for local testing).
Here is the command to compile the code (-> to include all redux edits made above) and make it accessible to web-app workspace :
yarn workspace common build && yarn workspace web-app add common#1.0.0 --force
Explanations on the second part of the command (yarn workspace web-app add common#1.0.0 --force) :
The web-app/package.json file contains { "dependencies": { ... "common":"1.0.0" ... }}

Next.JS Dynamic route static export project opened in new tab or reload the page leads to 404

I have a VPS with Apache + Cpanel.
I can't configure Nginx over it, so the only way, as far as I know, is to 'static export' first then deploy it.
Turns out I can't access the product page by link pasted on url bar directly (not by click a link text).
The link is look like this : www.example.com/products/4 or www.example.com/products/213
My first suggestion is because I 'static export' the project.
I use next-routes with <Link />
My code
import React, { Component } from 'react';
import { withRouter } from 'next/router';
import { connect } from 'react-redux';
import fetch from 'isomorphic-fetch';
import Navbar from '../components/Navbar';
import Footer from '../components/Footer';
import CheckoutBody from '../components/CheckoutBody';
class Product extends Component {
static async getInitialProps({ query }) {
let { id } = { ...query };
if (id === undefined) id = 14;
const res = await fetch(`http://www.example.com/api/product?id=${id}`);
const data = await res.json();
return { campaignDetail: data };
}
render() {
let { lang } = this.props;
return (
<React.Fragment>
<Navbar />
<CheckoutBody
key={this.props.productDetail.id}
productDetail={this.props.productDetail}
lang={lang}
/>
<Footer />
</React.Fragment>
);
}
}
export default Product ;
Same question but different problem: https://github.com/zeit/next.js/issues/9893
I have tried this to .htaccess. It is not working. I am very newbie to regex and htaccess.
RewriteCond %{REQUEST_FILENAME}.html -f
RewriteRule !.*\.html$ %{REQUEST_FILENAME}.html [L]
What should I do?
Is it what it's called dynamic routes?
The issue might be related to using next export rather than rewrite rule configuration. What we found is nextjs router pathname is not populated with the expected route on first hit if you use next export. Until this issue is fixed within nextjs, you can use a provider in _app.js that wraps your components and adjusts the route or put this before the return statement inside you _app.js default function:
import { useRouter } from 'next/router'
const { asPath, push, pathname } = useRouter()
if (asPath.split('?')[0] != pathname.split('?')[0] && !pathname.includes('[')) {
// Work around for next export breaking SPA routing on first hit
console.log('Browser route ' + asPath + ' did not match nextjs router route ' + pathname)
push(asPath)
return <div>Loading...</div>
}

how to Redirect to another page in next.js based on css media query?

im brand new to Next.js and i have the following situation. i want to redirect the user to the route /messages if he type route /messages/123 based on css media query so if he is mobile we will not redirect and if he in browser then redirect .
i have tried the following code
import React, { useLayoutEffect } from 'react';
import { useRouter, push } from 'next/router';
import useMediaQuery from '#material-ui/core/useMediaQuery';
import Layout from '../components/Customer/Layout/Layout';
import Chat from '../components/Customer/Chat/Chat';
const Messages = () => {
const { pathname, push } = useRouter();
const matches = useMediaQuery('(min-width:1024px)');
useLayoutEffect(() => {
console.log('I am about to render!');
if (matches && pathname === '/messages') {
console.log('match!');
push('/');
}
}, [matches, pathname, push]);
return (
<Layout currentURL={pathname}>
<Chat />
</Layout>
);
};
export default Messages;
the problem is the component render twice before redirect
But You should probably be using useEffect since you are not trying to do any DOM manipulations or calculations.
useLayoutEffect: If you need to mutate the DOM and/or DO need to perform measurements
useEffect: If you don't need to interact with the DOM at all or your DOM changes are unobservable (seriously, most of the time you should use this).
You should see immediate action.
Edit:
You can use Next JS getInitialProps to check the request headers and determine if the request if from mobile or desktop then redirect from there.
getInitialProps({ res, req }) {
if (res) {
// Test req.headers['user-agent'] to see if its mobile or not then redirect accordingly
res.writeHead(302, {
Location: '/message'
})
res.end()
}
return {}
}

Having difficulty integration Apollo Client with GatsbyJS

I'm having some problem integrating Apollo Client with GatsbyJS. When I run gatsby develop everything seems to be working fine and I can use Apollo Client without any problem. However, I keep getting errors when I run gatsby build, specifically I keep getting the following error: WebpackError: Invariant Violation: Could not find "client" in the context of ApolloConsumer. Wrap the root component in an <ApolloProvider>
I can't seem to figure where this problem is coming from. Here are all my files that concern Apollo Client.
This is the client.js file:
// client.js
import {ApolloClient} from 'apollo-boost';
import {InMemoryCache} from 'apollo-cache-inmemory';
import { split } from 'apollo-link';
import { HttpLink } from 'apollo-link-http';
import { WebSocketLink } from 'apollo-link-ws';
import { getMainDefinition } from 'apollo-utilities';
import fetch from 'isomorphic-fetch';
const httpLink = new HttpLink({
uri: 'https://api.emaildrop.io/graphql'
});
// Create a WebSocket link:
const wsLink = new WebSocketLink({
uri: `wss://api.emaildrop.io/subscriptions`,
options: {
reconnect: true
}
});
const link = split(
// split based on operation type
({ query }) => {
const { kind, operation } = getMainDefinition(query);
return kind === 'OperationDefinition' && operation === 'subscription';
},
wsLink,
httpLink,
);
const cache = new InMemoryCache();
export const client = new ApolloClient({
link,
cache,
fetch
});
This is the wrap-root-element.js file
// wrap-root-element.js
import React from 'react';
import {ApolloProvider} from 'react-apollo';
import {client} from './client';
export const wrapRootElement = ({ element }) => (
<ApolloProvider client={client}>
{element}
</ApolloProvider>
)
This is the gatsby-browser.js file
export {wrapRootElement} from './src/apollo/wrap-root-element'
Finally, this is the gatsby-srr.js file
// gatsby-srr.js
export {wrapRootElement} from './src/apollo/wrap-root-element'
Like, I've said previously I've successfully integrated Apollo Client with GatsbyJS however, I can't seem to build it for production. This the error I get when I build it via gatsby build:
error Building static HTML for pages failed
See our docs page on debugging HTML builds for help https://gatsby.app/debug-html
38 | var args = [a, b, c, d, e, f];
39 | var argIndex = 0;
> 40 | error = new Error(
| ^
41 | format.replace(/%s/g, function() { return args[argIndex++]; })
42 | );
43 | error.name = 'Invariant Violation';
WebpackError: Invariant Violation: Could not find "client" in the context of ApolloConsumer. Wrap the root component in an <ApolloProvider>
- invariant.js:40 invariant
[lib]/[invariant]/invariant.js:40:1
- ApolloConsumer.js:4 ApolloConsumer
[lib]/[react-apollo]/ApolloConsumer.js:4:1
- bootstrap:25 a.render
lib/webpack/bootstrap:25:1
- bootstrap:24 a.read
lib/webpack/bootstrap:24:1
- bootstrap:36 renderToString
lib/webpack/bootstrap:36:1
- static-entry.js:190 Module.default
lib/.cache/static-entry.js:190:18
- bootstrap:24 Promise
lib/webpack/bootstrap:24:1
- gatsby-browser-entry.js:3 Promise._resolveFromExecutor
lib/.cache/gatsby-browser-entry.js:3:1
- bootstrap:68 new Promise
lib/webpack/bootstrap:68:1
- bootstrap:5 tryCatcher
lib/webpack/bootstrap:5:1
- bootstrap:50 MappingPromiseArray._promiseFulfilled
lib/webpack/bootstrap:50:1
- api-runner-ssr.js:8 MappingPromiseArray.PromiseArray._iterate
lib/.cache/api-runner-ssr.js:8:1
You'll want to implement replaceRenderer in gatsby-ssr.js.
See this thread - https://github.com/gatsbyjs/gatsby/issues/3650

How to get react-hot-loader working with dynamic imports?

I saw this answer which shows how to get react-hot-loader working with import() syntax, but in my case I don't know the filename until runtime.
Here's what I've got:
export default function(component, props, mountPoint) {
function render() {
import(`./containers/${component}`).then(({default: Component}) => {
ReactDOM.render(
<AppContainer>
<ErrorBoundary>
<Component {...props}/>
</ErrorBoundary>
</AppContainer>, document.getElementById(mountPoint || 'react-root'));
});
}
render();
if(module.hot) {
module.hot.accept('./containers', () => {
render();
});
}
}
The first load works fine, it's just the module.hot block that doesn't work. Chrome tells me:
Uncaught (in promise) Error: Cannot find module "./containers"
And my terminal tells me the same thing:
WARNING in ./node_modules/babel-loader/lib?{"cacheDirectory":"/usr/local/myproject/cache/babel","forceEnv":"development"}!./assets/scripts/app/react_loader.js
Module not found: Error: Can't resolve './containers' in '/usr/local/myproject/assets/scripts/app'
If I try to accept ./containers/${component} then I get a runtime error instead:
Ignored an update to unaccepted module ./assets/scripts/lib/components/bpm/MyClientProcessMenu.jsx -> ./assets/scripts/lib/components/bpm/MyClientProcessMenuLoader.jsx -> ./assets/scripts/app/containers/MyClientProcessMenuContainer.jsx -> ./assets/scripts/app/containers lazy recursive ^./.$ -> ./node_modules/babel-loader/lib/index.js?{"cacheDirectory":"/usr/local/myproject/cache/babel","forceEnv":"development"}!./assets/scripts/app/react_loader.js -> ./node_modules/bundle-loader/index.js!./assets/scripts/app/react_loader.js -> ./assets/scripts/app recursive ./node_modules/bundle-loader/index.js!./ ^./.$ -> ./assets/scripts/lib/webpack.js -> ./assets/main.js -> 0
And no update occurs.
How can I "accept" a dynamic component?
I do not think that this is currently supported without duplicating a code. As a workaround, you can create two files one would be used for production with dynamic import and the second one would be without dynamic import for development.
The file with dynamic import has to be included only in production. That's the reason for moving the environment logic into the different file (index.js)
index.js
// Neded because HMR doesn't work with dynamic import for languages
let app;
if (process.env.NODE_ENV === 'development') {
app = require('./development').default;
} else {
app = require('./production').default;
}
app(component);
client.js
export default function(Component) {
function render() {
ReactDOM.render(
<AppContainer>
<Component />
</AppContainer>, document.getElementById('react-root'));
}
render();
if(module.hot) {
module.hot.accept('./containers', () => {
render();
});
}
}
production.js
import client from './client';
export default function (component) {
import(`./containers/${component}`).then(Component => {
client(Component)
});
}
development.js
import client from './client';
export default function (component) {
const Component = require(`./containers/${component}`);
client(Component);
}

Resources