I have a lot of minor components:
export const A = () => {...}
export const B = () => {...}
...
export default [A, B, ...];
After adding another component to the file, I might forget to add it to the export default [...]. By now you should see what the problem is.
How should I resolve this issue? Is it maybe possible to define a list that includes all exported functions?
I want to be able to iterate over all components in other files. Hence why it has to be a list they are grouped into.
origin file src/foo.js:
export const A = () => { ... };
export const B = () => { ... };
export const C = () => { ... };
Create an src/index.js as following:
import foos from './foo';
const allComponents = Object.values(foos);
export default allComponents;
From another file we'd:
import foos from 'src/foo';
foos[0]() // A()
foos[1]() // B()
...
EDIT: fixed the example code, needs to use values() not keys(), my bad
With some minor modifications, you can use import *. This gives you all of the named exports of a file, wrapped up into an object. If, for example, your old code was doing this:
import allComponents from 'someFile'
allComponents.forEach(component => {
// do something with the component
})
... instead do this:
import * as allComponents from 'someFile'
Object.values(allComponents).forEach(component => {
// do something with the component
})
As new exports are added to the file, this import statement will pull them in automatically, and so the default export array shouldn't be needed anymore.
Related
Recently I'm trying my best in redux, and I have seen a really good folder structure project, I tried to get the same structure, but the same way didn't work...
E.g. I've got a path something like that: ./src/_actions and inside this folder I've got "user.actions.js", "alert.actions.js", "index.js".
In alert.actions.js I've got something like that:
import { alertConstants } from "../_constants/alert.constants";
export const alertActions = {
success,
error,
clear,
};
function success(message) {
return { type: alertConstants.SUCCESS, message };
}
function error(message) {
return { type: alertConstants.ERROR, message };
}
function clear() {
return { type: alertConstants.CLEAR };
}
And I'd love to import all of them from one place like to the folder where path is "./../test.js":
import {alertActions} from "./../_actions";
import {useDispatch} from "react-redux";
export const test = () => {
const dispatch = useDispatch();
return (
<button onClick={() => dispatch(alertActions.success("test"))}> Click </button>
)
}
but I got something like "alertActions.success" is undefined. I don't know what I'm doing wrong.. In that project, index.js has been empty as well... that object supposed to export those all functions.. somebody know any solution? :(
You need to export the object after the functions are made. Usually they are hoisted, but in this case you are probably using strict mode and they are not. You have to either move the export object or better yet export all of them individually and then when you are importing them you should write:
import * as alertActions from 'youfile'
And if you want to export a whole object you should export it like:
export default alertActions
And then you need to import them like:
import alertActions from 'yourfile'
and access them:
alrtActions.error()
Using es6 imports, you can do this:
import { MyComponent } from "../path/to/components.js";
export default function () {
return <MyComponent/>;
}
Can I do it with React.lazy too?
const { MyComponent } = lazy(() => import("../path/to/components.js"));
I get the following error, but I'm not sure if it's related to this or some other bug I have:
Element type is invalid: expected a string (for built-in components) or a class/function (for composite components) but got: undefined
Here is how I did it when I faced this problem with FontAwesome:
const FontAwesomeIcon = React.lazy(()=> import('#fortawesome/react-fontawesome').then(module=>({default:module.FontAwesomeIcon})))
You can if you use react-lazily.
import { lazily } from 'react-lazily';
const { MyComponent } = lazily(() => import("../path/to/components.js"));
It also allows importing more than one component:
const { MyComponent, MyOtherComponent, SomeOtherComponent } = lazily(
() => import("../path/to/components.js")
);
See this answer for more options.
React.lazy currently only supports default exports. If the module you want to import uses named exports, you can create an intermediate module that reexports it as the default. This ensures that tree shaking keeps working and that you don’t pull in unused components.
// ManyComponents.js
export const MyComponent = /* ... */;
export const MyUnusedComponent = /* ... */;
// MyComponent.js
export { MyComponent as default } from "./ManyComponents.js";
// MyApp.js
import React, { lazy } from 'react';
const MyComponent = lazy(() => import("./MyComponent.js"));
More info:
https://reactjs.org/docs/code-splitting.html#named-exports
Of course you can. It's an honest mistake many has made,
const sub = a
const obj = { a: 'alpha', b: 'beta' }
obj.sub // wrong (accessing a direct key)
obj[sub] // right (computed property)
the same mistake slipped through for many. This is a work in progress but worked like a charm, and thanks for all the other answers to tailor it to my need.
const ComponentFactory = ({ componentName, ...props }) => {
const Component = lazy(() => import('baseui/typography').then((module) => ({ default: module[componentName] })))
return (
<Suspense fallback={<div>Loading...</div>}>
<Component {...props} />
</Suspense>
)
}
usage:
<ComponentFactory
componentName='Paragraph1'
margin='0.1rem 0rem 0.25rem 0.3rem'
color={style[of].headingText}
>
{headingMessage}
</ComponentFactory>
You can't with React.lazy :
React.lazy takes a function that must call a dynamic import(). This must return a Promise which resolves to a module with a default export containing a React component.
(cf. https://reactjs.org/docs/code-splitting.html#reactlazy)
A workaround for that exists: creating an intermediate module that imports your named export and exports it as default (cf. https://reactjs.org/docs/code-splitting.html#named-exports)
I'd like to another workaround. This compotent chains the promise and adds the named export to the default export. src. Although, I'm not sure if this breaks tree shaking. There's a bit of an explanation here.
import {lazy} from 'react'
export default (resolver, name = 'default') => {
return lazy(async () => {
const resolved = await resolver()
return {default: resolved[name]}
})
}
You can resolve a promise along with the lazy loading and this way resolve your named export.
The syntax is a bit funky, but it is working:
const MyComponent = React.lazy(
() =>
new Promise(async (resolve) => {
const module = await import('../path/to/components.js');
resolve({ ...module, default: module.default });
}),
);
Having some issues using spyOn for testing a method call inside my compose() block for my Reactjs app using recompose, redux, etc.
Basic layout is this:
// index.jsx
import { foo, baz } from './lib';
const enhance = compose(
foo(),
lifecycle({
componentDidMount() {
baz();
}
});
);
export const MyComp = (...);
const mapStateToProps = state => (...);
export connect(mapStateToProps)(enhance(MyComp));
// lib.js
export const foo = () => {
lifecycle({
componentDidMount() {
bar();
}
});
}
export const bar = () => {};
export const baz = () => {};
//index.test.jsx
import * as lib from '.libs';
describe('Test', () => {
const didMountSpy = jest.spyOn(MyComp.prototype, 'componentDidMount');
const fooSpy = jest.spyOn(lib, 'foo');
const barSpy = jest.spyOn(lib, 'bar');
const bazSpy = jest.spyOn(lib, 'baz');
const wrapper = mount(<MyComp ... />);
expect(didMountSpy).toHaveBeenCalledTimes(1); // PASS
expect(bazSpy).toHaveBeenCalledTimes(1); // PASS
expect(fooSpy).toHaveBeenCalledTimes(1); // FAIL
expect(barSpy).toHaveBeenCalledTimes(1); // FAIL
});
Strange part for me is if you look at baz(), I'm able to successfully expect the function call when it's not wrapped in another method in compse(). But I'm not able to expect foo(), bar(). My suspicion is that there is some weirdness with how enzyme/jest mocks the Reactjs lifecycle methods.
Has anyone one run into anything similar and have solutions to get spyOn to work for the nested methods under lifecycle.componentDidMount()?
Thanks!
You are supposed to use jest.mock method for mocking imports
Follow these steps:
Create a folder named __mocks__ in the same folder where lib.js is located
Create a file named lib.js in __mocks__ folder and provide a mock implementation for foo, bar and baz methods/components
In index.test.jsx do not import lib.js. Also, before you import index.js in index.test add the following line
jest.mock('path-to-lib/lib.js')
Refer: https://jestjs.io/docs/en/manual-mocks
fooSpy
fooSpy isn't called because foo gets called when enhance gets created which happens right when index.jsx is imported so foo has already been called by the time it gets wrapped in a spy to create fooSpy.
If you want to capture that foo was called you will need to delay the creation of enhance.
barSpy
bar() is defined locally in lib.js and it is being called directly within the definition of foo.
jest.spyOn replaces the module export of bar. Because the definition of foo is using bar directly and not the module export of bar, replacing the module export of bar with a spy does not affect the call within foo and barSpy is never called.
If you want to capture that bar was called you will need to use the module export of bar within the definition of foo.
Here is a working version of the test:
lib.js
import { lifecycle } from 'recompose';
import * as self from './lib'; // import the module into itself
export const foo = () =>
lifecycle({
componentDidMount() {
self.bar(); // call the module export for bar()
}
});
export const bar = () => {};
export const baz = () => {};
index.jsx
import * as React from 'react';
import { compose, lifecycle } from 'recompose';
import { foo, baz } from './lib';
export class MyComp extends React.Component {
render() { return (<div>My Comp</div>); }
}
// use a function to delay the creation of enhance()
export const initialize = () => {
const enhance = compose(
foo(),
lifecycle({
componentDidMount() {
baz();
}
})
);
return enhance(MyComp);
}
index.test.jsx
import * as React from 'react';
import { mount } from 'enzyme';
import * as lib from './lib';
import { initialize } from './index';
test('MyComp', () => {
const fooSpy = jest.spyOn(lib, 'foo');
const barSpy = jest.spyOn(lib, 'bar');
const bazSpy = jest.spyOn(lib, 'baz');
const MyComp = initialize(); // this will create enhance() which will call foo()
const didMountSpy = jest.spyOn(MyComp.prototype, 'componentDidMount');
const wrapper = mount(<MyComp />);
expect(didMountSpy).toHaveBeenCalledTimes(1); // PASS
expect(bazSpy).toHaveBeenCalledTimes(1); // PASS
expect(fooSpy).toHaveBeenCalledTimes(1); // PASS
expect(barSpy).toHaveBeenCalledTimes(1); // PASS
});
I cant get why following approach doesn't work:
constants.js
import { createConstants } from '../utils';
export default createConstants(
'LOGIN_REQUEST',
'LOGIN_SUCCESS',
'LOGIN_FAILURE',
'LOGOUT',
'FETCH_DATA_REQUEST',
'RECEIVE_DATA'
);
utils.js
import React from 'react';
export function createConstants(...constants) {
return constants.reduce((acc, constant) => {
acc[constant] = constant;
return acc;
}, {});
}
Next i want to import LOGIN_REQUEST for example as redux action.
import { LOGIN_REQUEST, LOGIN_SUCCESS, LOGIN_FAILURE, LOGOUT } from '../constants';
But i'm getting undefined each time for all imported constants.
It works only when i'm defining like this:
export const LOGIN_REQUEST = 'LOGIN_REQUEST';
Maybe somebody has some ideas?
Your first approach is called a default export. It doesn't work because the syntax you're using is not correct.
From the MDN export entry, that's how you write a default export:
// module "my-module.js"
export default function cube(x) {
return x * x * x;
}
Your second approach is called named export and it works because it has the right syntax. Again from MDN:
export const foo = Math.sqrt(2); // exports a constant
Hope it helps.
Export doesnt work like that. You can try something like:
import allConstants from '../constants';
Then use a constant like:
allConstants.LOGIN_REQUEST
I'm fairly new to React & Redux and all this while, I'm putting all my action creators & my constants into a single file '../actions/index.js'.
When I started out, this seemed fine but now, it's become this huge block of unreadable code.
Would you suggest breaking down index.js into functionality1.js, func2.js, func3.js and then importing actions as needed from split up files:
import {sampleAction} from '../action/func1';
Or is there a better way to do this?
Sorry if this seems like a silly question. I'm still trying to understand best practices in a React-Redux application.
You can still split it up but retain the index.js for simplicity using the export * from '...' syntax.
actions/functionality1.js:
export const ACTION_1 = '...'
export const ACTION_2 = '...'
export const action1 = () => {...}
export const action2 = () => {...}
actions/functionality2.js:
export const ACTION_3 = '...'
export const ACTION_4 = '...'
export const action3 = () => {...}
export const action4 = () => {...}
actions/index.js:
export * from './functionality1'
export * from './functionality2'
Then you can import any you need like so:
import { action1, action4, ACTION_2, ACTION_3 } from './actions'