automatically export all components in same folder with TS - reactjs

I have an Icons folder and it has so many Icons.
I am trying to export them automatically, but I can't achieve it.
It is my directory map
components
ㅏ ...
ㅏIcons
ㅏ index.ts
ㅏ IconA.tsx
ㅏ IconB.tsx
ㅏ ...
ㅏ IconZ.tsx
ㅏ ...
Icons/index.ts
// I wanna avoid exporting components like this.
// export { IconA } from './IconA';
//below code doesn't work in Typescript.
const req = require.context('.', true, /\.\/.\.tsx$/)
req.keys().forEach((key: any) => {
const componentName = key.replace(/\.\/.\.tsx$/, '$1')
module.exports[componentName] = req(key).default
});
Icons/IconA.tsx
import React from 'react';
export const IconA = () => (<svg>...</svg>);
I wanna use it in another component
import { IconA, IconB, IconZ } from 'components/Icons'

I think the problem here is that because you are dynamically importing and re-exporting, it is not possible for TypeScript to guess the type of the components you are exporting, because it is unknown at compile time: your imports are just files in the eyes of the compiler.
How about using a script to generate your index.ts file to export { IconA } from './IconA'; from each of your icon files, quite similarly to what you are doing here? You could then include this script in your build process and run it everytime you compile before the typescript compiler runs

Related

"Module not found" or "Import path cannot end with tsx" using Storybook with TypeScript

I am writing a component that makes use of another component, so I've written
import { Text } from "../Text/Text"
in the file /src/stories/TextArea/TextArea.tsx.
However, this gives the error
Module not found: Can't resolve '../Text/Text' in '/Users/username/project/src/stories/TextArea'
Changing the import statement to
import { Text } from "../Text/Text.tsx"
makes it work just fine. Instead, the linter complains:
An import path cannot end with a '.tsx' extension. Consider importing '../Text/Text.js' instead.ts(2691)
As I understand it, .tsx endings are forbidden in TypeScript so reconfiguring the linter doesn't seem to be the best option.
Obviously, importing Text.js instead doesn't work as it doesn't exist. Storybook is supposed to work out of the box with TypeScript, so I'm unsure of what I have to do.
In the .mdx files I am using as stories (like Text.stories.mdx), imports including .tsx are accepted without linter complaints. Removing the extension produces a similar Module not found error.
The project was created with create-react-app and is running Storybook 6.5.15.
your import import { Text } from "../Text/Text" should work fine.
Make sure you are using the latest version of Storybook - 6.5.15.
...
I tried to reproduce your issue and failed to do so. I did not get the same error and the import worked just fine. Let me describe what steps I took:
I installed Storybook in my project using npx storybook init --type react. This installed Storybook 6.5.15 for React.
I created a simple component in ./project/src/Button.tsx
import React from 'react'
export const Button = () => {
return <div>MY BUTTON</div>
}
I created a simple story in ./project/stories/Button.stories.jsx like this:
import React from 'react';
import { Button } from '../src/Button';
// More on default export: https://storybook.js.org/docs/react/writing-stories/introduction#default-export
export default {
title: 'Example/Button',
component: Button,
// More on argTypes: https://storybook.js.org/docs/react/api/argtypes
argTypes: {
backgroundColor: { control: 'color' },
},
};
// More on component templates: https://storybook.js.org/docs/react/writing-stories/introduction#using-args
const Template = (args) => <Button {...args} />;
export const Primary = Template.bind({});
// More on args: https://storybook.js.org/docs/react/writing-stories/args
Primary.args = {
primary: true,
label: 'Button',
};
And there are no issues. The import works as it should. If this does not help you, tell us more about your project setup (e.g. are you using Create-react-app), etc. So this issue is easier to reproduce.

how can i ise i18n in unit test from react testing library

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)

Re-exports not properly bundled with Webpack 5

