Welcome to Jungsoft's Frontend Style Guide. Sit down, have a cup of coffee and read this calmly. β
The goal of this guide is to help our team to understand and follow code style best practices, maintaining a pattern.
As this guide is an extension of the Airbnb JavaScript Style Guide, we highly recommend reading it before you continue.
All components should be defined within a directory. The main component file should be index.tsx
LeadsList/
βββ index.tsx
If it has some logic that would lead to a separation of concerns between presentational and container components, the structure should be as following:
LeadsList/
βββ index.tsx
βββ LeadsListContent.tsx
The source code of a project should be separated with at least these following essential directories:
awesome-project/
βββ src/
βββ components/ # Should store the components that are used in many views
βββ views/ # Should store the application views
βββ contexts/ # Should store the context API's
βββ router/ # Should store the routes configurations
βββ utils/ # Should store the utility functions used to perform atomic logics
βββ translations/ # Should store the JSON files containing the text translations used in the project
βββ settings/ # Should store the configuration files of libs
βββ yup
βββ schemas # Should store the validation schemas of an entity
βββ constants/ # Should store the constants related to business rules, shared values, etc.
Most of the components will be function-based components. The choice between presentation and container implementations depends of the scenario.
A directory called graphql
should be created inside the component/view directory in order to store the query/mutation files.
awesome-project/
βββ src/
βββ components/
βββ Leads
βββ graphql
βββ listLeadsQuery.ts
βββ updateLeadsMutation.ts
β Bad
const onlyNumbersRegex = /^\d+$/
const validateNumber = message => value => !onlyNumbersRegex.test(value) && message
validateNumber('error message')(1)
β Good
const onlyNumbersRegex = /^\d+$/
const getNumberValidation = message => value => !onlyNumbersRegex.test(value) && message
const isNumber = getNumberValidation('error message')
isNumber(1)
β Bad
setTimeout(doSomething, 86400000)
β Good
const DAY_IN_MILLISECONDS = 86400000
setTimeout(doSomething, DAY_IN_MILLISECONDS)
β Bad
// TODO: Extract this to a component.
{...}
β Good
// TODO: Support recording voice messages in chat.
{...}
β Bad
const handleFilePreview = useCallback((event) => {
const file = Array.from(event.target.files)[0];
// eslint-disable-next-line no-param-reassign
event.target.value = "";
β Good
const handleFilePreview = useCallback((event) => {
const file = Array.from(event.target.files)[0];
// Refer to issue https://github.com/ngokevin/react-file-reader-input/issues/11#issuecomment-363484861
// eslint-disable-next-line no-param-reassign
event.target.value = "";
It can be applied to components, handlers or utility functions.
β Bad
const truncateEventName = (
eventName,
maxLength = SHORT_EVENT_NAME_LENGTH,
) => (
β Good
/**
* Truncates one event name to the max length.
* @param {*} eventName The event name
* @param {*} maxLength Max text length
*/
const truncateEventName = (
eventName,
maxLength = SHORT_EVENT_NAME_LENGTH,
) => (
β Bad
const chatEventsStatuses = {
CURRENT_USER_LEFT,
CURRENT_USER_WAS_REMOVED,
CURRENT_USER_REMOVED_MEMBER,
CURRENT_USER_WAS_ADDED,
β Good
/**
* The chat event statuses according to the
* current user and the chat event member
*/
const chatEventsStatuses = {
CURRENT_USER_LEFT,
CURRENT_USER_WAS_REMOVED,
CURRENT_USER_REMOVED_MEMBER,
CURRENT_USER_WAS_ADDED,
β Bad
const handleClick = (item) => {
{...}
};
<Button onClick={() => handleClick(item)}>
β Good
const handleClick = (item) => () => {
{...}
};
<Button onClick={handleClick(item)}>
β Bad
useEffect(() => {
if (file) {
processFile(file)
.then(({ result }) => {
setFileSource(result);
})
.catch(() => {
notify.danger(t("errors.an_error_occurred"));
});
}
}, [setFileSource, file, fileSource, isVideo, notify, t]);
β Good
useEffect(() => {
if (file) {
processFile(file)
.then(({ result }) => {
setFileSource(result);
})
.catch(() => {
notify.danger(t("errors.an_error_occurred"));
});
}
}, [
setFileSource,
file,
fileSource,
isVideo,
notify,
t,
]);
β Bad
if ((!selectedProduct && !selectedLeasingOffer) || !individualOffer) {
return;
}
β Good
const hasSelectedOffer = !!(
selectedProduct
|| selectedLeasingOffer
);
if (!hasSelectedOffer && !individualOffer) {
return;
}
β Bad
const [currentUser] = useCurrentUser();
const notify = useSnackbar();
const [t] = useTranslation();
const [sendRiskAssessment] = useMutation(SEND_RISK_ASSESSMENT_MUTATION);
const {
loading: healthCentersLoading,
error: healthCentersError,
data: healthCentersData,
refetch: healthCentersRefetch,
} = useSafeQuery(GET_LIST_HEALTH_CENTER_QUERY);
β Good
const [currentUser] = useCurrentUser();
const [t] = useTranslation();
const notify = useSnackbar();
const [sendRiskAssessment] = useMutation(SEND_RISK_ASSESSMENT_MUTATION);
const {
loading: healthCentersLoading,
error: healthCentersError,
data: healthCentersData,
refetch: healthCentersRefetch,
} = useSafeQuery(GET_LIST_HEALTH_CENTER_QUERY);
β Bad
const {
values: {
answers,
},
} = formikForm;
β Good
const answers = formikForm?.values?.answers;
β Bad
{!isEnd && (
<Button
className={scheduleADemoButtonClasses}
onClick={handleNavigateToEnd}
>
<h4>
Schedule a Demo
</h4>
</Button>
)}
β Good
{
!isEnd && (
<Button
className={scheduleADemoButtonClasses}
onClick={handleNavigateToEnd}
>
<h4>
Schedule a Demo
</h4>
</Button>
)
}
The function should trigger the termination as soon as possible in case of divergence with its purpose.
β Bad
function someFunction(someCondition) {
if (someCondition) {
// Do something
}
}
β Good
function someFunction(someCondition) {
if (!someCondition) {
return
}
// Do something
}
It's important to use variables in order to increase the reusability of styles and also to be consistent.
For instance:
// COLORS
$white: #fff;
$gray: #868686;
$light-gray: #E5E9ED;
$medium-gray: #D9D9D9;
$dark-gray: #5D576B;
$facebook-blue: #3B5998;
// BREAKPOINTS
$screen-xs: 400px;
$screen-sm: 575px;
$screen-md: 767px;
$screen-lg: 991px;
$screen-xl: 1199px;
$aspect-ratio: 1.78;
.media-container {
right: calc(#{$video-width} * #{$video-position-ratio});
width: $video-width;
height: calc(#{$video-width} / #{$aspect-ratio});
}
When applying margin, padding (spaces in general) or lengths, try as much as possible to use the classes from the design system. If there's no class that applies to the case, then extract it to a variable in order to be more verbose.
β Bad
.media-container {
margin-bottom: 25px;
}
β Good
<div className="mb-2 media-container">{...}</div>
β Bad
.media-container {
width: 250px;
}
β Good
// COMPONENTS LENGTHS
$media-container-width: 250px;
.media-container {
width: $media-container-width;
}