Preface
I'm using create-react-app to generate an application.
Problem
TodoList === undefined
Code
components/index.js
export { default as App } from './App/App.js';
export { default as TodoList } from './TodoList/TodoList.js';
containers/index.js
export { default as VisibleTodoList } from './VisibleTodoList.js';
components/App/App.js
import { VisibleTodoList } from '../../containers/index.js';
containers/VisibleTodoList.js
import { TodoList } from '../components/index.js';
components/TodoList/TodoList.js
export default function TodoList({ todos, onTodoClick }) { ... }
TodoList is now undefined. I believe it may have something to do with the fact that I have some sort of circular issue.
The thing is, if inside containers/VisibleTodoList.js I import using the following method, everything works fine.
import TodoList from '../components/TodoList/TodoList.js';
What is so special that breaks the import, if I try to import using a 'middleman' (the components/index.js file).
Full code
I have created a CodeSandbox that contains my full code, as it stands in my application. The application is pretty simplistic, but more complicated than I have outlined here.
https://codesandbox.io/s/m54nql1ky9
The problem is caused by the order of exports in your components/index.js file.
export { default as App } from './App/App.js';
export { default as TodoList } from './TodoList/TodoList.js';
Since App.js imports VisibleTodoList which needs to import TodoList and pass it to the redux connect function before it can export itself, you end up with a conflict.
I'm not sure if this is a implementation quirk of babel, or if this is a logical result from how the ES import spec is defined.
In any case, changing the order of exports fixes the bug.
export { default as TodoList } from './TodoList/TodoList.js';
export { default as App } from './App/App.js';
As a rule of thumb, if you can't refactor your files to avoid the import loop, you should put the outer layer component last in the list, since it might rely on imports higher up in the list.
Full working codesandbox here: https://codesandbox.io/s/74mlwnwyy1
Related
I am working with a React project where each component's files are contained in their own directory. I have a component.jsx file and an index.js file that re-exports the component's default export. This works as expected. I would like to simplify my import statements by re-exporting all components up directory level from the main components folder. See below for an example of my current project foloder structure.
src
--components
----Alert
------Alert.jsx
------index.js
----LoginCard
------LoginCard.jsx
------index.js
--index.js
Alert/Alert.jsx
export default function Alert(props) {
// omitted for brevity - the component itself works fine
return <Alert />
}
Alert/index.js
export { default } from './Alert';
At this point, imports work as expected in the LoginCard component.
LoginCard.jsx
import { UserContext } from '#contexts';
import Alert from '#components/Alert';
export default function LoginCard(props) {
// omitted for brevity. Again, component works as expected
return <LoginCard />
In order to achieve my desired end result of simplifying import calls, I created components/index.js:
components/index.js
export { default as Alert } from './Alert';
Which I then attempt to import as:
LoginCard.jsx
import { Alert } from '#components'
When attempting to import from component/index.js as import { Alert} from '#components' I receive an exception that states "cannot read default property of undefined". What makes this strange is that I import/export my pages and contexts in exactly the same manner and they work as expected. I originally thought that this implied an issue with my components directory alias, but the alias is working as I can import just fine from #components/Alert, just not from #components
Have a missed something simple, or am I hitting a bug? Thanks.
I think the issue here is that you are attempting to push all the exports up the tree towards the root directory. This makes sense when importing somewhere outside that root directory. The issue lies in the fact that you are attempting to import from the root while inside the directory structure. In other words, all the exports from within the directory need to be processed before anything can be exported from the root.
The snag here is that you are attempting to import Alert from the root export from LoginCard... which while being processed hasn't finished its exports, so the root export isn't ready yet.
In other words, #components/Alert is ready, #components is not.
Just use a relative import of Alert (and any other import) from within the same components directory.
import { UserContext } from '#contexts';
import Alert from '#components/Alert';
// or
import Alert from '../Alert';
export default function LoginCard(props) {
// omitted for brevity. Again, component works as expected
return <LoginCard />
I need to add to ALL of my React function components the possibility to add [data-test-id] attribute for testing purposes. To achieve that I created withTestId() HOC which adds optional prop testId to wrapped component and when it's defined it adds [data-test-id] to final HTML.
So when I define component like:
<ExampleComponent testId="example" />
it returns:
<div data-test-id="example" />
The only problem I have is to apply it to every component without the necessity to wrap it individually in every component. So instead of writing code like:
function ExampleComponent() { ... }
export default withTestId(ExampleComponent)
I would like to wrap all of my exports in my index.ts file, which right now looks like this:
export { default as ExampleComponent } from "./ExampleComponent";
export { default as ExampleComponent2 } from "./ExampleComponent2";
...
How can I achieve this?
I see two ways of doing this; One dynamic way, making the user-code of your library a bit more convoluted. with you being able to change the implementation easily and another one with a bit more boilerplate code, keeping the user-code as it is.
I haven't tested their behavior regarding tree-shaking when bundling the code.
Using destructing in user-code
This allows to add / remove things from your main component export file without having to worry about additional boilerplate in your library. The higher-order-component can be switched on/off easily. One caveat: The user code needs to use destructuring to retrieve the components.
Your new index.ts file would look like this, while I've called your previous index.ts file components.ts in the same directory:
import * as RegularComponents from "./components";
import withTestId from "./with-test-id";
const WithTestIdComponents = Object
.keys(RegularComponents)
.reduce((testIdComps, key) => {
return {
...testIdComps,
[key]: withTestId(RegularComponents[key])
};
}, {});
export default WithTestIdComponents;
To use it in your application code:
import MyComponents from "./components/tested";
const { Component1, Component2, Component3, Component4 } = MyComponents;
This uses the default export to make it look like you have all components in one place, but since you cannot destructure exports directly, you need this second step to get the correct components out of it.
Add boilerplate to the export file
Since there is an index.ts file with all the components exported in the library, one could import/rename each component and re-export them with withTestId and their name:
import withTestId from "./with-test-id";
import { default as TmpComponent1 } from "./component1";
import { default as TmpComponent2 } from "./component2";
import { default as TmpComponent3 } from "./component3";
import { default as TmpComponent4 } from "./component4";
export const Component1 = withTestId(TmpComponent1);
export const Component2 = withTestId(TmpComponent2);
export const Component3 = withTestId(TmpComponent3);
export const Component4 = withTestId(TmpComponent4);
This way, imports can be used as before:
import {
Component1,
Component2,
Component3,
Component4
} from "./components";
I'd argue that using index files already is some kind of boilerplate and this approach adds to it. Since the user code does not need any changes, I'd favor this approach.
In one of our projects, we have used a custom takeoff script to create this kind of boilerplate for us, whenever we generate a new component.
Examples
Here is a code sandbox to see both approaches.
I have a files like this:
components/carousel.js
import hammerjs from 'hammerjs';
import React from 'react';
export default () => <div>...</div>;
components/layout.js
import React from 'react';
export default () => <div>...</div>;
components/index.js
export { default as Carousel } from './carousel';
export { default as Layout } from './layout';
Now in my server side rendered app I import layout like so:
import { Layout } from './components';
I get an error about window not being defined, because it's reading through components/index.js and seeing hammerjs dependency inside the Carousel export, which requires window and isn't available on the server.
Why is it reading the code in the Carousel component when I'm only trying to import the Layout component? How do I avoid this happening?
In short - why is it reading the Carousel component?
You are exporting two defaults in your components/index.js which is not a supported operation. Export the named components individually or one default objet that holds both.
1) From MDN on import/exports - You can have multiple named exports per module but only one default export. link
In your components index file you're exporting both as default - export the named variable. Then import the named variable.
2) The HammerJS dependency shouldn't matter. Your react code will be run through some sort of transpiler that will take the node modules and turn them into something the browser can understand.
I'm making a semi-complex Fragment using React/Styled-Components. I always try to improve the flexibility of larger Fragments/Components by exporting any sub-components that is styled so minor tweaks can easily be made anywhere in the DOM tree whenever my team need similar components.
I do this by collecting everything in an index file, and using Module Redirects as described at the end of this Mozilla article:
https://developer.mozilla.org/en-US/docs/web/javascript/reference/statements/export
However, for some reason when I try to do export { SomeComponent, SomeOtherComponent } from './SomeStuff.styled' or export * from './SomeStuff.styled' I get the error Cannot create styled-component for component: undefined.
What am I doing wrong?
The project is closed source so I can't show full files or a repo
All the exports in ./ListElement are named exports.
// In index.js
export { default as Counter } from './Counter';
export { Element, Container, Names, Prices } from './ListElement';
export { default as OrderListHeading } from './OrderListHeading';
// In the Fragment where I'm using it
import { Counter, Element } from '.';
...
const Heading = styled(Element)``;
The curious thing is that it works if I import and export seperately as such:
// In index.js
import { Element, Container, Names, Prices } from './ListElement';
export { default as Counter } from './Counter';
export { Element, Container, Names, Prices };
export { default as OrderListHeading } from './OrderListHeading';
export { default as Counter } from './Counter';
export { Element, Container, Names, Prices } from './ListElement';
export { default as OrderListHeading } from './OrderListHeading';
This is not yet in current ECMAScript standard.
It's still a proposal in stage 1. In order for a language feature proposal to land in the current ECMAScript standard, it must move to stage 4.
You are probably using a project configuration that doesn't support this. But if you can extend your Babel configuration you can start using this feature by installing #babel/plugin-proposal-export-default-from.
I am learning react-native and redux from this article,
https://github.com/alinz/example-react-native-redux/tree/master/Counter, and I want to understand why inside folder reducers, there is an index.js with content as below:
import counter from './counter';
export {
counter
};
I dont understand why we need this, since in the same folder reducers, there is counter.js with content as follow
export default function counter(state = initialState, action = {}) {
...
}
it already export default counter, why does index.js do it again
If your application grows with lots of reducers, you can 'import nameHere from reducers'. (it is just a convenience). Also, your code is easier to 'refactor' ussually, since you don't need to change the actual import, but you can for instance import multiple from this same file.
// this is preferred
import { ScalesReducer, BoxReducer } from './reducers';
// does the same, takes more space (more distraction in your code)
import ScalesReducer from './reducers/ScalesReducer';
import BoxReducer from './reducers/BoxReducer';