React best practices and patterns to reduce code
I have been working with React.js for the past 4 to 5 years and have been using it for a number of different projects. While working on different projects I’ve found some common patterns I would like to share in this blog post. Without further ado, Let’s get started.
1. Create custom hooks for redux actions and dispatches
I’m not a fan of using redux, but I’ve been using it for a number of different projects. I’ve found that redux-thunk is used in almost all the projects I’ve worked on. I found that is more of a boilerplate code.
Let’s take an example of a redux-thunk project.
function fetchUser(id) {
return (dispatch, getState, { api, otherValue }) => {
return fetch(`/api/user/${id}`)
.then(res => res.json())
.then(user => dispatch({ type: ‘FETCH_USER’, payload: user }))
}
}function fetchUsers() {
return (dispatch, getState, { api, otherValue }) => {
return fetch(`/api/users`)
.then(res => res.json())
.then(user => dispatch({ type:‘FETCH_USERS’, payload: user }))
}
}
Create a custom hook for all user actions.
const useUser = () => {
const dispatch = useDispatch();
const state = useSelector(); // get auth info or something const fetchUser = (id) => {
return fetch(`/api/user/${id}`).then((res) => res.json())
.then((user) => dispatch({type: "FETCH_USER",payload:user}));
}; const fetchUsers = () => {
return fetch('/api/users').then((res) => res.json())
.then((user) => dispatch({type:"FETCH_USERS",payload: user}));
}; return { fetchUser, fetchUsers };
}
// Inside Componentconst { fetchUser } = useUser();
useEffect(() => fetchUser(1), [])
NOTE: As you can see here I don’t have to create multiple functions for all redux actions. We can also use the useSelector hook to get any info from redux.
2. Use an object instead of a switch inside the reducer
This is not a good idea if you have a lot of cases to handle. You can use an object literal as an alternative to switch statements. The object literal is more readable and easier to maintain.
Switch case for reducer
const reducer = (state, action) => {
switch (action.type) {
case 'INCREMENT': return { ...state, count: state.count + 1 };
case 'DECREMENT': return { ...state, count: state.count - 1 };
default: return state;
}
};
Object literal for reducer
const actionMap = {
INCREMENT:(state, act) => ({...state, count: state.count + 1 }),
DECREMENT: (state, act) => ({...state, count: state.count - 1 }),
}const reducer = (state, action) => {
const handler = actionMap[action.type];
return handler ? handler(state, action) : state;
};
NOTE: The map variable must be declared outside the dispatch context otherwise it will always be re-evaluated.
A switch can be implemented using a tree which makes it O(log n)
. Searching on the map is O(1)
.
3. Create a hook for REST calls
you can use the browser fetch API and create your own hook and avoid some repetition of code. like getting data from API updates in state and render.
Common Approach
const getData = () => {
fetch(`/api/users`).then((res) => res.json());
}const Users = () => {
const [data, setResponse] = useState({ data: [], loading: true, error: null});
useEffect(() => {
getData()
.then((data) =>
setResponse({ data, loading: false, error: null}))
.catch((error) =>
setResponse({ data: [], loading: false, error,}));
}, []); return (
<div>
{da.map((user) => <User key={user.id} user={user} />)
</div>
);
}
useFetch hook
const useFetch = (input, {auto, ...init}) => {
const [result, setResult] = useState([null, null, true]);
const fetcher = useCallback((query, config) => fetch(query, config)
.then((res) => res.json())
.then((data) => setUsers([ null, data, false ]))
.catch((err) => setResult([err, null, false])),
[input, init]
); useEffect(() => {
if(auto){
fetcher(input, init)
}
}, []); // if you want to fetch data only once, do this. return [...result, fetcher];
//fetcher(refetch) function or can be used for post api call
}const Users = () => {
const [err, users, loading] = useFetch(`/api/users`, {auto:true});
return (
<div>
{users.map((user) => <User key={user.id} user={user} />)}
</div>
);
}
NOTE: It’s similar to react-query/useSWR, both libraries have much more to offer. you can use these libraries, but if you have restrictions on your project you can go ahead with this approach to avoid some extra code.
4. Code Splitting
use React.lazy, It is a very powerful tool that allows you to load components only when they are needed. The React.lazy function lets you render a dynamic import as a regular component.
A good place to start is with routes. When you go with the traditional approach, you have to load both components before rendering them, but this is not a good approach, because it will take extra time to load all components. Even though we are not showing the component.
We can use react.lazy to load the components asynchronously. So when you are at the first(Home) page, you can load the first component and when you are at the second(About) page, you can load the second component. This way we can avoid the unnecessary loading of components.
Traditional approach
import React from 'react';
import Home from './Home';
import About from './About';function MyComponent() {
return (
<Route path="/home" component={Home} />
<Route path="/about" component={About} />
);
}
Code Splitting of react app
const Home = React.lazy(() => import('./Home'));
const About = React.lazy(() => import('./About'));function MyComponent() {
return (
<Suspense fallback={<div>Loading...</div>}>
<Route path="/home" component={Home} />
<Route path="/about" component={About} />
</Suspense>
);
}
NOTE: This is a straightforward use case but what if we have hundreds of routes and components? You will see a massive difference in the performance.
Want to learn more about react best practices must check part 2
No more redux action creator functions
React best practices and patterns to reduce code — Part 2
Reference:
Code-Splitting — React (reactjs.org)
Got any questions? please leave a comment.
Thank you for reading 😊
More content at Medium
Follow me on Youtube, GitHub, Twitter, LinkedIn, Dev.to, and Stackblitz.