How many times did you struggle to pick up the appropriated technical stack for your front-end project? How hard when we structure the web skeleton for the very first in the project? How many nights could you not get to sleep well? If you like me, then this article is just for you.
Nowadays, the web development is emerging really fast. We feel like if we don't learn new thing then we become a caveman from that day. A lot of technologies out there is for web development, but in this article, I would only like to focus on the React Ecosystem. Because I like React library and its ecosystem, I spent a lot of time to research and work on it for a long time.
The previous article from this series can be found at How to Organize CLEAN ARCHITECTURE to Modular Patterns in 10 Minutes
So let's get started.
Dependencies (framework and technologies)
- JavaScriptServices (React/Redux template)
- CoreUI (with ReactStrap)
- TypeScript
- WebPack
- Yarn
- Sass
- Others: react-router, react-table, redux-form, redux-observable…
SSR and CSR
SSR stands for Server Side Rendering, and CSR for Client Side Rendering. Check out the article at The Benefits of Server Side Rendering Over Client Side Rendering for a full explanation about it. I just mention a bit because I know somebody will wonder why do I use JavaScriptServices packages to make the skeleton for this article. Apparently, I am focusing in .NET Core in this time, and I have a look around other technologies like NodeJS, Java, PHP, Python, you name it, and found out that they also have a way to make the server rendering the scripts and related things like style, HTML… So in this time, I will tell you how can we do it using JavaScriptServices from the .NET world.
Some of the caveats in this approach are:
- It makes the development more complex because now we need to have a look at the server console startup as well, some of the cases make the debugging more complicated.
- It makes the first rendering a little bit slower if you dump a lot of tasks upfront.
The benefits of this approach as
- It makes SEO better and more consistency
- End-users shall feel like the content comes up very fast because it was composed upfront on the server side already (in case we cache the output content it makes more sense)
To be honest, if you want to build a site that very heavy to SEO related, then you should consider it. Someone asked me the question that if they build the Web Application just for in-house users used, so could they also need to consider to build the web app with SSR approach? In this case, I would like to recommend that they should go with CSR approach for a bit naturally in term of the front-end development. But again it is up to you, I let you choose it.
Stay tuned for some simple steps that make the project with SSR approach in Visual Studio 2017. Firstly, you need to choose to create a new solution for the following image:
Secondly, it will take you to the following screen:
Final step, we have the project structure as
Simple. Isn't it? This is just for any .NET developer with a basic level :) Let rolling out to other following excited things
Why use TypeScript?
In this section, I just want to make clear that the reason I use TypeScript not only because its benefits, but also my team is familiar with it. Let first analyzing a bit about the pros of it:
- Don't care about a lot of ES6, ES7, ES8… releases anymore
- Don't want to have some of the naive exceptions in the production related to type constraints
- A large code-based that a big team to works in, then you need to have a way to avoid common bugs (type system bugs)
- A back-end team that is familiar with C#, then we will be benefits of usage TypeScript (a lot of concepts from the statically-typed language like class, interface…)
Beside of that, we also struggle many nights to fighting with typing system, like in this article when I work with react-table, I try to pull the @types/react-table package from NPM, but a lot of conflicts and incompatible with the newest react-table package. Then I just recognized that actually, that typing library has not owned to react-table's author (other guys maintain it). Then I decide to go with any variable declaration when using this library.
I don't want to compare between TypeScript and Flow because of a lot of biases here. One more reason because I am working on TypeScript then it will be unfair if I talk about it. A lot of people do comparisons out there. Just googling it.
Work with un-typing library in TypeScript (react-table)
Actually, when working on the project, we have found that sometimes we need to use the un-typing library (react-table in this case) so that we need to declare it with ES 5.1 style. Let's deep dive into how we make the react-table works well with others. We need to put following code into the code:
const ReactTable: any = require('react-table').default;
Now, we can use ReactTable instance in anywhere inside this file. Let says we process and bind the data for the table as
const table = (
<ReactTable
columns={columns}
manual
data={this.props.blogs}
className="-striped -highlight"
defaultPageSize={10}
showPageSizeOptions={false}
filterable
defaultSorted={[
{
id: 'title',
desc: true
}
]}
pages={this.props.totalPages}
loading={this.props.loading}
onFetchData={this.fetchData}
getTdProps={(state: any, rowInfo: any, column: any, instance: any) => {
return {
onClick: (event: any, handleOriginal: any) => {
this.props.history.replace(`/admin/blog/${rowInfo.original.id}`);
if (handleOriginal) {
handleOriginal();
}
}
};
}}
previousText={<i className="icon-arrow-left" />}
nextText={<i className="icon-arrow-right" />}
/>
);
As you can see the code above, several places declare with any type. That's fair enough. Because now we will handle types by our-self. This is simple but the time when I started coding, I got a lot of troubles to find a way to make the un-typing library work well with others. Hope this tip will help someone like me get rid of the trouble in the upfront phase.
Duck typing
"If it walks like a duck and it quacks like a duck, then it must be a duck." — Wiki
For the sake of well-organized code, I use duck typing for bundling reducers, action types and actions in the web application. To my experience, we need to handle the {actionTypes, actions, reducer} over and over again in our redux code. With duck typing proposed by Erik Rasmussen, we can avoid a lot of messy code (functional code), and turn them into modules (business code), it makes more sense in term of bug fixing in the future as well. Let imagine that you have a bug, and you need to find out the place that caused it, then you just need to go to one place (specific module in this case) to read the code and debug it.
Let see how can I organize the code for it in the following image:
- Components folder contains all dump components
- Configs folder contains only configuration info, in this case, it exports the literal object to outside
- Containers folder contains all smart components
- redux folder contains modules folder which has several duck typing here and there middleware folder which manage redux side-effects
- utils folder contains some of the utility pieces of code that helping out for some of the complicated work, normally it is a pure function, then it can be easy to make a test
- boot-client.tsx is a boot client file (because we follow up with the JavaScriptServices)
- boot-server.tsx is a boot server file (doing some works like prepare routers, store… once again because we use JavaScriptServices)
- routes.tsx contains all the possible routes can have in the system
In any redux application, we need having action, action creator, store, side-effect and so on. Subsequently, I will so you how can I do it with duck typing.
First of all, we add the type of structure we want to use. Let say Blog type as below
export type Blog = {
id: string;
title: string;
description?: string;
theme?: number;
image?: string;
postsPerPage: number;
daysToComment: number;
moderateComments: boolean;
};
Then, we add BlogState interface
export interface BlogState {
loading: boolean;
loaded: boolean;
addLoading: boolean;
ids: any;
blogByIds: any;
blogSelected: Blog | null;
themes: Theme[];
error: any;
page: number;
totalPages: number;
}
Forgive me for some of any type declaration in here :(
We need to declare several action types as
// Load blogs
const LOAD_BLOGS = 'LOAD_BLOGS';
const LOAD_BLOGS_SUCCESSED = 'LOAD_BLOGS_SUCCESSED';
const LOAD_BLOGS_FAILED = 'LOAD_BLOGS_FAILED';
// The rest of the source code in my GitHub
And, action for each of them
interface LoadBlogAction extends Action {
type: typeof LOAD_BLOGS;
page: number;
}
interface LoadBlogSuccessedAction extends Action {
type: typeof LOAD_BLOGS_SUCCESSED;
items: any;
totalPages: number;
page: number;
}
interface LoadBlogFailedAction {
type: typeof LOAD_BLOGS_FAILED;
error: any;
}
// The rest of the source code in my GitHub
...
Then, we define the KnownAction for the reducer
export type KnownAction =
| LoadBlogAction
| LoadBlogSuccessedAction
| LoadBlogFailedAction
// The rest of the source code in my GitHub
;
Some of the epics (redux-observable in this case) to handle the side-effect
export const blogEpics: any = [
(action$: ActionsObservable<LoadBlogAction>): Observable<Action> => {
return action$
.ofType(LOAD_BLOGS)
.debounceTime(100)
.switchMap(action =>
blogRequest
.loadBlogs(action.page)
.map(result => {
return actionCreators.loadBlogsByPageSuccessed(result.response);
})
.catch(error =>
Observable.of(actionCreators.loadBlogsByPageFailed(error))
)
);
}
// The rest of the source code in my GitHub.
];
I don't gonna explain what I do with redux-observable in this article. Check out Using redux-observable to handle asynchronous logic in Redux for more information you need.
We define Action Creator for them as the following:
export const actionCreators = {
loadBlogsByPage: (page: number): Action =>
<LoadBlogAction>{ type: LOAD_BLOGS, page },
loadBlogsByPageSuccessed: (data: any) =>
<LoadBlogSuccessedAction>{ type: LOAD_BLOGS_SUCCESSED, ...data },
loadBlogsByPageFailed: (error: any) =>
<LoadBlogFailedAction>{ type: LOAD_BLOGS_FAILED, error },
// The rest of the source code in my GitHub
};
Final step, we define the following code for the reducer
export const reducer: Reducer<BlogState> = (
state: BlogState,
action: KnownAction
) => {
switch (action.type) {
case LOAD_BLOGS:
return {
...state,
loading: true
};
case LOAD_BLOGS_SUCCESSED:
console.log(action);
return {
...state,
ids: action.items.map((blog: Blog) => blog.id),
blogByIds: action.items.reduce((obj: any, blog: Blog) => {
obj[blog.id] = blog;
return obj;
}, {}),
loaded: true,
loading: false,
page: action.page || 0,
totalPages: action.totalPages,
blogSelected: null
};
case LOAD_BLOGS_FAILED:
return {
...state,
ids: [],
blogByIds: {},
error: action.error,
loaded: true,
loading: false,
page: 0,
blogSelected: null
};
// The rest of the source code in my GitHub
default:
const exhaustiveCheck: never = action;
if (typeof exhaustiveCheck != 'undefined') break;
}
return (
state || {
loading: true,
loaded: false,
redirectTo: '/',
ids: [],
blogByIds: [],
blogSelected: null,
themes: [
{
value: 1,
label: 'Default'
}
],
error: null,
page: 0,
totalPages: 0
}
);
};
That's it. And it should be in one file (blog.ts).
Sometimes, I feel that this file is too long. Do you have any suggestion for organizing effectively? Feel free to comments at the end, I am happy to discuss it.
A few dot or too many dots
Have you ever faced the following issue with your code?
import { ComponentA, ComponentB, ComponentC, ComponentE} from '../../../../components';
It turns your code into messy and bloated structure. If you feel annoying on this issue, I will help you do it better.
First step, putting index.js file in each root folder structure to gather related stuff as the image following:
And the code inside it in this case is
export { default as Aside } from './Aside/Aside';
export { default as Breadcrumb } from './Breadcrumb/Breadcrumb';
export { default as Footer } from './Footer/Footer';
export { default as Header } from './Header/Header';
export { default as Sidebar } from './Sidebar/Sidebar';
export { renderTextBoxField as TextBoxField, renderNumberField as NumberField, renderSingleSelectField as SingleSelectField } from './Form/Input';
export { default as CheckboxField } from './Form/Checkbox';
export { confirmationBox as ConfirmationBox } from './Confirmation/ConfirmationBox';
The second step, we need to let Webpack config knows about it as well
resolve: {
extensions: [".js", ".jsx", ".ts", ".tsx"],
alias: {
OurComponents: path.resolve(__dirname, "./ClientApp/components/"),
OurModules: path.resolve(__dirname, "./ClientApp/redux/modules/"),
OurConfigs: path.resolve(__dirname, "./ClientApp/configs/"),
OurUtils: path.resolve(__dirname, "./ClientApp/utils/")
}
}
Now Webpack will do aliases for your code into simple one, but we also need to let TypeScript interpreter knows it :). In the tsconfig.json file put following code as
"paths": {
"OurComponents": ["./ClientApp/components"],
"OurComponents/*": ["./ClientApp/components/*"],
"OurModules": ["./ClientApp/redux/modules"],
"OurModules/*": ["./ClientApp/redux/modules/*"],
"OurConfigs": ["./ClientApp/configs"],
"OurConfigs/*": ["./ClientApp/configs/*"],
"OurUtils": ["./ClientApp/utils"],
"OurUtils/*": ["./ClientApp/utils/*"]
}
That's it. Let look at how easy we can refer to our component now
import { Breadcrumb, Sidebar, Header, Footer } from 'OurComponents';
I really like this approach. How about you?
Recap
In this article, we walk through to several ways to organize the code more effectively. We analyze some of the reason why do we choose the SSR approach, then the reason we like TypeScript for code typing. Subsequently, we discuss Duck typing in a redux way. Finally, we give you a tip to avoid too many dots when you manipulate the file structure in code. Furthermore, we actually want to share more and more, but we want to let you suggest it. I know I have a lot of things to learn so that feel free to comment as below. I am really eager to hear from you.
Resources
The source code for this article can be found at my GitHub
Additional Readings
- https://medium.com/walmartlabs/the-benefits-of-server-side-rendering-over-client-side-rendering-5d07ff2cefe8
- https://medium.com/@dan_abramov/smart-and-dumb-components-7ca2f9a7c7d0
- https://github.com/erikras/ducks-modular-redux
- https://hackernoon.com/the-100-correct-way-to-structure-a-react-app-or-why-theres-no-such-thing-3ede534ef1ed
- https://medium.com/dailyjs/using-redux-observable-to-handle-asynchronous-logic-in-redux-d49194742522
- https://hackernoon.com/applying-clean-architecture-on-web-application-with-modular-pattern-7b11f1b89011