Internationalization gone wrong in gatsby - javascript

ERROR #95313 Building static HTML failed for path "/404/"
I recently implemented localization in my gatsby project using the react-intl package. I was able to pull that off by mimicking the implementation of the package in the gatsby-starter-default-contentful-i18n repo. Everything works fine when I run gatsby-develop but sadly not the same after running build.
The react-intl packages require some route configurations to get it up and running, which made me make use of the location props in my layout component which is exposed to the route paths which I also implemented using the earlier linked repo as a guide. However, I always meet this error.
const url = location.pathname;
| ^
36 | const { langs, defaultLangKey } = data.site.siteMetadata.languages;
37 | const langKey = getCurrentLangKey(langs, defaultLangKey, url);
38 | const homeLink = `/${langKey}/`;
-WebpackError: TypeError: Cannot read property 'pathname' of undefined.
I keep getting pointed towards the url variable. I made sure to cross-check all the location props that were passed down from layout component parents to make sure none of them were undefined, but still no dice. I really don't know where to start debugging this problem.
Below is my index.js file.
import React from 'react';
import { graphql, navigate, withPrefix } from 'gatsby';
import { getUserLangKey } from 'ptz-i18n';
class RedirectIndex extends React.PureComponent {
constructor(args) {
super(args);
// Skip build, Browsers only
if (typeof window !== 'undefined') {
const { langs, defaultLangKey } = args.data.site.siteMetadata.languages;
const langKey = getUserLangKey(langs, defaultLangKey);
const homeUrl = withPrefix(`/${langKey}/`);
navigate(homeUrl);
}
}
render() {
// It's recommended to add your SEO solution in here for bots
// eg. https://github.com/ahimsayogajp/ahimsayoga-gatsby/blob/master/src/pages/index.js#L22
return (<div />);
}
}
export default RedirectIndex;
export const pageQuery = graphql`
query IndexQuery {
site{
siteMetadata{
languages {
defaultLangKey
langs
}
}
}
}
`;
Thanks in advance for all the help!

Related

Unable to do Conditional import of file in React app

