Traversing React Component Hierarchy in VS CODE
Sapling is a very helpful extension for traversing React Components.
You can read about it here
Sapling is a very helpful extension for traversing React Components.
You can read about it here
Types for Post resource
// types
export type Post = {
id: number;
title: string;
description: string;
};
export type PostFormData = Omit<Post, 'id'>;
export type PostQueryKey = ['post', { postId: number }];
export type FetchPost = {
queryKey: PostQueryKey;
};
// api/posts/requests.ts
export const fetchPosts = (): Promise<Post[]> => client.get('/posts');
export const fetchPost = ({ queryKey: [, param] }: FetchPost): Promise<Post> =>
client.get(`/posts/${param.postId}`);
export const createPost = (data: PostFormData) => client.post('posts', data);
export const editPost = (data: Post) => client.put(`/posts/${data.id}`, data);
export const deletePost = (postId: number) => client.delete(`/posts/${postId}`);
// api/posts/selectors.ts
export const getPosts = (data: any): Post[] => data.data;
export const getPost = (data: any): Post => data.data;
// api/shared.ts
type SelectorsMap = {
[key: string]: (...arg: any[]) => any;
};
export function handleSelectors<T extends SelectorsMap, K extends keyof T>(
selectors: T,
...additionalParams: any[]
) {
return function selectorResult(rawData: any) {
const keys = Object.keys(selectors) as K[];
const initialData = {} as {
[Key in K]: ReturnType<T[Key]>;
};
return keys.reduce((acc, selectorName) => {
const selector = selectors[selectorName];
acc[selectorName] = selector(rawData, additionalParams);
return acc;
}, initialData);
};
}
// api/posts/hooks.ts
export const useGetPosts = ({
selectors = { posts: getPosts },
...options
} = {}) =>
useQuery('posts', fetchPosts, {
select: handleSelectors(selectors),
...options,
});
export const useGetPost = ({
postId = 0,
selectors = { post: getPost },
...options
} = {}) => {
const queryKey: PostQueryKey = ['post', { postId }];
return useQuery(queryKey, fetchPost, {
select: handleSelectors(selectors),
...options,
});
};
export const useCreatePost = (options = {}) =>
useMutation(createPost, {
mutationKey: 'createPost',
...options,
});
export const useEditPost = (options = {}) =>
useMutation(editPost, {
mutationKey: 'editPost',
...options,
});
export const useDeletePost = (options = {}) =>
useMutation(deletePost, {
mutationKey: 'deletePost',
...options,
});
There are tutorials for setting up HTTPS for CRA like this one but if you are using craco to override webpack config, then setting SSL_CRT_FILE=./.cert/cert.pem SSL_KEY_FILE=./.cert/key.pem
env variables does not properly link the cert and key files.
In order to do so you need to add below config to craco.config.js
module.exports = {
devServer: {
https: {
key: fs.readFileSync('./.cert/key.pem'),
cert: fs.readFileSync('./.cert/cert.pem'),
},
},
}
Kudos to TRomik who helped me with that.
Short answer is: NO.
Long answer is: It will not work.
Proof: codesandbox
When using console.log
or debugger
to display draftState in redux-toolkit reducer we normally get Immer.js proxy object which contains lots of unnecessary information. (Immer.js is used by redux-toolkit by default)
<ref *1> {
type_: 0,
scope_: {
drafts_: [ [Circular *1], [Object], [Object] ],
parent_: undefined,
canAutoFreeze_: true,
unfinalizedDrafts_: 0
},
modified_: true,
finalized_: false,
assigned_: {},
parent_: undefined,
base_: {
effects: {
createEnrollmentBatch: [Object],
updateEnrollmentBatch: [Object],
deleteEnrollmentBatch: [Object],
loadEnrollmentList: [Object]
},
enrollmentsByQstreamId: {},
companyEnrollmentStausByQstreamId: {}
},
draft_: [Circular *1],
revoke_: [Function (anonymous)],
isManual_: false
// and many more
}
To display the actual data use current
function imported from @redux/toolkit
import { createSlice, current } from '@reduxjs/toolkit'
...
console.log(current(draftState))
In the log output we’ll see only our data.
{
effects: {
createEnrollmentBatch: { results: [], status: 'NOT_BATCHING', isEnrollMe: null },
updateEnrollmentBatch: { results: [], suspended: null, status: 'NOT_BATCHING' },
deleteEnrollmentBatch: { results: [], status: 'NOT_BATCHING' },
loadEnrollmentList: { status: 'LOAD_SUCCESSFUL', error: false }
},
enrollmentsByQstreamId: { '3': {} },
companyEnrollmentStausByQstreamId: {}
}
Running some logic conditionally on component unmount requires usage of useRef
hook.
We cannot do:
useEffect(() => {
return () => {
if (status === "explanation") {
console.log("Trigger logic");
}
};
}, []);
Because status
coming from the useState hook would not be properly updated due to stale closure
We also cannot add status to dependency array
useEffect(() => {
return () => {
if (status === "explanation") {
console.log("Trigger logic");
}
};
}, [status]);
Because the effect would be triggered every time the status changed (not only on unmount)
We need to useRef to smuggle the status value into useEffect without triggering it on status change.
useEffect(() => {
return () => {
if (statusRef.current === "explanation") {
console.log("Trigger logic");
}
};
}, [statusRef]);
Sometimes I have a problem with z-index
of a dropdown, mainly when the dropdown is inside a modal - not all options are visible because the modal is too small.
Instead of trying to fix it using CSS z-index property, Select
component from react-select
provides menuPortalTarget
prop - example value: document.body
Now dropdowns are fully visible
I had to get some results, depending on reference type. I didn’t want to get all, since it would be pretty much. It’s possible with include (or skip) directives.
Passing variables:
Using variables in query:
Initially, gql query/mutation
are displayed as regular strings.
To enable syntax highlighting, autocompletion, and validation for graphql query/mutation definition in VSCode follow few steps.
. .
Open Extensions tab in your VS Code and install GraphQL plugin
. .
Create a new file graphql.config.json
file in your project’s root folder.
{
"name": "Name of your Project",
"schemaPath": "./schema.json",
"extensions": {
"endpoints": {
"Default GraphQL Endpoint": {
"url": "URL_to_your_graphql",
}
}
}
}
. .
Now you should have syntax highlighting, autocompletion, and validation in your graphql files based on your graphql schema
. .
Any problems with the setup? Please check out the plugin documentation
If you’re using Apollo in your project you can try the Apollo GraphQL Extension for VS Code
Very often when working with backend it is useful for frontend application to know what response can we get.
When working with TypeScript we can actually generate types and hooks using schema.graphql
or even “live” backend.
This is where graphql-codegen comes in handy
When you need to create generic components for resources (types) shared across different components you can use typescript plugin
When writing graphql queries, you can also generate type-safe react hooks with typescript-react-apollo plugin. With this plugin you only need to write *.graphql
with desired Query, Mutation, Subscription or Fragment (you don’t need to import fragments in your quries!) and respective hooks will be created.
Example configuration codegen.yaml
file from project:
overwrite: true
schema: 'http://localhost:4000/graphql'
documents:
- 'src/**/queries.ts'
- 'src/**/*.graphql'
generates:
src/__generated__/types.tsx:
plugins:
- 'typescript'
config:
maybeValue: T | undefined
src/__generated__/operations.tsx:
preset: import-types
presetConfig:
typesPath: ./types
plugins:
- typescript-operations
config:
maybeValue: T | undefined
src/__generated__/hooks.tsx:
preset: import-types
presetConfig:
typesPath: ./operations
plugins:
- typescript-react-apollo
config:
maybeValue: T | undefined
As with any performance improvements, it’s important to have the tooling to evaluate if any changes provide value.
In case of React apps setup with CRA it is source-map-explorer non CRA users can use other tools like webpack-bundle-analyzer
browser devtools with network throttling
Decide what’s the lowest network speed that need to work relatively fast. (I have picked fast 3G)
Build the application as for production environment npm run build
and then serve -s build -l 3002
in CRA or open the deployed app.
Throttle the network and force load all resources without using cache CMD+Shift+R. If the application loads relatively fast, there might not be the need to split the code at all.
Analyze generated bundle using source-map-explorer or webpack-bundle-analyzer (non CRA). Spot biggest/rarely used elements. In my case it was:
Tackle the biggest issues and evaluate the results (step 4)
lodash: 'lodash-es',
so they are included only once.
import ReactPlayer from 'react-player/lazy'
lazy loading players for different video hostings.
Details depend on every specific case.
If tackling the biggest elements is not enough, continue splitting the app per route or group of routes accessible for different users. eg. split admin routes, regular user routes and not logged in guests routes.
Unless your application is gigantic, following all steps should be enough. In my case handling initial issues found in step 4 was enough.
Implementing RBAC in React (Role-Based Access Control) means you will need to add logic in multiple components to check if user has the correct role to display part of the UI.
Here is how using HOC’s enables clean code.
The assumption is store user roles are stored in context or redux.
Create withRole
HOC
export default const withRole = (roles) => (Component) => (props) => {
const {userRoles} = useContext(userRoles)
if (userRoles.match(roles)) {
return <Component {props} />
}
return null
}
This HOC can be easily applied on any component.
import Button from "./Button"
import Something from "./Something"
const RestrictedButton = withRole(['admin', 'manager'])(Button)
const RestrictedSomething = withRole(['editor'])(Something)
const SomeComponent = () => {
return (
<div>
<RestrictedButton>Only admin and manager sees me</RestrictedButton>.
<RestrictedSomething someProp={someProp}/>
</div>
)}
It’s useful to create helpers with already applied roles.
export const withAdminRole = withRole(['admin'])
export const withEditorRole = withRole(['editor'])
export const withManagmentRole = withRole(['admin', 'editor'])
and use them like that:
const RestrictedButton = withManagmentRole(Button)
const RestrictedSomething = withEditorRole(Something)
HOC’s should always be created outside of the components, to prevent them being recreated on every render.
const MyComponent = () => {
// VERY BAD
const EditorButton = withRole('editor')(Button)
const AdminButton = withAdminRole(Button)
return (
<>
<EditorButton />
<AdminButton />
</>
)
}
// GOOD
const EditorButton = withRole('editor')(Button)
const AdminButton = withAdminRole(Button)
const MyComponent = () => {
return (
<>
<EditorButton />
<AdminButton />
</>
)
}
To enable syntax highlight in graphql query/mutation definition in IntelliJ IDE’s follow few steps.
Initially gql query is displayed as regular string.
In editor preferences > plugins > marketplace find JS GraphQL plugin
Then you need to configure .graphqlconfig by selecting: new > GraphQL Configuration File in project root file
schemaPath property should point to schema.graphql file in your project
If you configure the application correctly you should see the schema discovered and “No errors found”
Now you should see syntax highlight, suggestions and validation based on graphql schema.
If you have problems with the setup checkout the plugin documentation
With redux devtools disabled on production it’s problematic but possible to monitor dispatched actions and state changes. Here’s how it can be done.
You need to find this code in your Sources.
Even if they are minified, you can search for text 'Reducers may not dispatch actions.'
which is close to the code we are interested in.
Here is how it look in my case.
Now you only need to add logpoint(s) to display action and state.
STATE BEFORE
{
actionType: e.action.type,
action: e.action,
stateBeforeAction: s.computedStates[s.computedStates.length - 1].state
}
STATE AFTER
{
actionType: e.action.type,
action: e.action,
stateAfterAction: s.computedStates[s.computedStates.length - 1].state
}
In your case the code might be minified differently so you might have different letters instead of “e” and “s”.
Now then you click through the app (or reload the page) you should see in the console the feed of actions and state.
When configuring redux store you have to manually enable devtools. Often they are only enabled in development environment.
Redux toolkit by default enables devtools on all environments. It is easy to overlook that and forget disabling devtools on production.
However some say having devtools enabled in production is not an issue.
const store = configureStore({
reducer: rootReducer,
middleware: getDefaultMiddleware(),
// do not forget this
devTools: process.env.NODE_ENV !== 'production',
})
Redux itself is not opinionated about how we design the store structure, but having good structure allows us to write simpler, less bug prone code. Based on redux style guide, state normalization and own experiences I would like to suggest some data shape for single resource. Depending on the specific needs of the project the structure should be adjusted, but it should be sufficient for standard use-cases.
const resource = {
// normalized data for resource
byId: {
id1: {id: "id1", ...rest_of_item1_data},
id2: {id: "id2", ...rest_of_item2_data},
},
// default list of items (key 'list' is just example)
list: {
// ids of users in the list
// ids could be sorted, filtered
// so there is no need to calculate them in selectors
ids: ["userId1", "userId2"],
// status of list operation
// can be idle, loading, loaded, updating, etc.
status: 'idle',
// error data for the whole list operations (when appears)
error: false,
// metadata for the whole list
// loadedAt and state are just example properties
meta: {
loadedAt: 7305798374957,
stale: false,
}
},
// when there is a need to manage another list
someOtherList: {
ids: ["userId4", "userId5"],
status: 'loading',
error: false,
meta: {}
},
// default singular resource (key 'single' is just example)
// for CRUD operations on single resource
single: {
id: 'id1',
status: 'idle',
error: false,
meta: {}
},
// when there is a need to manage another singular resource
anotherSingle: {
id: 'id1',
status: 'idle',
error: false,
meta: {}
}
}
Normally debugger does not stop test execution and allow us to check the application when running jest tests. However we can do it by passing --inspect
flag to node and using chrome devtools for node.
When using CRA add to your package.json scripts:
"test:debug": "react-scripts --inspect test --runInBand --no-cache",
When not using CRA add to your package.json scripts:
"test:debug": node --inspect node_modules/.bin/jest --runInBand
In Chrome open chrome://inspect
and click Open dedicated DevTools for Node
you should see new inspector window open. (do not close it)
Add debugger
keyword in the tested code/component.
Run npm run test:debug
And there it is. In a moment in the open inspector window, you should see test executions stopped at your breakpoint.
You could also use debugger in terminal or your IDE. More info in the article and node docs
With only a few exceptions storing props in state is anti-pattern, because:
export function MyComponent({ dateString }) {
// DON'T
const [timestamp, setTimestamp] = useState(() => new Date(dateString).getTime());
return <div>Some UI do not mind me</div>;
}
if timestamp is not changed by MyComponent, it could be implemented simply by useMemo
export function MyComponent({ dateString }) {
// DO
const timestamp = useMemo(() => new Date(dateString).getTime(), [dateString])
return <div>Some UI do not mind me</div>;
}
if timestamp is changed by MyComponent, maybe parent component should handle it
export function MyComponent({ timestamp, setTimestamp }) {
return <div>Some UI do not mind me</div>;
}
That way we’ll not have duplication of data
If we receive defaults from parent, then they can be safely used by the children in state. That way we could also implement state reset.
export function MyComponent({ defaultTimestamp }) {
// OK
const [timestamp, setTimestamp] = useState(() => defaultTimestamp);
const resetTimestamp = () => setTimestamp(defaultTimestamp)
return <div>Some UI do not mind me</div>;
}
If you have Redux in React project you should not handle data preparation in components. Why? Take a look on example below.
export function MyComponent({ userId, uniqueness }) {
const user = useSelector((state) => getUser(state, userId));
const selectedFilter = useSelector(getFilter);
const selectedSorting = useSelector(getSorting);
// in function components we can use useMemo for optimization
// but in class components we would not be able to optimize this properly
const answers = useMemo(() => {
if (!user) return [];
const uniqAnswers = uniqBy(user.answers, uniqueness);
const filteredAnswers = filter(uniqAnswers, selectedFilter);
return sortBy(filteredAnswers, selectedSorting);
}, [user, selectedFilter, selectedSorting]);
if (!user) return "Loading";
return (
<div>
{user.name}
{answers.map((answer) => (
<div key={answer.id}>{answer.description}</div>
))}
</div>
);
}
If we ever need to display answers, we would need to copy-paste the logic, or extract it to the reusable hook which still would not be the perfect solution.
Wherever possible preparation of data should be handled by the selector. Even if the data comes from different reducers, or component state/props.
That way:
const getUserWithSortedAnswers = createSelector(
getUser,
getFilter,
getSorting,
(_, _2, uniqueness) => uniqueness,
(user, selectedFilter, selectedSorting, uniqueness) => {
if (!user) return [];
const uniqAnswers = uniqBy(user.answers, uniqueness);
const filteredAnswers = filter(uniqAnswers, selectedFilter);
const sortedAnswers = sortBy(filteredAnswers, selectedSorting);
// in this example sorted answers are modified in user object
// but this can be done as separate selector eg. getSortedAnswers
return { ...user, answers: sortedAnswers };
}
);
export function MyComponent({ userId, uniqueness }) {
// selector can receive component data from state or props (uniqueness)
const user = useSelector((state) =>
getUserWithSortedAnswers(state, userId, uniqueness)
);
if (!user) return "Loading";
return (
<div>
{user.name}
{user.answers.map((answer) => (
<div key={answer.id}>{answer.description}</div>
))}
</div>
);
}
If you do not have redux in your React project you can do data preparation in reusable hooks or in wrapper components. As it also provides some reusability and decreases component size.
Component receiving many props might suggest something is wrong.
In this case MyComponent receives many props used only to calculate another values.
function MyComponent({ value, format, isVisible, color, height, width }) {
const displayValue = useMemo(() => {
if (!isVisible) return "";
if (!value) return "";
value.transform(format);
}, [isVisible, value, format]);
return (
<div style={{ color, height, width }}>Some content and {displayValue}</div>
);
}
function MyParentComponent() {
// value, format, isVisible, color, height, width come from state or somewhere else
return (
<div>
<h2>Here will be my component</h2>
<MyComponent
value={value}
format={format}
isVisible={isVisible}
color={color}
height={height}
width={width}
/>
</div>
);
}
In such cases we could simplify the component by calculating the values in parent or additional wrapper component and pass only that.
function MyComponent({ style, displayValue }) {
return <div style={style}>Some content and {displayValue}</div>;
}
function MyParentComponent() {
// value, format, isVisible, color, height, width come from state or somewhere else
const style = useMemo(
() => ({ color, height, width }),
[color, height, width]
);
const displayValue = useMemo(() => {
if (!isVisible) return "";
if (!value) return "";
value.transform(format);
}, [isVisible, value, format]);
return (
<div>
Here will be my component
<MyComponent style={style} displayValue={displayValue} />
</div>
);
}
Big components suggest that there might be too much going on inside. However sometimes it is hard to remove/extract the logic, as it really belongs there. That’s the case often with form components. In such cases we can extract related groups of logic into hooks.
function QuestionForm({ defaultQuestion }) {
const [questionData, setQuestionData] = useState(defaultQuestion);
// ====== stuff related to image refresh ======
const [imageTimer, setImageTimer] = useState({});
useEffect(
function refreshFrontendSignedImages() {
// logic for refreshing
},
[]
);
useEffect(function setupRefreshForInitialImages() {
// logic to refresh setup
}, []);
// ====== stuff related to submitting ======
const dispatch = useDispatch();
const [questionValidationErrors, setQuestionValidationErrors] = useState([]);
const cleanupSaveActions = useCallback(() => {
// cleanup handler logic
}, []);
const onSubmit = useCallback(() => {
// submit handler logic
}, []);
// ====== stuff related to handling changes ======
const handleTitleChange = useCallback(({ target }) => {
// handle title change
}, []);
const handleChoicesUpdate = useCallback((choice, index) => {
// handle choices update
}, []);
const handleTopicsUpdate = useCallback((topicIds) => {
// handle topic update
}, []);
return <form>Some Form UI</form>;
}
After extracting imageRefresh, submitHandlers and changeHandlers logic to hooks we have certain benefits:
function QuestionForm({ defaultQuestion }) {
const [questionData, setQuestionData] = useState(defaultQuestion);
const { setImageTimer } = useSignedImagesRefresh({
setQuestionData,
defaultQuestion,
});
const {
onSubmit,
onSubmitAndCreateAnother,
validationErrors,
} = useQuestionFormSubmitHandlers({ questionData });
const {
handleTitleChange,
handleChoicesUpdate,
handleTopicsUpdate,
} = useQuestionFormChangeHandlers({ setQuestionData });
return <form>Some Form UI</form>;
}
Whenever mapped elements have their own functions, they should be extracted to their own components.
const UsersList = () => {
const [users, setUsers] = useState([])
const fetchUsers = () => { ...fetchingLogic }
const getHeader = (item, index) =>
item.isAdmin
? `${index + 100} :: ${item.superName} :: ${item.rank}`
: `${index} :: ${item.name} :: ${item.age}`
const getImage = (item) =>
item.url ? `${item.url}?token=${item.authToken}` : 'http://default.img.com'
return (
<div>
{list.map((user, index) => (
<div key={user.id}>
<h3>{getHeader(user, index)}</h3>
<p>Some introduction</p>
{getImage(user)}
</div>
))}
</div>
)
}
That way:
const User = ({ user, index }) => {
const getHeader = () =>
user.isAdmin
? `${index + 100} :: ${item.superName} :: ${item.rank}`
: `${index} :: ${user.name} :: ${user.age}`
const getImage = () =>
user.url ? `${user.url}?token=${user.authToken}` : 'http://default.img.com'
return (
<div>
<h3>{getHeader()}</h3>
<p>Some introduction</p>
{getImage()}
</div>
)
}
const UsersList = ({ list }) => {
const [users, setUsers] = useState([])
const fetchUsers = () => { ...fetchingLogic }
return (
<div>
{users.map((user, index) =>
<User user={user} index={index} key={user.id}/>
}
</div>
)
}
Styled-components generate unique hashes for each styled-component you create. They look like .nNUbag
or .JbCpiZ
. From a production perspective, these names are beneficial, since it reduces bundle size, but the development process is a nightmare.
Thankfully, there is a solution: babel-plugin-styled-components
. It changes the class names to use the component name, which will help you in debugging process. The exact pattern is fileName__componentName-hash
, eg. styles__StyledSearchBar-hXxaWb
. Also, you don’t have to worry about production bundle size, cause it works only in development.
If you use create-react-app, the plugin is already set up. The only thing you have to do is update your imports.
import styled from 'styled-components/macro';
Big or often rerendering component which displays list of elements, can be optimized by extracting the list to separate component and using React.memo.
In example below it provided 40x speed improvement. YMMV of course.
In the examples I have provided different stages and mistakes this optimization. They also help understand why are useMemo and useCallback hooks useful.
Install webpack plugin: npm install --save-dev react-dev-tools-iframe-webpack-plugin
Update webpack config to enable the react dev tools iframe:
const ReactDevToolsIFramePlugin = require('react-dev-tools-iframe-webpack-plugin');
plugins: [
// other plugins,
new ReactDevToolsIFramePlugin(),
],
Update cypress plugins to load React & Redux dev tools for chrome. Add the paths to these extensions in a comma-separated string:
module.exports = (on, config) => {
on('before:browser:launch', (browser = {}, args) => {
if (browser.name === 'chrome') {
args.push('--load-extension=/home/work/.config/google-chrome/Default/Extensions/lmhkpmbekcpmknklioeibfkpmmfibljd/2.17.0_0,/home/work/.config/google-chrome/Default/Extensions/fmkadmapgofadopljbjfkapdkoienihi/4.2.1_0');
return args;
}
return null;
});
};
Here’s a post where you can find out where the extensions are.
If after deploying to github pages you can see “Gatsby’s Default Starter” instead of the page you have created, make sure that in
Your GitHub Repository => Settings => GitHub Pages => Source
you have selected gh-pages branch
(instead of master branch
)
If you have created React app using CRA and store environment variables using .env then you have to add REACT_APP_
in front of the variable.
eg. REACT_APP_S3_SECRET='secretkey'
Otherwise, it will not be accessible in the app.
https://medium.com/@danieljameskay/create-react-app-dotenv-281693a19ecd
Note that this feature is available in formik >= 2.0
Usually when you’re creating forms using formik
you access formik props using render-prop pattern
export function MyForm() {
return (
<Formik
initialValues={{ /* something here */ }}
onSubmit={(values, actions) => {/* handler here */}}
>
{(formikProps) => (
<form onSubmit={formikProps.handleSubmit}>
<MyInputComponent value={formikProps.values.name} />
</form>
)}
</Formik>
)
}
Instead you can use formik as context provider (which it is under the hood actually) and use useFormikContext()
hook or FormikConsumer
component to consume provided form state.
Example below:
export function MyFormContainer() {
return (
<Formik
{/* Under the hood Formik uses Context Provider */}
initialValues={{ /* something here */}}
onSubmit={(values, actions) => { /* handler here */ }}
>
{/* You do not have to use function here! */}
{/* However you can, if you would like to :) */}
<MyForm />
</Formik>
)
}
export function MyForm() {
const { handleSubmit, values } = useFormikContext(); // formikProps
return (
<form onSubmit={handleSubmit}>
<MyInputComponent value={values.name} />
</form>
);
}
This allows us to use formik props deep down in our nested components
without drilling down formikProps
.
Let’s imagine a following situation: we need to create a new npm package with create-react-app, in order to bundle some files in a package to have them available in a project (it’s possible to use create-component-lib
for this task). The aforementioned sometimes requires from us to update our babel config.
In my latest task I had a situation where my babel config contained invalid (for a situation) presets:
module.exports =
{
"presets": ["@babel/preset-env", ["react-app", { "absoluteRuntime": false }]]
}
In the above example, the build process creates a transpiled code, which contains babel runtime for minimalizing package size:
var _interopRequireWildcard = require("@babel/runtime/helpers/interopRequireWildcard");
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = void 0;
var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/esm/defineProperty"));
var _objectSpread4 = _interopRequireDefault(require("@babel/runtime/helpers/esm/objectSpread"));
var _classCallCheck2 = _interopRequireDefault(require("@babel/runtime/helpers/esm/classCallCheck"));
var _createClass2 = _interopRequireDefault(require("@babel/runtime/helpers/esm/createClass"));
var _possibleConstructorReturn2 = _interopRequireDefault(require("@babel/runtime/helpers/esm/possibleConstructorReturn"));
var _getPrototypeOf3 = _interopRequireDefault(require("@babel/runtime/helpers/esm/getPrototypeOf"));
var _inherits2 = _interopRequireDefault(require("@babel/runtime/helpers/esm/inherits"));
Now when we start our test which uses a transpiled component it’s possible we receive error code like this:
Jest encountered an unexpected token
By default, if Jest sees a Babel config, it will use that to transform your files, ignoring "node_modules".
Here's what you can do:
• To have some of your "node_modules" files transformed, you can specify a custom "transformIgnorePatterns" in your config.
• If you need a custom transformation to specify a "transform" option in your config.
• If you simply want to mock your non-JS modules (e.g. binary assets) you can stub them out with the "moduleNameMapper" config option.
You'll find more details and examples of these config options in the docs:
https://jestjs.io/docs/en/configuration.html
Details:
/Users/mobile32/projects/formik-generator/node_modules/@babel/runtime/helpers/esm/defineProperty.js:1
({"Object.<anonymous>":function(module,exports,require,__dirname,__filename,global,jest){export default function _defineProperty(obj, key, value) {
^^^^^^
SyntaxError: Unexpected token export
10 | exports.default = void 0;
11 |
> 12 | var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/esm/defineProperty"));
| ^
at ScriptTransformer._transformAndBuildScript (node_modules/jest-runtime/build/script_transformer.js:403:17)
at Object.require (dist/FormGenerator.js:12:47)
This happens because by default jest ignores transformations for everything in node_modules
directory.
Now we have two possibilities to resolve this problem. In the first scenario we can add transformIgnorePatterns
for our jest config to transpile babel runtime module:
transformIgnorePatterns: [
'/node_modules/(?!@babel\/runtime)'
],
In the above example transpiled code still will be smaller in application runtime but it doesn’t crash our tests.
The second option is using another babel preset for proper transpilation:
module.exports = {
plugins: [
'@babel/plugin-proposal-class-properties',
],
presets: [
'@babel/preset-env',
'@babel/preset-react',
],
}
Now after build, we get transpiled code working in a test environment, however, our code will be bloated with helpers required to mimic transpiled features (for example class inheritance):
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = void 0;
var _react = _interopRequireWildcard(require("react"));
var _propTypes = _interopRequireDefault(require("prop-types"));
var _formik = require("formik");
var _yup = require("yup");
var _classnames = _interopRequireDefault(require("classnames"));
var _moment = _interopRequireDefault(require("moment"));
var _BooleanField = _interopRequireDefault(require("./FieldTypes/BooleanField"));
var _EnumField = _interopRequireDefault(require("./FieldTypes/EnumField"));
var _MoneyField = _interopRequireDefault(require("./FieldTypes/MoneyField"));
var _TextField = _interopRequireDefault(require("./FieldTypes/TextField"));
var _QuarterDateField = _interopRequireDefault(require("./FieldTypes/QuarterDateField"));
On one of our projects we were able to reduce main bundle size from ~1080kB to ~820kB only by lazy loading one library (recharts).
in router file
import React, {Component, lazy, Suspense} from 'react'
//other imports
const StatisticsComponent = lazy(() => import('./path/to/component'))
export class Root extends Component {
render(){
return(
<Router history={history}>
<Switch>
// other routes
<Suspense fallback={<div>Loading...</div>}>
<Route exact component={StatisticsComponent} path='/statistics' />
</Suspense>
</Switch>
</Router >
)
}
}
If you’re using react-datepicker
, and the last time you’ve tested your date-picker was in winter time or sooner, please check if your date-picker still works properly.
<DatePicker
dateFormat="DD/MM/YYYY"
onChange={val => this.setValue(input, val)}
selected={input.value ? moment(input.value) : null}
/>
Seems pretty basic, right?
Add
utcOffset={0}
to props in your react-date-picker.
<DatePicker
dateFormat="DD/MM/YYYY"
onChange={val => this.setValue(input, val)}
selected={input.value ? moment(input.value) : null}
utcOffset={0}
/>
You can read more about this issue Here.