22
loading...
This website collects cookies to deliver better user experience
src
directory; conversely, there are no directories such as components
or utils
in the root directory.pages
in the root directory, so putting them under src
is the intentional directory structure of this repository.docs
), CI settings such as GitHub Actions (.github
), and Docker settings (docker
) if the application is container-based. Therefore, if we put components
directly at the root level, the source code of the application and the non-components will be mixed in the same hierarchy.src
when writing CI settings, for example, to make it easier to specify the scope of application.features
directory.src
|-- assets
+-- assets # assets folder can contain all the static files such as images, fonts, etc.
*(omitted)*
+-- features # feature based modules ← here
*(omitted)*
+-- utils # shared utility functions
features
is directories with the name of each feature that the application has. For example, for a social networking service, this might be posts
, comments
, directMessages
, and so on.src/features/awesome-feature
|
+-- api # exported API request declarations and api hooks related to a specific feature
|
+-- components # components scoped to a specific feature
*(omitted)*
+-- index.ts # entry point for the feature, it should serve as the public API of the given feature and exports everything that should be used outside the feature
components
, hooks
, types
, etc. under src
, and then finally create a directory for each function in each directory.app/Domain
for backend implementations, and then create a directory for each feature, such as app/Domain/Auth
or app/Domain/HogeSearch
. So it made a lot of sense to manage the front-end with the same idea.features
directory, you can manage components, APIs, Hooks, etc. by feature. In other words, if you have an API for each feature, you can cut the directory for the API, and if you don't, you don't have to.features
.features/HOGE
as in this repository, it is possible to prioritize development speed with a fat design in the initial release, and impose strict constraints in the second and subsequent releases.features
or not, based on whether it will disappear with the feature when the feature is obsolete.'no-restricted-imports': [
'error',
{
patterns: ['@/features/*/*'],
},
],
src/components
.src/components/Elements/Button/Button.tsx
App.tsx
, and the number of lines gets bloated, but I found it very clever that this repository has separate providers
and routes
directories. App.tsx
are very simple. I would like to copy this.import { AppProvider } from '@/providers/app';
import { AppRoutes } from '@/routes';
function App() {
return (
<AppProvider>
<AppRoutes />
</AppProvider>
);
}
export default App;
<Outlet>
can be used to carve out routing into a separate object.export const protectedRoutes = [
{
path: '/app',
element: <App />,
children: [
{ path: '/discussions/*', element: <DiscussionsRoutes /> },
{ path: '/users', element: <Users /> },
{ path: '/profile', element: <Profile /> },
{ path: '/', element: <Dashboard /> },
{ path: '*', element: <Navigate to="." /> },
],
},
];
features
.model
in this article is similar to the features
in this repository. The general idea is to put all the .tsx
files under components
, which is well known from the default structure of Nuxt.js, so creating a directory components/models
and putting components for each feature under it is also a good idea. <Link>
of react-router-dom, as shown below, I can increase the possibility of limiting the scope of influence when destructive changes are made to that component in the future. If you are importing external libraries directly from a number of components, you will be affected, but if you have in-house modules in between, you will have a better chance of limiting the impact.import clsx from 'clsx';
import { Link as RouterLink, LinkProps } from 'react-router-dom';
export const Link = ({ className, children, ...props }: LinkProps) => {
return (
<RouterLink className={clsx('text-indigo-600 hover:text-indigo-900', className)} {...props}>
{children}
</RouterLink>
);
};
Chakra
with emotion
is the best choice. I also think that Chakra is currently the best component library, and MUI
is the next best, so I rather agree with the statement :)react-hook-form
(RHF). I personally recommend it.FieldWrapper
. The idea is to implement a form component by putting <input>
etc. in the FieldWrapper
.import clsx from 'clsx';
import * as React from 'react';
import { FieldError } from 'react-hook-form';
type FieldWrapperProps = {
label?: string;
className?: string;
children: React.ReactNode;
error?: FieldError | undefined;
description?: string;
};
export type FieldWrapperPassThroughProps = Omit<FieldWrapperProps, 'className' | 'children'>;
export const FieldWrapper = (props: FieldWrapperProps) => {
const { label, className, error, children } = props;
return (
<div>
<label className={clsx('block text-sm font-medium text-gray-700', className)}>
{label}
<div className="mt-1">{children}</div>
</label>
{error?.message && (
<div role="alert" aria-label={error.message} className="text-sm font-semibold text-red-500">
{error.message}
</div>
)}
</div>
);
};
useController
.
registration={register('email')}
.Form.tsx
to make this type-safe.unknown
in the form of extends T<unknown>
such as TFormValues extends Record<string, unknown> = Record<string, unknown>
is a typedef tip that I often use for puzzles.react-error-boundary
is useful.AppProvider.tsx
as mentioned above.<ErrorBoundary FallbackComponent={ErrorFallback}>
<Router>{children}</Router>
</ErrorBoundary>.
<Button className="mt-4" onClick={() => window.location.assign(window.location.origin)}>
Refresh
</Button>.
window.location.assign(window.location.origin)
is doing here is transitioning to the top page because it is transitioning to the origin. When I saw this, I thought that I should just write location.reload()
, but I realized that if I want to put a button on the top page, it would be more appropriate to go back to the top page, because it will keep dropping infinitely when an error occurs due to Invalid query parameter or page.location.href =
to get the same behavior, but assign has the subtle advantage that it is a method call and therefore easier to write tests for, so assign is slightly preferable.location.replace()
, which doesn't leave the error in the history, because it seems to be more subtle if you want to return to the page where the error occurred. However, I wonder if that would result in unexpected behavior.docs
in the repository for details.generators
directory.Scaffdog
, which can be written in markdown.
test/test-utils.ts
as a corruption prevention layerReact.lazy
can only be used for Default Export, but I heard that it can be used for named export. I didn't know that (or I never thought to do something about it).import/order
because I thought it would be too radical, but now that I've seen it set up, it does seem to be easier to read...ReactNode
is safe to use.
ReactNode
for all React element props, but I was wondering if I need to be more strict since ReactNode
can be classified into more detailed types. I was wondering if I should do that.ReactNode
is fine for most cases.react-helmet
is almost out of maintenance, and react-helmet-async
should be better, so I published a pull request (https://github.com/alan2207/bulletproof-react/pull/45 )