Store
We love unity in diversity. But we feel that the unity is a problem in programming. Let's imagine that we put the representation and the logic in a same file. That will produce a large file size and maybe thousands line. That make a new developer hard to learn the code and even hard to debug. So, the proposal is, how we separate the representation component and the logic. As we know the representation component handle how the data presented, and the store processing data to make it match with the representation requirement.
For example, we need to show a bunch of data in a list. The store receiving raw data from the service. We don't know, is the data valid or not, null or not, or maybe undefined or not. Before we send the data to the representation layer, we process it first, to minimize the error when we send it to the representation layer.
Frustated
Actually, I feel frustated about this. I read a lot of articles that explain how to treat the state data and asking a colleague to help me. There are so much articles suggest to use state management for best practice, like Redux. Redux is more powerfull to handle separating logic from the UI, but for some engineers, Redux is sucks.
In some articles, Redux is powerfull to handle some global state. But I have another option to handle global state, the Context API. It's more easier to setup and can handle a global state. Maybe, in the future, we can found the engineer that can help to restructure this project better.
Custom Hook
The hook isn't actually separating ui and the logic. In my opinion, this hook is same as the componet ui, but with separate file. We can handle state update and trigger the ui change using this hook and this is enough for me, and I affirmed again that the hook isn't absolutely separating logic, it's just separating file with the ui.
I found something interested about this idea. Previously, I think very hard about how to separate representation component with the logic component, and I don't want to use any state management. After some "hidayah", we found Hook Pattern. For further information, you can go to References section below.
Basic Example
First of all, let's create counter ui. The simple one.
import React from "react";
const Counter: React.FC = () => {
const [number, setNumber] = React.useState(0);
return (
<>
<div>
<h5>{number}</h5>
<button onClick={() => setNumber(number + 1)}>Clik Me</button>
</div>
</>
);
};
Let's separate state with the Custom Hooks.
// Hello I'm Custom Hook
const useCounter = () => {
const [number, setNumber] = React.useState(0);
const incrementNumber = () => {
setNumber(number + 1);
};
return {
number,
incrementNumber,
};
};
Just import it to the representation component.
import React from "react";
import useCounter from "@/store/counter";
const Counter: React.FC = () => {
const { number, incrementNumber } = useCounter();
return (
<>
<div>
<h5>{number}</h5>
<button onClick={incrementNumber}>Clik Me</button>
</div>
</>
);
};
Modify a Little Bit
The example above is just a basic of React Hook. We did a little modification and follow the structure of Hook Pattern. Shortly, we put all of our state and our logic in the Custom Hook and send the updated data to the representation layer. The representation layer will update automatically, follow the state value on the Custom Hook.
Be Careful
You must follow Hook Rule to avoid violation, that can reproduce the error and in my opinion, it will be hard to elaborate the error.
In the React Blueprint, we use hook as a store for the component/page. We handle almost whole lifecycle and data management here. Let's make a sample for Login Page. We have some layer to build a login page. The Wrapper, Component and Store.
The Wrapper
It wrap a whole page. This wrapper is used for representation only. We don't put any store variables/function here to make it simple, so nothing happens here about state update to prevent re-rendering whole page.
The wrapper hold some component, for example a form component.
Component
This layer exist on wrapper layer. The component hold some store variable and functions from store. In this component, state change will affect to a ui render in small affect. So, we don't need to re-render the whole page when state updated.
Store
This layer is a place that we put a functions, variables and state management. We modify the state, we handle logic in this layer. We pass some variables and functions that component needed and the component will call and use them as components needs.
Blueprint Hook Sample
Let's make a simple example how we use the React Hook. In this case, we'll make a simple page called LoginPage. We have one input
component that will be edited with the state.
import React from "react";
const LoginPage = () => {
return (
<>
<div>
<input placeholder="Username/Email" />
</div>
</>
);
};
Don't forget to see the skeleton section, where we put the page file. Next we need to create a custom hook for the page. We need to create a folder called login, and a file inside login folder named index.tsx. Don't forget to use .tsx
instead of .ts
, because we use typescript react, not the pure typescript for Custom Hook.
const useLogin = () => {
return {};
};
Create a state that will be sent to the login page. In this case, we'll create username. After it created successfully, we throw that with return statement.
import { useState } from "react"; // we use hook from the react, called useState
const useLogin = () => {
const [username, setUsername] = useState(""); // I love `any` type
return {
username,
};
};
Where is the modification? Alright. After we create the state varible, how we can change/update it? We need to create a function that will update the state directly without the representation layer knowing. Don't forget, we can return the function too, to the representation layer.
import React, {useState} from 'react';
const useLogin = () => {
...
const onChange = (event: React.ChangeEvent<HTMLInputElement>) => {
setUsername(e.target.value)
}
return {
...,
onChange
}
}
Just consume it in the representation layer. Don't forget to import the hook first! :(
import React from "react";
const LoginPage = () => {
const { username, onChange } = useLogin();
return (
<>
<div>
<input
placeholder="Username/Email"
value={username}
onChange={onChange}
/>
</div>
</>
);
};