DEV Community ๐Ÿ‘ฉโ€๐Ÿ’ป๐Ÿ‘จโ€๐Ÿ’ป

Cover image for React best practices and patterns to reduce code - Part 3
Rahul Sharma
Rahul Sharma

Posted on • Updated on

React best practices and patterns to reduce code - Part 3

This is 3rd the article about React best practices, If you have not read my previous articles, please check them out.

React best practices and patterns to reduce code - Part 1
React best practices and patterns to reduce code - Part 2

let's look at some more best practices and patterns to reduce code.


Store Tokens to an HTTP Cookie rather than localStorage
Bad code:
const token = localStorage.getItem("token");
if (token) {
  axios.defaults.headers.common["Authorization"] = token;
}
Enter fullscreen mode Exit fullscreen mode
Good code:
import Cookies from "js-cookie"; //  use another library if you want

const token = Cookies.get("token");
if (token) {
  axios.defaults.headers.common["Authorization"] = token;
}
Enter fullscreen mode Exit fullscreen mode
Better code:
No Code ๐Ÿ˜‰
Enter fullscreen mode Exit fullscreen mode

Note:

  • Cookies are shared with all sites on the same domain. No need to pass the token to every request. If the backend is not on the same domain as the frontend, you have to use 2nd approach.
  • Use the HttpOnly attribute for to prevent access to cookie values(token) via JavaScript. but you need some flag at React app for checking route access.

Use interceptors for auth token or any other common headers
Bad code:
axios.get("/api", {
  headers: {
    ts: new Date().getTime(),
  },
});
Enter fullscreen mode Exit fullscreen mode
Good code:
// only once
axios.interceptors.request.use(
  (config) => {
    // Do something before request is sent
    config.headers["ts"] = new Date().getTime();
    return config;
  },
  (error) => {
    // Do something with request error
    return Promise.reject(error);
  }
);

// Component
axios.get("/api");
Enter fullscreen mode Exit fullscreen mode

Use context/redux for passing props to children
Bad code:
const auth = { name: "John", age: 30 };
return (
  <Router>
    <Route path="/" element={<App auth={auth} />} />
    <Route path="/home" element={<Home auth={auth} />} />
  </Router>
);
Enter fullscreen mode Exit fullscreen mode
Good code:
return (
  <Provider store={store}>
    <Router>
      <Route
        path="/"
        element={<App />}
      />
      <Route
        path="/home"
        element={<Home />}
      />
    </Router>
);


// Inside child component
const { auth } = useContext(AuthContext); // For context
const { auth } = useSelector((state) => state.auth); // For redux
Enter fullscreen mode Exit fullscreen mode

Use helper function for styled-components
Not bad code but difficult to read when you think in terms of px.
const Button = styled.button`
  margin: 1.31rem 1.43rem;
  padding: 1.25rem 1.5rem;
`;
Enter fullscreen mode Exit fullscreen mode
Create helper function for px to rem conversion
const toRem = (value) => `${value / 16}rem`;
const Button = styled.button`
  margin: ${toRem(21)} ${toRem(23)};
  padding: ${toRem(20)} ${toRem(24)};
`;
Enter fullscreen mode Exit fullscreen mode

Use common function for input data change
Bad code:
const onNameChange = (e) => setName(e.target.value);
const onEmailChange = (e) => setEmail(e.target.value);

return (
  <form>
    <input type="text" name="name" onChange={onNameChange} />
    <input type="text" name="email" onChange={onEmailChange} />
  </form>
);
Enter fullscreen mode Exit fullscreen mode
Good code:
const onInputChange = (e) => {
  const { name, value } = e.target;
  setFormData((prevState) => ({
    ...prevState,
    [name]: value,
  }));
};

return (
  <form>
    <input type="text" name="name" onChange={onInputChange} />
    <input type="text" name="email" onChange={onInputChange} />
  </form>
);
Enter fullscreen mode Exit fullscreen mode

Use intersection observer for lazy loading
Bad code:
element.addEventListener("scroll", function (e) {
  // do something
});
Enter fullscreen mode Exit fullscreen mode
Good code:
const useScroll = (ele, options = {}): boolean => {
  const [isIntersecting, setIsIntersecting] = useState(false);
  useEffect(() => {
    const cb = (entry) => setIsIntersecting(() => entry.isIntersecting);
    const callback: IntersectionObserverCallback = (entries) => entries.forEach(cb);
    const observer = new IntersectionObserver(callback, options);
    if (ele) observer.observe(ele);
    return (): void => ele && observer.unobserve(ele);
  }, [ele]);
  return isIntersecting;
};


// Component
const ref = useRef<any>();
const isIntersecting = useScroll(ref?.current);

useEffect(() => {
  if (isIntersecting) {
    // call an API
  }
}, [isIntersecting]);

Enter fullscreen mode Exit fullscreen mode