I am having issues importing a JSON file depending on a process.env.WORLD value in my React Typescript app. This is done in a .tsx file defining a React Context, and no React components are used here.
The JSON file can be loaded without problems when we ignore the process.env variable.
import data from '../main.json';
TS file loading the JSON file: src/contexts/world.tsx
JSON file: src/main.json
In my first attempt, I tried
let file;
if (process.env.WORLD == 'main') {
file = '../main.json';
} else {
file = '../test.json';
}
import data from file;
but got the error
Import in body of module; reorder to top import/first
Tried using dynamic import
const data = await import(
process.env.WORLD == 'main'
? '../main.json'
: '../test.json'
);
but my setup does not allow a top-level await
Module parse failed: Cannot use keyword 'await' outside an async function
Tried using require instead of import
let file;
if (process.env.WORLD === 'main') {
file = '../main.json';
} else {
file = '../test.json';
}
const data = require(file);
but it almost works except that the file cannot be found
contexts sync:2 Uncaught Error: Cannot find module '../main.json'
Strangely, there is no problem using the same file path with import as shown in the first example in this question.
Typescript target is ES2018, and React 18 is used.
Why is the file not found when using require, and how can we solve this problem?
In a comment, you added information which implies that the external JSON data is being used as part of the value of a ContextProvider.
By fetching (or importing) the JSON data dynamically just before render, you can pass it via props to your context provider on the initial render, then memoize it in a ref. Below is a working example in a Stack Overflow snippet which uses object URLs in place of your local file specifiers.
Note that there are many considerations involved in how to optimize external data loading, and your question does not provide much detail regarding your scenario, but the approach that works best for your app will depend on those details. In any case, what you've described in the question would be better handled by re-writing the contents of a single JSON file on disk (based on the environment variable) before building or running your app each time. This would need to be handled outside the React app by an external process (like an npm script, for example), and would allow you to avoid the scenario entirely, while also optimizing the build result with static imports.
TypeScript Playground
<div id="root"></div><script src="https://unpkg.com/react#18.2.0/umd/react.development.js"></script><script src="https://unpkg.com/react-dom#18.2.0/umd/react-dom.development.js"></script><script src="https://unpkg.com/#babel/standalone#7.18.8/babel.min.js"></script><script>Babel.registerPreset('tsx', {presets: [[Babel.availablePresets['typescript'], {allExtensions: true, isTSX: true}]]});</script>
<script type="text/babel" data-type="module" data-presets="tsx,react">
// import ReactDOM from 'react-dom/client';
// import {
// default as React,
// createContext,
// StrictMode,
// useContext,
// useRef,
// type ReactNode,
// type ReactElement,
// } from 'react';
// This Stack Overflow snippet demo uses UMD modules
// instead of the commented import statments above
const {
createContext,
StrictMode,
useContext,
useRef,
} = React;
type JsonData = {
items: string[];
listTitle: string;
};
// An initial value is not required as long as this
// is not used outside the context provider:
const jsonContext = createContext(undefined as unknown as JsonData);
function JsonProvider ({children, data}: {
children: ReactNode;
data: JsonData;
}): ReactElement {
const ref = useRef(data);
return (
<jsonContext.Provider value={ref.current}>
{children}
</jsonContext.Provider>
);
}
function App (): ReactElement {
const {items, listTitle} = useContext(jsonContext);
return (
<div>
<h1>{listTitle}</h1>
<ul>
{items.map((item, index) => (<li key={index}>{item}</li>))}
</ul>
</div>
);
}
type JsonPrimitive = boolean | null | number | string;
type JsonSerializable = JsonPrimitive | JsonSerializable[] | { [key: string]: JsonSerializable };
function createJsonObjectUrl (serializable: JsonSerializable): string {
const json = JSON.stringify(serializable);
const blob = new Blob([json], {type: 'application/json'});
return URL.createObjectURL(blob);
}
async function inititalRender () {
// The Stack Overflow code snippet sandbox doesn't have Node's `process`,
// so we'll simulate it here:
const process = {
env: {
// Simulate that it could be either "main" or "test"
REACT_APP_JSON_FILE: Math.random() < 0.5 ? 'main' : 'test',
} as Partial<Record<string, string>>,
};
// In your app, this would simply be "../main.json" or "../test.json",
// but the Stack Overflow code snippet sandbox doesn't have access to
// your local files, so we'll simulate the specifier with an object URL
// (which can also be fetched/imported):
const specifier = process.env.REACT_APP_JSON_FILE === 'main'
? createJsonObjectUrl({
items: ['a', 'b', 'c'],
listTitle: 'main',
})
: createJsonObjectUrl({
items: ['one', 'two', 'three'],
listTitle: 'test',
});
// Use fetch to get the data (we're in an async function, so that's OK!)
const data = await (await fetch(specifier)).json();
// Or (in the future) dynamic import with an assertion:
// const {default: data} = await import(specifier, {assert: {type: 'json'}});
const reactRoot = ReactDOM.createRoot(document.getElementById('root')!);
reactRoot.render(
<StrictMode>
<JsonProvider {...{data}}>
<App />
</JsonProvider>
</StrictMode>
);
}
inititalRender();
</script>
Could importing both files then choose the right file be a fit for you ?
import main from '../main.json';
import test from '../test.json';
let file;
if (process.env.WORLD == 'main') {
file = main;
} else {
file = test
}
or use react.lazy :
const Questions = React.lazy(() => {
if (process.env.WORLD == 'main') {
import('../main.json'))
} else {
import('../test.json'))
}
}

React context isn't found within it's own file changing from JS to TS

