How to wrap every exported comopnent with HOC? - javascript

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.

Related

Dynamic export of variables ES5 to ES6

I'm working on a vue/nuxt.js project and would like to apply the atomic design methodology, i would like to import the components in a clustered and smarter way.
currently
import ButtonStyled from '#/components/atoms/ButtonStyled.vue'
import TextLead from '#/components/atoms/TextLead.vue'
import InputSearch from '#/components/atoms/InputSearch.vue'
How I wish
import {
ButtonStyled,
TextLead,
InputSearch
} from '#/components/atoms'
Solution?
index.js in folder of atoms
it works perfectly (ES5)
// ES5 works 👍
const req = require.context('.', false, /\.vue$/)
req.keys().forEach(fileName => {
const componentName = fileName.replace(/^.+\/([^/]+)\.vue/, '$1')
module.exports[componentName] = req(fileName).default
})
// ES6 does not work 👎
// ERROR: Module build failed, 'import' and 'export' may only appear at the top level
const req = require.context('.', false, /\.vue$/)
req.keys().forEach(fileName => {
const componentName = fileName.replace(/^.+\/([^/]+)\.vue/, '$1')
export const [componentName] = req(fileName).default
})
nuxt use ES6
NOTE: I can not export an object because I can not use import {ButtonStyled} or I will have to de-structure the object after importing it
I need to export so that I can use
import { ButtonStyled } from '#/components/atoms'
I need to export name of each component in the folder
Any advice, information or suggestions will be appreciated.
Thanks in advance.
Well first of all you need to be careful when making use of import/export on EM6, since now you can't export anywhere outside of the top level scope of the js file and the general treatment of it is different than in EM5.
Now with the problem. I see you are exporting the components from inside of a ForEach loop/function and that works totally fine in EM5 but with EM6 It's different, and at least I see two ways you can solve the problem if you aren't expecting the number of components to grow dinamically:
Call a function that returns the component and export it, do it for each component. Should look something like this:
./componentsFile.js
exports.component1 = () => { /*code...*/ return component }
exports.component2 = () => { /*code...*/ return component }
./renderingFile.js
import { component1, component2 } from './componentsFile.js'
/* use the components that you get from the return... */
The other way is to build an object which fields are the components. And destructure it when you are importing.
./componentsFile.js
const component1 = /*Create the component*/
const component2 = /*Create the component*/
exports.object = {
component1,
component2,}
./renderingFile.js
import { component1, component2 } from './componentsFile.js'
/*Use the components...*/
I think you can get the idea with this two ways.
I created a library that solved this problem for me, makes exports named from a directory and listens to the creation, rename and exlclusion of the modules and updates the index.js that does the export.
Maybe it helps other people.
named-exports

JS `import` is undefined, potentially circular import issue?

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

ES6 modules: How to automatically re-export all types in the current directory from index.js?

I have lots of code like this scattered around index.js files throughout my React Native project:
import Restaurant from './Restaurant';
import Store from './Store';
import Vineyard from './Vineyard';
import Wine from './Wine';
export {
Restaurant,
Store,
Vineyard,
Wine
};
It's very repetitive and tedious to write out. Is there a way I can automatically re-export all of the other files in the current working directory from index.js? (note I'm also using Flow in my project so any solution should preserve the type information it can infer.)
Thanks.
export * from './Restaurant';
export * from './Store';
By using the above syntax, you can access all exported properties from each component and export them directly.
This is a common pattern when you grouping all Actions in each individual Action file inside index.js and exporting them directly. You can have a look at the github repo
You can also use this pattern if you'd like:
export { default } from './Comp'
export { default as CompHeader } from './CompHeader'
export { default as CompContent } from './CompContent'
// Usage
import Comp, { CompHeader, CompContent } from './component/Comp'

React-native Redux counter example: explain about reducer

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';

import or require React components dynamically

I'm trying to import / require components dynamically, but somehow when I do it React complains. The require function does find it, but React throws an error saying it is missing some functions 't' etc.. All of this in an electron app.
I have a wizard setup (that is working, but not so elegant I think), where each page has it's own layout and jsx component. If I'd like to add a new page, I don't want to manage x-number of files, and at the moment I have to due to the setup I have currently. Below you can find what I want to achieve and what I'm doing now to achieve it. If there are any suggestions, code smells or better options please let me know as I'm quite new to React and ES2015 (as I'm from a C# background).
What I'm trying to achieve
export default class WizardPageContainer extends Component {
// Constructor
constructor(props) {
super(props);
}
// Render
render() {
const WizardPage = require(`./path/to/${this.props.step.id}`);
return (
<WizardPage step={this.props.step} />
);
}
}
How I'm currently doing it : which means I have to declare the imports / files first on top of the "WizardPageContainer" component.. Which means extra work and prone to errors/forgetting things. I should add, this code is working now ok, but I don't think this is elegant/future proof:
/* PAGES */
import WizardPage_Welcome from './pages/0.wizard.welcome';
import WizardPage_SystemCheck from './pages/1.wizard.systemCheck';
import WizardPage_SignIn from './pages/2.wizard.signIn';
import WizardPage_ExamCode from './pages/3.wizard.examCode';
import WizardPage_TakeExamination from './pages/4.wizard.takeExamination';
import WizardPage_Close from './pages/5.wizard.close';
const pages = [
WizardPage_Welcome,
WizardPage_SystemCheck,
WizardPage_SignIn,
WizardPage_ExamCode,
WizardPage_TakeExamination,
WizardPage_Close
];
/*/********************************************************************///
/* ******************************************************************** */
/* COMPONENT */
export default class WizardPageContainer extends Component {
// Constructor
constructor(props) {
super(props);
}
// Render
render() {
const WizardPage = pages[`${this.props.step.id}`];
return (
<WizardPage step={this.props.step} />
);
}
}
/*/********************************************************************///
I think it is about the "default". i have problem like this. Can you check this code;
https://github.com/robeio/robe-react-admin/blob/master/src/app/HasAuthorization.jsx#L10
Also you can check the example usage;
https://github.com/robeio/robe-react-admin/blob/master/src/app/HasAuthorization.jsx#L26
Your const pages needs to be an object, not an array.
You can see a working version I made of this here:
https://github.com/Frazer/meteor-react-nav/blob/master/lib/jsx/App.jsx
Best advice: Use Webpack to handle your imports, it's way more efficient than we could ever be.

Categories

Resources