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'))
}
}
Related
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 (
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!
I'm working on a project with nuxt.js and I want to implement the atomic design methodology
so I currently import the components like this
import ButtonStyled from '#/components/atoms/ButtonStyled.vue'
import TextLead from '#/components/atoms/TextLead.vue'
import InputSearch from '#/components/atoms/InputSearch.vue'
but I need to import like this
import {
ButtonStyled,
TextLead,
InputSearch
} from '#/components/atoms'
the closer I got was that,
/atoms/index.js
const req = require.context('./', true, /\.vue$/)
const modules = {}
req.keys().forEach(fileName => {
const componentName = fileName.replace(/^.+\/([^/]+)\.vue/, '$1')
modules[componentName] = req(fileName).default
})
export const { ButtonStyled, TextLead } = modules
but I'm still defining the export variable names statically, I need to define dynamics based on the components inside the folder
NOTE: I can not use
export default modules
if I use the above code snippet I will not be able to import the way I need it, which is:
import { ButtonStyled } from "#/components/atoms"
require.context is a quite obscure function in Webpack, you will have issues while running unit tests. But, to solve your problem; You will need to import the index.js file in the main.js of your project.
This is how I do it:
_globals.js
// Globally register all base components prefixed with _base for convenience, because they
// will be used very frequently. Components are registered using the
// PascalCased version of their file name.
import Vue from 'vue'
import upperFirst from 'lodash/upperFirst'
import camelCase from 'lodash/camelCase'
const requireComponent = require.context('.', true, /_base-[\w-]+\.vue$/)
requireComponent.keys().forEach(fileName => {
const componentConfig = requireComponent(fileName)
const componentName = upperFirst(
camelCase(fileName.replace(/^\.\/_base/, '').replace(/\.\w+$/, ''))
)
Vue.component(componentName, componentConfig.default || componentConfig)
})
components/index.js
//...
import './_globals'
//...
main.js
//...
import './components' // This imports in the index.js
//...
This way your components loaded in with require.context() gets registered as a vue component and made globally available. I advice to only use global components with components that will be used a lot. Do not load a component globally if you intend to use it only one time.
You can find a working example here -> https://github.com/IlyasDeckers/vuetiful/tree/master/src
To get your unit tests working with jest, you will need to mock require.context(). This was a true pain, but can be achieved easily by using babel-plugin-transform-require-context
I try to use your way to do that, and known you have make a mistake at module.exports
module.exports can not use import , i think you may can do like this
at atoms/index.js
const req = require.context("./", true, /\.vue$/);
const atoms = {};
req.keys().forEach(fileName => {
const componentName = fileName.replace(/^.+\/([^/]+)\.vue/, "$1");
atoms[componentName] = req(fileName).default;
});
export default atoms;
at where to use
import k from "#/components/atoms/index.js";
export default {
components: {
test1: k.test1,
test2: k.test2
}
};
or index.js
import test1 from "./test1.vue";
import test2 from "./test2.vue";
export { test1, test2 };
and where to use like this
import {test1,test2} from "#/components/atoms/index.js";
export default {
components: {
test1,
test2
}
};
I created a library that does all this for me, maybe it helps other people.
named-exports
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.
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 />
)
}