Use HOC for authentication and private route
Bad code:
const Component = () => {
  if (!isAuthenticated()) {
    return <Redirect to="/login" />;
  }
  return <div></div>;
};
Enter fullscreen mode Exit fullscreen mode
Good code:
const withAuth = (Component) => {
  return (props) => {
    if (!isAuthenticated()) {
      return <Redirect to="/login" />;
    }
    return <Component {...props} />;
  };
};

// Route
<Route path="/home" component={withAuth(Home)} />;

// Component
const Component = (props) => <div></div>;
export default withAuth(Component);
Enter fullscreen mode Exit fullscreen mode

Use Array of route object to define the routes
Common approach:
return (
  <Router>
    <Route path="/" element={<App />} />
    <Route path="/about" element={<About />} />
    <Route path="/topics" element={<Topics />} />
  </Router>
);
Enter fullscreen mode Exit fullscreen mode
Good code:
const routes = [
  {
    path: "/",
    role: ["ADMIN"],
    element: React.lazy(() => import("../pages/App")),
    children: [
      {
        path: "/child",
        element: React.lazy(() => import("../pages/Child")),
      },
    ],
  },
  {
    path: "/about",
    role: [],
    element: React.lazy(() => import("../pages/About")),
  },
  {
    path: "/topics",
    role: ["User"],
    element: React.lazy(() => import("../pages/Topics")),
  },
];

const createRoute = ({ element, children, role, ...route }) => {
  const Component = role.length > 0 ? withAuth(element) : element;
  return (
    <Route key={route.path} {...route} element={<Component />}>
      {children && children.map(createRoute)}
    </Route>
  );
};

return <Routes>{routes.map(createRoute)}</Routes>;
Enter fullscreen mode Exit fullscreen mode

Note: This requires more code, but it is more flexible. If you want to use more HOC, you have to only update createRoute.


Use Typescript
Nothing wrong if you don't use Typescript ๐Ÿ˜€, but it'll help you to write better code
npx create-react-app my-app --template typescript
Enter fullscreen mode Exit fullscreen mode

Use eslint, prettier for Formatting
npm install -D eslint prettier
npx eslint --init
Enter fullscreen mode Exit fullscreen mode
Refer this: Eslint setup, Prettier setup

๐Ÿ˜ฅ Not added complete steps, I want to keep this short and simple. If you find any difficulties, please leave a comment.


Use pre-commit hook to run eslint and prettier
npx mrm@2 lint-staged // This will install and configure pre-commit hook

// This script will be created at the root of your project
.husky/pre-commit

// Package.json
"lint-staged": {
  "src/**/*.{js,ts,jsx,tsx}": [
    "npm run lint",
    "npm run prettier",
    "npm run unit-test",
    "git add"
  ]
}
Enter fullscreen mode Exit fullscreen mode

Note:

  • You can update the config to run prettier and eslint on commit. You can add or remove the command in the project package.json.
  • Better to have CI & CD setup for this, Someone can comment out the pre-commit hook and push code to git.

Use vscode extension for better development
Auto Close Tag, Auto Rename Tag, CodeMetrics, CSS Peek, ES7+ React/Redux/React-Native snippets, Eslint, GitLens, Import Cost, Prettier

Note: Must try code complexity extension(CodeMetrics). It'll help you to write better code by showing the complexity of your code.

React best practices and patterns to reduce code - Part 3


Thank you for reading ๐Ÿ˜Š

Got any questions or additional? please leave a comment.

Must Read If you haven't
No more redux action creator functions
How to cancel Javascript API request with AbortController
13 Typescript Utility: A Cheat Sheet for Developer
Javascript short reusable functions trick and tips

More content at Dev.to.
Catch me on Github, Twitter, LinkedIn, Medium, and Stackblitz.

Top comments (5)

Collapse
 
pikekwc profile image
pike-kwc

Thanks for the tips.

I have a love/hate relationship with the pre-commit hook. It can be useful, but almost every time I've joined a code base that uses it, it has been VERY strict and heavy handed, but your example runs the lint/prettier instead of running a checker which is much nicer.

Your note to actually put it in the CI/CD is great, I wish more people would do that, rather than making me run "npx stylelint src/*.scss" because background is before display or some other nonsense that makes my commit fail.

I was working on someone elses code base, and they basically told me to just run --no-verify because the pre-commit was so strict.

Collapse
 
devsmitra profile image
Rahul Sharma Author • Edited on

That's true, Most of the time people comment eslint rule or disable for file, CI/CD is best option.

Collapse
 
rahmabn99 profile image
Rahma Ben Afia

Nice guide and thanks for sharing, but i want to ask you if you give me an example on haw to use interceptors for authentication.
thanks for your help

Collapse
 
theinfosecguy profile image
Keshav Malik

Great Stuff!

Collapse
 
nambui98 profile image
Nam Bui

awesome, tks

๐ŸŒš Browsing with dark mode makes you a better developer.

It's a scientific fact.