When bundling my web-application, I have realised that re-exports of (some) modules do not work as expected. I have tried multiple optimization settings but so far with no success.
Setup
So basically I have the following setup
config/
webpack.config.ts
package.json
frontend/
apps/
app1/
src/
index.tsx
App.tsx
...
packages/
test/
index.ts
testFunc.ts
test1Func.ts
Test.tsx
Test1.tsx
So I run webpack from config with the entry point frontend/apps/app1/index.tsx which imports App.tsx (standard React application).
This all works fine but I have realised that when produce a production build from app1, unused exports in my App.tsx appear in the bundle. To clarify
import { testFunc } from 'packages/test' // <- `packages` is an alias
const App: React:FC = () => {
const t = testFunc();
return <>Hello World!</>;
}
will include Test.tsx, Test1.tsx and 'test1Func.ts' in the bundle. My index.ts in test/ looks like
export { testFunc } from './testFunc';
export { test1Func } from './test1Func';
export { Test } from './Test';
export { Test1 } from './Test1';
I should mention that testFunc1.ts contains a useEffect hook because I found that as soon as I have react related code, there is no tree shaking for the source anymore. So
// test1Func.ts
export const test1Func = () => {
useEffect(() => {
// do nothing
}, []);
return "Test 1";
}
However, if I import my files directly, eg. import { testFunc } from 'packages/test/testFunc', everything works as expected and only test appears in the bundle. This also applies to the other test components:
App import tests
testFunc
import via index.ts & use in App.tsx => bundles all files inside test/ ❌
import via import { testFunc } from 'packages/test/testFunc' & use in App.tsx => only testFunc.ts is included in the bundle ✅
import via import { testFunc } from 'packages/test/testFunc' & don't use in App.tsx => nothing gets included in the bundle ✅
testFunc1 | Test | Test1 <- they all behave the same
import via index.ts & use in App.tsx => bundles all but testFunc ❌
import via import { test1Func } from 'packages/test/test1Func' & use in App.tsx => only test1Func.ts is included in the bundle ✅
import via import { test1Func } from 'packages/test/testFunc' & don't use in App.tsx => nothing gets included in the bundle ✅
I guess this is just a configuration error, although I have already tried multiple different optimisation settings. The closest probably is the sideEffects option but so far, this also did not have any effect.
Any ideas?
Thank you very much!
I found the solution! 🙌
As guessed, I had Webpack's optimization.sideEffects property misconfigured. sideEffects is necessary for proper tree shaking.
However, in order to get this work properly there are 2 destinations where you have to set the sideEffects property:
your package.json (in my case in every package I use in my monorepo)
and in the webpack.config.ts
Solution
The admittedly very confusing thing is that you will have to set
sideEffects: false in the package.json
and sideEffects: true in the optimization section in your webpack.config.ts
Here's a bit more if you are interested.

'ReactComponent' is not exported from svg

I created a typescript app using CRA, with code like this:
import { ReactComponent as Search } from './search.svg'
It ran fine, and now I want to strip this code into an npm package (library). I created this package by first running CRA again, then removing everything that did not seem relevant (ex. public folder). A simplified version of /dist looks like this:
esm/
icon/
index.d.ts
index.js
index.d.ts
index.js
This is the original icon/index.ts:
/// <reference types="react-scripts" />
import { ReactComponent as Search } from './search.svg'
export const Icons = {
Search,
}
This is the compiled icon/index.d.ts:
/// <reference types="react" /> <-- Changed for some reason??
export declare const Icons: {
Search: import("react").FunctionComponent<import("react").SVGProps<SVGSVGElement> & {
title?: string | undefined;
}>;
};
When I try to run an app that then uses this library, I get the following error:
../dist/esm/common/icon/index.js Attempted import error:
'ReactComponent' is not exported from './search.svg' (imported as 'Search').
How do I resolve this?
My original answer wasn't really clear, and it didn't consider the fact that you were using create-react-app.
Since you are using create-react-app the SVGR library is already installed and being used to process SVG files as a ReactComponent.
You can just import them directly like this:
import Search from './search.svg'
and then use it like so:
export const Component = () => {
return (
<div>
<Search />
</div>
);
}
You import svg files as default imports, without brackets.
Below is an example
/// <reference types="react-scripts" />
import Search from './search.svg'
export const Icons = {
Search,
}

import file dynamically by variable - react native

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

Resources