Useful Practices for React Hooks
Hooks provide a way to reuse stateful logic across different components, making it easier to manage state, side effects. In this article, we introduce some useful hook practices that might come in handy, resulting in performant and maintainable ReactJS code.

Custom Hooks
1. useArrayState
Generally, since React requires component state to be immutable, we don't modify the state directly if we want to change it, instead we make a copy of it and replace the old state with the new copy.
const TodoList = () => {
const [todos, setTodos] = useState([]);
const add = (newValue) => {
setTodos((currentState) => [...currentState, newValue]);
};
const remove = (index) => {
setTodos((currentState) => {
const newState = [...currentState];
newState.splice(index, 1);
return newState;
});
};
return <ul>{/* Some todos */}</ul>;
};This code is totally fine, but what if we need a lot of array modifications in multiple places. Certainly, there's a better way to create an abstraction over the array state to be reused. We can create a custom hook for it.
const useArrayState = (initialState = []) => {
const [state, setState] = useState(initialState);
const add = (newValue) => {
setTodos((currentState) => [...currentState, newValue]);
};
const remove = (index) => {
setTodos((currentState) => {
const newState = [...currentState];
newState.splice(index, 1);
return newState;
});
};
return [state, { add, remove }];
};We can definitely implement more methods like pop, shift, unshift,.. And the hook usage is very simple, useArrayState now handles array helper methods for us. So neat~
const TodoList = () => {
const [todos, { add, remove }] = useArrayState([]);
const addTodo = () => {
add({ name: "Some new todo " });
};
const removeTodo = (index) => {
remove(index);
};
return <ul>{/* Some todos */}</ul>;
};2. useAsyncCache
With this hook, we can call the API, and also cache the result for a certain duration. We can export loading & error states to handle later.
const useAsyncCache = (
fetchData,
cacheKey,
cacheDuration = 300000 /* 5 minutes */
) => {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(false);
const [error, setError] = useState(null);
useEffect(() => {
let isMounted = true;
const cacheData = JSON.parse(localStorage.getItem(cacheKey));
const cacheExpired =
cacheData && new Date().getTime() - cacheData.timestamp > cacheDuration;
if (cacheData && !cacheExpired) {
setData(cacheData.data);
} else {
setLoading(true);
fetchData()
.then((res) => {
if (isMounted) {
setData(res.data);
localStorage.setItem(
cacheKey,
JSON.stringify({
data: res.data,
timestamp: new Date().getTime(),
})
);
}
})
.catch((err) => {
if (isMounted) {
setError(err);
}
})
.finally(() => {
if (isMounted) {
setLoading(false);
}
});
}
return () => {
isMounted = false;
};
}, [fetchData, cacheKey, cacheDuration]);
return [data, loading, error];
};useReducer
One mistake that's popular is to put so many business logic right inside components. We can use useReducer hook to abstract your app/business logic and make predictable state changes.
So instead of an over-bloated component like this:
function Form() {
const [shippingDate, setShippingDate] = useState(addDays(new Date(), 1));
const [shippingPrice, setShippingPrice] = useState(0);
const [shippingMethod, setShippingMethod] = useState("flat rate");
const [paymentMethod, setPaymentMethod] = useState("credit card");
const [address, setAddress] = useState("");
const [city, setCity] = useState("");
const [zipCode, setZipCode] = useState("");
const baseRate = 10;
const calculateShippingPrice = () => {
let totalCost = baseRate;
if (shippingMethod === "express") {
totalCost += 20;
} else if (shippingMethod === "overnight") {
totalCost += 40;
}
setShippingPrice(totalCost);
};
return (
<form>
<p>Shipping price: {shippingPrice}</p>
</form>
);
}We can useReducer for much simpler React component & business logic abstraction.
function Form() {
const [{ shippingPrice }, dispatch] = useReducer(
shippingReducer,
initialState
);
const calculateShippingPrice = () => {
dispatch({ type: "calculate_shipping_rate" });
};
return (
<form>
<p>Shipping price: {shippingPrice}</p>
</form>
);
}And here's its reducer:
const shippingReducer = (state, action) => {
const newState = { ...state };
switch (action.type) {
case "calculate_shipping_rate":
let totalCost = baseRate;
if (newState.shippingMethod === "express") {
totalCost += 20;
} else if (newState.shippingMethod === "overnight") {
totalCost += 40;
}
newState.shippingPrice = totalCost;
return newState;
break;
default:
break;
}
};Reference:
More useful custom hooks can be found here: