45

I am new to react and I find it sore in the eyes to look at the component flooded with lots of functions and variable initializations together with the UI. Is it possible to separate them?

Instead of the default setup, like below. How do I separate the business logic into another file?

function MyComponent() {
    const [data, setData] = useState('');
    const someFunc = () => {
        //do something.
    };
    ... some 100-liner initializations

   return ( 
       ...
   )
}
Mehul Thakkar
  • 2,177
  • 13
  • 22
sabunan
  • 537
  • 1
  • 5
  • 11

3 Answers3

79

Yes it is possible, That is called as Separation of concern.

You can create your component structure as below.

MyComponentDirectory
 - useCustomHook
 - Component
 - helper

The code will look like this one.

Hook

const useCustomHook = () => {

    const [value, setValue] = useState('');
    const handleClick = (value) => {
        setValue(value)
        //do something.
    };
    ... some 100-liner initializations/business logic, states, api calls. 

    return {
        value, 
        handleClick,
        ... // Other exports you need. 
    } 
}

export default useCustomHook; 

Component

function MyComponent() {
   const {
       value, 
       handleClick, 
       ... // Other imports 
   } = useCustomHook() 

   return ( 
       <Element value={value} onClick={handleClick} />
   )
}

Helper

const doSomething = () => {}

EDIT

Here's a detailed example of React counter application using Separation of concern

Structure

Directory
- App
- Counter
- useCounter
- helper

App Component

import Counter from "./Counter";
import "./styles.css";

export default function App() {
  return (
    <div className="App">
      <Counter />
    </div>
  );
}

Counter Component

import useCounter from "./useCounter";

const Counter = () => {
  const { count, increaseCount, decreaseCount } = useCounter();

  return (
    <div>
      <p>{count}</p>
      <div>
        <button onClick={increaseCount}>Increase</button>
        <button onClick={decreaseCount}>Decrease</button>
      </div>
    </div>
  );
};

export default Counter;

useCounter Hook

import { useState } from "react";
import numberWithCommas from "./helper";

const useCounter = () => {
  const [count, setCount] = useState(9999);

  const increaseCount = () => setCount(count + 1);
  const decreaseCount = () => setCount(count - 1);

  return {
    count: numberWithCommas(count),
    increaseCount,
    decreaseCount
  };
};

export default useCounter;

Helper Function

const numberWithCommas = (x) => {
  return x.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",");
};

export default numberWithCommas;

Here's the working example in Codesandbox

Note: if you create a simple Javascript util function instead of hook then you won't be able to access other hooks, context inside that function.

Arno van Oordt
  • 2,912
  • 5
  • 33
  • 63
Mehul Thakkar
  • 2,177
  • 13
  • 22
  • I prefer this answer than below since it does not mess up the states. – sabunan Sep 26 '21 at 08:50
  • 1
    Updated the example with Real React counter and working example at Codesandbox. – Mehul Thakkar Sep 26 '21 at 09:15
  • 3
    This is amazing example! Upon further thinking, we could move `numberWithCommas(..)` method out of custom hook and directly call in ui `

    {numberWithCommas(count)}

    ` This way our custom hook acts as isolated model (data) without any view logic. All it needs to do is persist state, update & notify listeners (UI). That makes it more re-usable and clean. Just a thought :)
    – krupesh Anadkat Mar 14 '22 at 03:59
  • 1
    Great idea! We can surely do that. This was added just to showcase how we can use helper within the custom hook to seggregate any data manipulation from react component. – Mehul Thakkar Mar 15 '22 at 16:22
  • 1
    Great answer! Love the clear separation of concerns. – Karson Kalt Aug 14 '22 at 14:46
  • 2
    learning react after working in Angular for years and it is really frustrating to have all the rendering and component logic in one place and that too html(jsx) below a ton of business of logic. This is exactly what i was looking for. I think this should be default convention that every component should be have a hook for business logic keeping the UI component lean. – Suresh Nagar Aug 14 '22 at 20:00
  • 1
    great example, I was using customHooks for different purposes but it's a helpful approach for separating logic. – gurkan May 11 '23 at 06:53
  • it is error-prone, repetitive, and boring to define variables and set them again in return in the Hook especially when you have a lot of statuses, isn't there something better? like exposing every variable here? or use a class/object? – shamaseen Aug 15 '23 at 20:46
5

A common approach that I use myself is to separate the business logic into its own file myComponentHelper.js

This will also make it easier to test the function because it will not be able to use and change the react state without having it passed in as arguments and returning the changes.

myComponent/
  myComponent.jsx
  myComponentHelper.js
  myComponentTest.js
// myComponent.js

import { someFunc } from './myComponentHelper';

function MyComponent() {
    const [data, setData] = useState('');
    
    const x = someFunc(data);

    return ( 
        ...
    )
}
// myComponentHelper.js

export const someFunc = (data) => {
    //do something.
    return something;
}
// myComponentTest.js

import { someFunc } from './myComponentHelper';

test("someFunc - When data is this - Should return this", () => {
    const data = {...};
    const result = someFunc(data);
    expect(result).toEqual("correct business data");
});
ouflak
  • 2,458
  • 10
  • 44
  • 49
2

Separating business logic into other files can be done in various different ways.

  1. Create a helperFile.js that has logic or basically the functions required by the corresponding file.
  2. Creating Custom Hooks. More on that can be found here in the official docs or in this playlist (refer the videos at the very end)
  3. Global State mangement way - where contextAPI or Redux is used to seperate out state and business logic
Tarun
  • 104
  • 4