Monday, December 19, 2022

React-Redux Application Coding Guidelines


 


In this post we will list some of the coding best practices for a react/redux application development. These thumb rules prevents a great pain during the actual application initial development, and a greater pain during the later stages of the application maintenance. 


create-react-app

Use the create-react-app and redux template to initialize your new application. See example in the post: Create Redux App with ASync Ajax Calls, which also includes a requests wrapper, and error handling.

When using create-react-app, the application build is super simplified. This reduces the pain involved in a JavaScript application build, and it is a real pain, trust me. In some cases, an issue that you encounter might require your to eject. Trying avoiding this as long as you can, even at a price of ugly bypass, as the alternative is for sure uglier.

Dockerize

Use two stages docker build to shorted the application build time. An example for a docker file is:

FROM node:16.3 as builder

WORKDIR /app
COPY src/package.json ./
COPY src/package-lock.json ./
RUN npm install

COPY src/ ./
RUN npm run build


FROM nginx:1.21.5
COPY --from=builder /app/build /gui
CMD ["nginx", "-g", "daemon off;"]

Flat Components

Keep all components as a flat folders list under the src folder.



This might seems counter intuitive at first sight, but in later stage of the application maintenance, when you often refactor components due to requirements changes, you are saved from trying to understand where to import your component from. 

Should I use:
import '../componentA/component.js'

Or:
import '../componentA/componentB/component.js'

Or:
import '../../componentA/componentB/component.js'

In addition, finding you component is much simpler when the entire list is flat.


Component Files

Each component folder should contain at the most 3 files:
  • component.js
  • component.module.css
  • slice.js

For example, a super button component will have the following directories and files:
~/src/super-component/component.js
~/src/super-component/component.module.css
~/src/super-component/slice.js

This is a good standard, that all component look the same, and it is also a great time saver for refactors.

component.js Visualization

Use only visualization related code in the component. Any logic related code should be moved to the slice.js. 

For example, displaying a list of items that includes a search by item name text box (assuming the search is client side). The component.js should never do the filter action. The slice.js should handle the filtering and produce a new filter-result list that will be used in the component.js.

component.js Indentation

Handling GUI and css can be a real pain sometimes, hence we should strive to keep the component.js simple. Do not overload it with multiple levels of components, but instead keep only a flat list of items in the result of the component.

For example, this simple table component:


return (
<div className={styles.root}>
<div>My Table</div>

<div>
<div className={styles.tableHead}>
<div className={styles.tableHeadColumn}>
Header1
</div>
<div className={styles.tableHeadColumn}>
Header2
</div>
</div>
<div className={styles.tableRow}>
<div className={styles.tableValue}>
Value 1
</div>
<div className={styles.tableValue}>
Value 2
</div>
</div>
</div>
</div>
)

is terrible!

It should be a flat list of items:


return (
<div className={styles.root}>
<div>My Table</div>
<TableHeader/>
<TableRows/>
</div>
)

So we should break the complex table component into multiple small components.

Flex

CSS flex is a great method to position and stretch the components, but we should keep it neat and clean to make stuff work. When using flex, make sure to avoid a case when a parent flex CSS directive is handled in one component, and affects or combines with a flex CSS directive in another component.

The following guideline usually applies to flex usage:


return (
<div className={styles.parent}>
<div className={styles.nonStretchedChild}>
<ChildComponent1/>
</div>
<div className={styles.stretchedChild}>
<ChildComponent2/>
</div>
</div>
)


and the styles are:

.parent {
width: 100%;
display: flex;
flex-direction: row;
}

.nonStretchedChild {
width: 100px
}

.stretchedChild {
flex: 1
}


slice.js Scope


Keep the slice.js small as possible, while it handles only the related component logic. When we have a slice which is very large, that's usually a sign that you need to break the component to multiple child components.

Thunk

When we split our logic to multiple slices, we probably have some (but not many) actions that require access to a state which is distributed among multiple slices. To access multiple slices, we can use the Thunk API. We can also use thunk API for async request/response handling for example:


export const graphClick = createAsyncThunk(
'graph/clickGraph',
async (input, thunkApi) => {
const {graphId} = input
const state = thunkApi.getState()
const lastXLocation = state.graph.lastXLocation[graphId]

const body = {
x: lastXLocation,
}

thunkApi.dispatch(setLoading(true))
const {ok, response} = await sendRequest('/get-my-data', body)
thunkApi.dispatch(setLoading(false))

if (!ok) {
thunkApi.dispatch(addNotification(true, response))
return
}

thunkApi.dispatch(setBuckets(response))
},
)


Final Note

In this document we have listed some coding best practices. Looking on this list, a first impression might be that this is a "too much overhead" for an application, but trust me, it is not. Once starting to use these guidelines, coding, maintenance, refactoring, and bug fixes are all much simpler, and coding without these guidelines looks like a huge mess.









No comments:

Post a Comment