I'm developing an application using create-react-app and I'm trying to split my code into modules implementing the way described in the react-router huge-apps example.
Everything works well except the unit tests : I get this error while running the jest tests for the route components :
TypeError: Cannot read property 'contextTypes' of undefined
A route component looks like this :
export class IntroPage extends React.Component {
render() {
return (
<div></div>
);
}
}
const mapStateToProps = (state) => {
return {
...
}
};
module.exports = connect(mapStateToProps)(IntroPage);
and a test :
import React from 'react';
import {shallow} from 'enzyme';
import {IntroPage} from '../IntroPage';
it('should render without crashing', () => {
shallow(
<IntroPage {...props}/> // IntroPage is undefined
)
});
How do I have to export/import my components to be able to test them properly.
Thanks.
If you transpile in Babel:
export class IntroPage extends React.Component {
...
}
You will notice that Babel will move that to the exports variable.
Object.defineProperty(exports, "__esModule", {
value: true
});
... more good stuff
...
var IntroPage = exports.IntroPage = function (_React$Component) {
So you can console.log these:
console.log(exports);
console.log(module.exports);
console.log(module);
and check exports variable and module object.
In here module.exports should be the same as exports.
If you type:
module.exports = connect(mapStateToProps)(IntroPage);
at the end of your component file, you are overwriting the module.exports with the new value.
This is the core of the problem.
The solution?
I think you already found one, but the best would be not to mix commonJS with ES6 export, since ES6 export will be transpiled to commonJS syntax.
Check also "What is export default in JavaScript?"
Found a solution with this post : React router dynamic routes not rendering component
I just had to add 'default' to the require statements when exporting with es6 module.
Related
I'm trying to get translations from i18n files in my unit testing, I've seen other answers but they work with just one i18n file, My problem is that, I have 2 files and the folder structure is like this,
i18n/en/translation.json
i18n/es/translation.json
and translation.json file is written like this
{... "info":"information", "name":"Name", ...}
doesn't have an export default.
and here is my test file,
import React from 'react'
import '#testing-library/jest-dom'
import {render} from '#testing-library/react'
import AddUsers from '../../components/AddUsers'
test('Render OK',()=>{
const menuLinkUp =false
const component =render(
<AddUsers/>
)
component.getByText(" how can i call my i18n?")
})
I'm using react testing library and jest for doing this.
There is a section in the documentation: https://react.i18next.com/misc/testing.
I would probably mock the react-i18next module, as it requires the least amount of changes.
jest.mock('react-i18next', () => ({
// this mock makes sure any components using the translate HoC receive the t function as a prop
withTranslation: () => Component => {
Component.defaultProps = { ...Component.defaultProps, t: () => "" };
return Component;
},
}));
(If you actually want to "inject" the translations: https://react.i18next.com/misc/testing#example-test-using-this-configuration)
I am trying to get up and running with react create app and mobx state tree. I keep getting
Support for the experimental syntax 'decorators-legacy' isn't currently enabled (4:1):
I never used react create app so I am not sure how to enable, I tried making a .babelrc file but that did not help
{
"presets": ["#babel/preset-env"],
"plugins": [
["#babel/plugin-proposal-decorators", { "legacy": true }]
]
}
import React, { Component } from "react";
import { observer, inject } from "mobx-react";
#inject("domainStores")
#observer
export default class MainComponent extends Component {
async componentDidMount() {}
render() {
return <div className="main-container">
helllo
</div>;
}
}
I am also open to suggestions if things have changed, I have not used the newest version of Mobx State tree so many there is a better way of doing this now?
If you only using MST without regular MobX stuff and only need inject and observer then you can use regular function syntax, it looks not that great, but does not require any setup:
export const WrappedComponent = inject("domainStores")(observer(Component)
// Or use export default here if you prefer
If you are using other features of MobX then in MobX 6 there is a new thing that will probably allow you to drop decorators (like computed, action and etc) altogether, makeAutoObservable:
import { makeAutoObservable } from "mobx"
class Store {
// Don't need decorators now
string = 'Test String';
setString = (string) => {
this.string = string;
};
constructor() {
// Just call it here
makeAutoObservable (this);
}
}
With that you don't even need decorator syntax to be enabled.
More info here
https://mobx.js.org/migrating-from-4-or-5.html
and
https://mobx.js.org/react-integration.html
Instead of using the old decorators proposal, you can use observer as a function on your components instead of as a decorator.
We can also use React's own context instead of inject, as the documentation states:
Note: usually there is no need anymore to use Provider / inject in new code bases; most of its features are now covered by
React.createContext.
This way we can use Create React App as is.
Example
import React from "react";
import { observer } from "mobx-react";
import { types } from "mobx-state-tree";
const StoresContext = React.createContext("store");
// custom hook that we can use in function components to get
// the injected store(s)
function useStore() {
return React.useContext(StoresContext);
}
const StoreModel = types.model({
things: types.array(types.string)
});
const storeInstance = StoreModel.create({ things: ["foo", "bar"] });
// instead of using the #observer decorator, we can use observer as
// a function and give it a component as argument
const MainComponent = observer(() => {
const store = useStore();
return (
<div>
{store.things.map((thing) => (
<div key={thing}>{thing}</div>
))}
</div>
);
});
export default observer(function App() {
return (
<StoresContext.Provider value={storeInstance}>
<MainComponent />
</StoresContext.Provider>
);
});
CRA does not allow you to extend your own configuration, so, in order to extend cra configuration, you will have to use customize-cra with react-app-rewired.
So, follow the steps below:
Install customize-cra, react-app-rewired and #babel/plugin-proposal-decorators using npm or yarn
add config-overrides.js at root level of your project and paste the code given below:
const {override, addDecoratorsLegacy } = require('customize-cra');
module.exports = override(addDecoratorsLegacy());
Update package.json scripts to the below ones:
"start": "react-app-rewired start",
"build": "react-app-rewired build"
P.S: if you want to use babel configuration then your config-overrides.js should be like:
const {override, addDecoratorsLegacy, useBabelRc } = require('customize-cra');
module.exports = override(addDecoratorsLegacy(), useBabelRc());
useBabelRc will load config from your root of project automatically.
I have a path.json file that contains the path of a component
// path.json
{
"main": "./login/index.js",
"paths": [
{
"name": "login",
"path": "./login/index.js",
"image": ""
}
]
}
I want to load './login/index.js' file dynamically in react native and render this particular file
My current implementation
const MyComponent = createLazyContainer(() => {
const componentPath = PathJson.main; // ./login/index.js
return import(`${componentPath}`); //import error here # line 7
});
export default MyComponent;
I am getting following error :
Invalid call at line 7: import("" + componentPath)
What people have been telling you in the thread is correct but I'd like to add one possible solution. All the imports/require are resolved at compilation time and not at running time which you are trying to do. By the time you are running your app, if you haven't imported the files, you can't use them.
There is a workaround tho, assuming that you know all the files that you might in advance which is to do something like a factory:
const possiblePaths = {
'one': require('path/to/file/1'),
'two': require('path/to/file/2')
}
function(type){
return possiblePaths[type];
}
And then you use it somehow like:
render(){
const MyComponent = function('one');
return <MyComponent/>;
}
This is more or less pseudo code and my not work right away, but hopefully yo get the idea. You need to store a reference to each of the imports you might need and then dont use the import, use the reference that was created for you at compilation time.
Actually, the React Native development concerns are not like development for the Web.
Just for this reason, it is not so important at all to have lazy loading in the production of a react-native project. Just import anything you want and then use them in any files of the project. all of them are in the bundle of production and exactly it is not important at all.
So for this problem, I prefer to have a helper file to collect all selectable libraries and export them:
// helper file
export { default as Index } from './Login';
export { default as OtherComponent } from './OtherComponent';
Then when you wanna use:
import { Index, OtherComponent } from 'helper';
~~~
render() {
const MyComponent = someCondition ? Index : OtherComponent;
return (
<MyComponent />;
);
}
Solution:
const allPaths = {
path1: require('file path1').default,
path2: require('file path2').default
};
render(){
const MyComponent = allPaths["path1"];
return <MyComponent/>
}
In React Native all the files that are being imported are bundled together, only those files can be dynamically imported.
Let's say you have three files index.js, test_1.js and test_2.js and if you have imported only test_1.js in index.js than React Native will only bundle those two files leaving test_2.js.
So to answer your question even if dynamic import works in React Native but because these files are not part of the bundle you are not able to import them.
I've once been in a similar situation where I need to do imports by variable, but that is limited to importing components inside a component and it uses code-splitting (Edit: I'm playing around to look for a solution without relying on code-splitting, I just realized there was a react-native tag in the question, and I don't think code-splitting is a good choice to go with in RN). I'm not sure by how much my method could help you, but here goes.
Side notes:
Importing folder that contains an index.js(jsx|ts|tsx) file should automatically resolve to that index file.
Importing from from './login/index.js' usually throws a 'Module not found' error. Either import from './login/index' or from './login but I prefer the last one as it's the shortest & simplest.
In path.json:
{
"main": "./login", // '.js' is removed
"paths": [
{
"name": "login",
"path": "./login/index.js", // Not sure what this is for, but if necessary, remove the '.js' here as well
"image": ""
}
]
}
In MyComponent.js:
import React, { lazy, Suspense } from 'react'
import PathJson from './path'
// 1. We need a UI to show while component is being loaded
const Loader = () => <div>{'Loading...'}</div>
// 2. We need a fallback UI if component is not found
const DocUnavailable = () => <div>{'We\'re sorry, but this document is unavailable.'}</div>
// 3. Create a resolver function ('resolver' is just a name I give)
function resolveImport(pathToComponent, FallbackComponent) {
let componentFound = false
let RenderComponent = () => <FallbackComponent /> // Assign fallback first
try {
if (require.resolve(pathToComponent)) {
componentFound = true
}
} catch (e) { } // Kinda hacky, if you don't mind, but it works
if (componentFound) {
// If found, replace fallback with the valid component
RenderComponent = lazy(() => import(pathToComponent))
}
return RenderComponent
}
// 4. Finally, implement it in a component
class MyComponent extends React.Component {
render() {
const componentPath = PathJson.main
const RenderComponent = resolveImport(componentPath, DocUnavailable)
return (
<Suspense fallback={<Loader />}>
<RenderComponent />
</Suspense>
)
}
}
export default MyComponent
References:
Implementation for 'resolver' function based on Langutil
Code-splitting with lazy & Suspense based on React Docs
I've got a (private) npm module that exports several React components. The module is bundled by Webpack and in the generated bundle a reference to one of the components (say Warning) looks like this:
t.d(n,"Warning",function(){return ge})
Then I've got a React project importing this module:
import { Warning } from 'my-custom-module';
...
render() {
return (
<Warning>Lorem ipsum</Warning>
);
}
This all works OK, but when I create a Jest snapshot of the component above, I expect the snapshot to look like
<Warning>Lorem ipsum</Warning>
but it looks like:
<ge>Lorem ipsum</ge>
For some reason Jest takes the minified identifier instead of the exported name of the component. How can I see the component name in the Jest snapshot? I'm unsure if I do need to adjust my Webpack config or the Jest setup...
Since you are referring the uglified version of the 'my-custom-module' it will try to render to the uglified names. However, I assume what you actually you need is to shallowly render your component.
You can use the Enzyme libraries's shallow renderer for this.
//MyAwesomeComponent.js
import { Warning } from 'my-custom-module';
export default class MyAwesomeComponent extends Component{
render(){
return (<Warning>Lorem ipsum</Warning>);
}
}
//MyAwesomeComponent.test.js
import { shallow } from 'enzyme';
import MyAwesomeComponent from './MyAwesomeComponent';
it('renders <MyAwesomeComponent />', () => {
const shallowMyComponent = shallow(<MyComponent />);
expect(shallowMyComponent).toMatchSnapshot()
});
This should show your snapshot as Warning without going a level deeper.
Using Create-react-app, I want to lazy load some of my components, this is working fine as long as the components are in the same folder as my main component (where I define the routes) however as soon as I want to load a component from another folder like
loader: () => import("../containers/HomeAContainer")
it fails to find/import the module. (exact same module will work if I move the file!
I have made a complete example which can be seen here
I have also tried to change the route to src/x/x instead of ../x/x but again getting errors.
You are using a wrong path, correct it by using :
{ path: "/c", component: "./containers/HomeAContainer" }
The way i create lazy loading components is through a higher order component. I create a file called "asyncComponent", then i put in the code:
import React, { Component } from 'react';
const asyncComponent = ( importComponent ) => {
return class extends Component{
state = { component: null }
componentDidMount(){
importComponent().then(cmp =>{
this.setState({component: cmp.default});
});
}
render (){
const C = this.state.component;
return C ? <C {...this.props} /> : null;
}
}
}
export default asyncComponent;
This component will receive a function as a parameter and then return the component you want. The function is actually a import funcion with the path of your component that you want to lazy load.
So instead of using:
import Exemple from './example/example';
You will use:
import asyncComponent from './asyncComponent';
const asyncExample = asyncComponent(()=>{
return import('./example/example');
});