I had my file of a context and context provider which worked just fine. I'm doing a shift and changing all files to TypeScript, so wanted to change this file too. But I'm having a weird probem I can't quite figure out.
I get an error warning from VS that Cannot find namespace 'gameInfoContext' . It's literally right there, not sure what I am doing wrong here.
This is my code (trimmed it down for clarity, the issue is still there like this too:
import React, { ReactNode } from "react";
type Props = {
children: ReactNode;
};
interface ContextValues {
[key: string]: any;
}
export const gameInfoContext = React.createContext<ContextValues | undefined>(
undefined
);
export const GameInfoProvider = ({ children }: Props) => {
return (
<gameInfoContext.Provider>
{children}
</gameInfoContext.Provider>
);
};
My file extension was ts rather then tsx.

Data from Gatsby GraphQL always returns undefined?

Gatsby noob here so please bear with me. I have a component that accepts props from the index.js where it is supposed to receive data from an array of objects but will always receive the error TypeError: Cannot read property 'map' of undefined where it's referring to the Hero.js component index.js is calling for.
My assumption is that the data being queried in index.js is either not specific enough or that it is rendering the component before data is received. Here is the index.js file:
import { graphql } from 'gatsby';
import { Layout, SEO, Hero } from 'components';
const IndexPage = ({ data }) => {
const dataFetch = data.contentfulTemplateIndex.heroes;
let tester = () => {
for (let count = 0; count < dataFetch.length; count++) {
return <Hero {...props} />;
}
};
console.log(dataFetch);
let props = {
impactText: dataFetch.impactText,
labels: dataFetch.labels,
primaryLabel: dataFetch.primaryLabel,
location: dataFetch.location
// supportingText: dataFetch.supportingText.json
};
return (
<Layout>
{dataFetch && tester()}
</Layout>
);
};
export const query = graphql`
query {
contentfulTemplateIndex {
heroes {
image {
fluid {
src
}
}
impactText
labels
location
primaryLabel
supportingText {
json
}
}
}
}
`;
export default IndexPage;
Here is the Hero.js component which index.js is calling:
import { Link } from 'gatsby';
import { documentToReactComponents } from '#contentful/rich-text-react-renderer';
import cx from 'classnames';
import styles from './hero.module.scss';
const Hero = (props) => {
return (
<div>
<ul>
<Link className={styles.pills}>{props.primaryLabel}</Link>
{props.labels.map((label) => {
return <Link className={styles.pills}>{label}</Link>;
})}
</ul>
<div className={styles.grid}>
<h1>{props.impactText}</h1>
</div>
</div>
);
};
export default Hero;
It's impossible for an outsider to debug your code without a minimum reproducable example.
The best way to debug GraphQL is to use the GraphiQL interface of your browser.
Run gatsby develop. If it fails because of the TypeError remove the lines of code that cause the type error (but not the code of your GraphQL query!). You need to get your development server runnning.
Open your browser, use the URL: http://localhost:8000/___graphql
Copy your graphQL query from your code and paste it into the GraphiQL query window.
Can you access your data there? If not you made a mistake writing your query or the data is not where it's supposed to be.
This way you can make sure the data exists.
It also helps to console.log(props) so you can examine the data object:
const Hero = (props) => {
console.log(props);
return (

Passing component as a function argument

I have a function that take a component as argument, and return another, enhanced component:
import React from 'react';
import { compose } from 'recompose';
import { connect } from 'react-redux';
import { Link } from 'react-router-dom';
import { Layout, DarkBar } from 'SharedComponents/Layouts';
const myCreationFunction = ({
component,
}) => {
const Route = (props) => {
// Some code here
return (
<Layout>
<div><Link to={props.path}>LinkTitleHere</Link></div>
{React.createElement(component, {
...props,
...someOtherPropsHere,
})}
</Layout>
);
}; // The error points here
const mapStateToProps = () => ({});
const enhance = compose(
connect(mapStateToProps, someActionsHere),
)(Route);
return enhance;
};
I invoke that function in this way:
import MyComponent from './MyComponent';
import myCreationFunction from './HOC/myCreationFunction';
const Route = myCreationFunction({
component: MyComponent,
});
When I run it in the development mode, it runs smoothly. But when trying to build the app using npm run build and going through webpack, I get:
Module parse failed: Unexpected token (35:47)
You may need an appropriate loader to handle this file type.
| function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
> var createListRoute = function myCreationFunction((_temp = _ref, _ref2 = <_Layouts.DarkBar>
| <_Breadcrumbs2.default />
| <_Layouts.RoundAddButton path={addPath} color="white" />
What am I doing wrong?
Edit #1
It seems that the <Link> is causing the problem. Removing it fixed the problem. Also, when trying to replace it with a tag, I get the same issue. Weird.
Edit #2
I have not resolved this issue because of lack of time. I was trying for 1 hour without any progress and decided to go with button tag and onClick method that uses history to push the new url.
It was and is really weird to me, that a Link or <a> tag can break something during the build process itself. I will definitely jump deeper into it in some free time.

Conditional imports in React Native

I'm having a bit of trouble getting conditional imports working in react native.
I have some files that are used in a react web app and in react native.
What I'd like:
if(process.env.REACT_PLATFORM === 'WEB') {
import('some_file').then(({someFunc})=> someFunc())
}
Because 'some_file' imports react_router.
However, this import is still happening, and the RN metro bundler throws
UnableToResolveError: Unable to resolve module 'react-router' from 'some_file'.
Even If I replace it as:
if(false) {
import('some_file').then(({someFunc})=> someFunc())
}
It still trys to load some_file. Is there anyway to only import/require this file if a condition is met?
Cheers!
EDIT:
Things I've tried:
Require instead of import.
https://babeljs.io/docs/plugins/syntax-dynamic-import/
Platform specific imports;
You can place the import in a component with the native.js extension and it will only be bundled for mobile (ios/android). e.g. MyComponent.native.js Then you have a component for web with the same name but the .js extension. e.g. My Component.js
When you import MyComponent from './components/MyComponent', the correct one will be imported and the other ignored.
After a bit of searching, in turns out dynamic imports can be a bit of a pain.
This is the solution I came up with, I've tried it in node.
const MODULE_NAME = <CONDITION> ? require(MODULE_A) : require(MODULE_B);
Alternatively, I guess you could do something like this;
const MODULE_TO_IMPORT = 'MODULE_IMPORT_STRING';
const MODULE_NAME = import(MODULE_TO_IMPORT).then(({someFunc}) => someFunc());
But the problem is that these require a module to be imported either way.
For React-navive-web we can use manage code for platform-specific that will manage in Mobile App and Web Also
Web-specific code # Minor platform differences can use the Platform
module.
import { Platform } from 'react-native';
const styles = StyleSheet.create({
height: (Platform.OS === 'web') ? 200 : 100,
});
For example, with the following files in your project:
MyComponent.android.js
MyComponent.ios.js
MyComponent.web.js
And the following import:
import MyComponent from './MyComponent';
React Native will automatically import the correct variant for each specific target platform.
Platform specific imports are nice but won't help you on the web.
react-native section in package.json is your friend:
"react-native": {
"module1": false,
"module2": "module3"
}
with this setup
// module1
export const x = 1
// module2
export const x = 2
// module3
export const x = 3
// will result in
import {x} from 'module1'
console.log( x === undefined ) // in the react-native environment
console.log( x === 1 ) // in the browser
import {x} from 'module2'
console.log( x === 3 ) // in the react-native environment
console.log( x === 2 ) // in the browser
import {x} from 'module3'
console.log( x === 3 ) // in the react-native environment
console.log( x === 3 ) // in the browser
The docs can be found here. It's for the browser section, but the react-native section works the same way.
I came across a problem where the project I am working on is using react-native-tvos and I tried to add react-native-ad-manager as a dependency but it doesn't support tvOS so I wanted to dynamically import the ad manager dependency for non tvOS platforms. I was able to get it to work like such:
import {Platform} from 'react-native';
const NullComponent = (props: any) => null;
const AdComponent = () => {
const [Banner, setBanner] = React.useState(() => NullComponent);
if (!Platform.isTV) {
import('react-native-ad-manager')
.then(({Banner: AdBanner}) => {
setBanner(() => AdBanner);
})
}
return (
<Banner />
)
}

Categories