5 React Useful Tips for Beginners
Tags: javascript, react, beginners, tutorial
Published: 2022-02-17 16:43:57

When I first started learning React, I had a hard time understanding its concepts. I still do, but as we all learn from our struggles, I did learn some very useful tips.
In this post I'm gonna share few tips which I've learned over the time when I was learning React.

Let's get started!

# 1. Event Handler With Closure

**Before**
```javascript
type State = { name: string; number: string };

function App() {
  const [state, setState] = useState<State>({
    name: '',
    number: '',
  });

  const handleNameChange = useCallback(
    (e: React.ChangeEvent<HTMLInputElement>) => {
      const name = e.target.value.substring(0, 20);

      setState((prevState) => ({
        ...prevState,
        name,
      }));
    },
    []
  );

  const handleNumberChange = useCallback(
    (e: React.ChangeEvent<HTMLInputElement>) => {
      const number =
        e.target.value
          .substring(0, 20)
          .match(/[0-9]|-/g)
          ?.join('') || '';

      setState((prevState) => ({
        ...prevState,
        number,
      }));
    },
    []
  );

  return (
    <div>
      <span>Name</span>
      <input type="text" onChange={handleNameChange} value={state.name} />
      <span>Phone</span>
      <input type="text" onChange={handleNumberChange} value={state.number} />
    </div>
  );
}
```

**After**
```javascript
type State = { name: string; number: string };
type StateKey = keyof State;

type HandleStateChangeOption = { maxLength?: number; useTelFormat?: boolean };

function App() {
  const [state, setState] = useState<State>({
    name: '',
    number: '',
  });

  const handleStateChange = useCallback(
    (key: StateKey, options: HandleStateChangeOption) =>
      (e: React.ChangeEvent<HTMLInputElement>) => {
        let { value } = e.target;

        if (options.useTelFormat) {
          value = value.match(/[0-9]|-/g)?.join('') || '';
        }

        if (options.maxLength !== undefined) {
          value = value.substring(0, options.maxLength);
        }

        setState((prevState) => ({
          ...prevState,
          [key]: value,
        }));
      },
    []
  );

  return (
    <div>
      <span>Name</span>
      <input
        type="text"
        onChange={handleStateChange('name', { maxLength: 20 })}
        value={state.name}
      />
      <span>Phone</span>
      <input
        type="text"
        onChange={handleStateChange('number', {
          useTelFormat: true,
          maxLength: 20,
        })}
        value={state.number}
      />
    </div>
  );
}
```

Now, you can control several states in a place and use the options.

# 2. Functional Components

The React functional components? No, Not. It means the components that do something without `UI`.

**Before**
```javascript
function App() {
  const [enableScroll, setEnableScroll] = useState(true);
  const [prevBodyOverflow, setPrevBodyOverflow] = useState<string>('auto');

  const handleButtonClick = useCallback(() => {
    if (enableScroll) {
      setPrevBodyOverflow(document.body.style.overflow);
      document.body.style.overflow = 'hidden';
    } else {
      document.body.style.overflow = prevBodyOverflow;
    }
    setEnableScroll(!enableScroll);
  }, [enableScroll, prevBodyOverflow]);

  const buttonText = useMemo(
    () => (enableScroll ? 'disable scroll' : 'enable scroll'),
    [enableScroll]
  );

  return (
    <div style={{ height: '200vh', backgroundColor: 'gray' }}>
      <button onClick={handleButtonClick}>{buttonText}</button>
    </div>
  );
}
```

**After**
```javascript
function DisableScroll() {
  useEffect(() => {
    const prevBodyOverflow = document.body.style.overflow;

    document.body.style.overflow = 'hidden';
    return () => {
      document.body.style.overflow = prevBodyOverflow;
    };
  }, []);

  return null;
}

function App() {
  const [enableScroll, setEnableScroll] = useState(true);

  const handleButtonClick = useCallback(() => {
    setEnableScroll(!enableScroll);
  }, [enableScroll]);

  const buttonText = useMemo(
    () => (enableScroll ? 'disable scroll' : 'enable scroll'),
    [enableScroll]
  );

  return (
    <div style={{ height: '200vh', backgroundColor: 'gray' }}>
      {!enableScroll && <DisableScroll />}
      <button onClick={handleButtonClick}>{buttonText}</button>
    </div>
  );
}
```

**Before**
```javascript
function App() {
  const [count, setCount] = useState(60);

  useEffect(() => {
    const tm = setInterval(() => {
      setCount((count) => {
        if (count === 1) {
          clearInterval(tm);
        }

        return count - 1;
      });
    }, 1000);
  }, []);

  return (
    <div>
      <span>{count}</span>
    </div>
  );
}
```

**After**
```javascript
interface ICountProps {
  onCount: (count: number) => void;
  startNumber: number;
}

function Count({ onCount, startNumber }: ICountProps) {
  const [count, setCount] = useState(startNumber);

  useEffect(() => {
    const tm = setInterval(() => {
      setCount((count) => {
        if (count === 1) {
          clearInterval(tm);
        }

        return count - 1;
      });
    }, 1000);
  }, []);

  useEffect(() => {
    onCount(count);
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [count]);

  return null;
}

function App() {
  const [count, setCount] = useState(0);

  return (
    <div>
      <Count startNumber={60} onCount={(count) => setCount(count)} />
      <span>{count}</span>
    </div>
  );
}
```

I'm not sure it's a good example though. I believe you'll find the right case.
Replace your js code to components in a React way.

# 3. Separating `map` from 'return' and use it with `useMemo`

**before**
```javascript
type ItemType = { id: number; name: string; price: string };

type StateChangeHandler = (
  state: keyof ItemType
) => (e: React.ChangeEvent<HTMLInputElement>) => void;

interface IItemProps extends ItemType {
  onChange: StateChangeHandler;
  onRemove: VoidFunction;
}

function Item({ id, name, price, onChange, onRemove }: IItemProps) {
  return (
    <div>
      <span>ID: {id}</span>
      <span>name</span>
      <input type="text" value={name} onChange={onChange('name')} />
      <span>price</span>
      <input type="text" value={price} onChange={onChange('price')} />
      <button onClick={onRemove}>REMOVE</button>
    </div>
  );
}

const newItem = (id: number): ItemType => ({ id, name: 'Item', price: '10' });
const initItems = (): ItemType[] => [newItem(1)];

function App() {
  const [items, setItems] = useState<ItemType[]>(initItems());

  const handleItemAdd = useCallback(() => {
    setItems(items.concat(newItem(items[items.length - 1].id + 1)));
  }, [items]);

  const handleItemRemove = useCallback(
    (index: number) => () => {
      const newItems = [...items];
      newItems.splice(index, 1);
      setItems(newItems);
    },
    [items]
  );

  const handleStateChange = useCallback(
    (index: number) =>
      (state: keyof ItemType) =>
      (e: React.ChangeEvent<HTMLInputElement>) => {
        const newItems = [...items];

        switch (state) {
          case 'id':
            newItems[index][state] = parseInt(e.target.value, 10);
            break;
          default:
            newItems[index][state] = e.target.value;
        }

        setItems(newItems);
      },
    [items]
  );

  return (
    <div>
      <button onClick={handleItemAdd}>ADD</button>
      <br />
      {items.map((item, itemIdx) => (
        <Item
          key={itemIdx}
          {...item}
          onChange={handleStateChange(itemIdx)}
          onRemove={handleItemRemove(itemIdx)}
        />
      ))}
    </div>
  );
}
```

**After**
```javascript
...
const itemComponents = useMemo(() => {
    return items.map((item, itemIdx) => (
      <Item
        key={itemIdx}
        {...item}
        onChange={handleStateChange(itemIdx)}
        onRemove={handleItemRemove(itemIdx)}
      />
    ));
  }, [handleItemRemove, handleStateChange, items]);

  return (
    <div>
      <button onClick={handleItemAdd}>ADD</button>
      <br />
      {itemComponents}
    </div>
  );
```

It looks focus more `First thing`.
But the point is keeping your `return` clean.

# 4. Conditional Rendering with Compound Components 

**Before**
```javascript
function App() {
  const [number, setNumber] = useState('');

  const handleNumberChange = useCallback(
    (e: React.ChangeEvent<HTMLInputElement>) =>
      setNumber(e.target.value.match(/[0-9]/g)?.join('') || ''),
    []
  );

  const realNum = Number(number);

  return (
    <div>
      <input type="text" value={number} onChange={handleNumberChange} />
      <br />
      {realNum < 10 && <div>number is less than 10.</div>}
      {realNum > 10 && <div>number is greater than 10.</div>}
      {realNum < 5 && <div>number is less than 5.</div>}
      {realNum > 3 && <div>number is greater than 3.</div>}
    </div>
  );
}
```

**After**
```javascript
interface IContext {
  contextNum: number;
}

interface INumComponentProps {
  num: number;
  children?: React.ReactNode;
}

const CondtionalRenderingContext = React.createContext<IContext>({
  contextNum: 0,
});

const useContextNum = () => React.useContext(CondtionalRenderingContext);

function CondtionalRendering({ num, children }: INumComponentProps) {
  return (
    <CondtionalRenderingContext.Provider value={{ contextNum: num }}>
      {children}
    </CondtionalRenderingContext.Provider>
  );
}

function Over({ num, children }: INumComponentProps) {
  const { contextNum } = useContextNum();
  if (contextNum <= num) return null;

  return <>{children}</>;
}

function Under({ num, children }: INumComponentProps) {
  const { contextNum } = useContextNum();
  if (contextNum >= num) return null;

  return <>{children}</>;
}

function App() {
  const [number, setNumber] = useState('');

  const handleNumberChange = useCallback(
    (e: React.ChangeEvent<HTMLInputElement>) =>
      setNumber(e.target.value.match(/[0-9]/g)?.join('') || ''),
    []
  );

  const realNum = Number(number);

  return (
    <div>
      <input type="text" value={number} onChange={handleNumberChange} />
      <br />
      <CondtionalRendering num={realNum}>
        <Under num={10}>
          <div>number is less than 10.</div>
        </Under>
        <Over num={10}>
          <div>number is greater than 10.</div>
        </Over>
        <Under num={5}>
          <div>number is less than 5.</div>
        </Under>
        <Over num={3}>
          <div>number is greater than 3.</div>
        </Over>
      </CondtionalRendering>
    </div>
  );
}
```

It's not always a better way. There are more components, it can be not good in performance. Consider it whether the benefits are better than the shortcoming or not in your code.  (Like readability)

# 5. Custom Dialogs with `context`

**Before**
```javascript
interface IDialog {
  message: string;
  onConfirm?: VoidFunction;
}

function Dialog({ message, onConfirm }: IDialog) {
  return (
    <ModalOverlay>
      <ModalContainer>
        <p>{message}</p>
        <button onClick={onConfirm}>OK</button>
      </ModalContainer>
    </ModalOverlay>
  );
}

function App() {
  const [text, setText] = useState('');
  const [dialog, setDialog] = useState<IDialog | undefined>(undefined);

  const handleDialogOpen = useCallback(() => {
    setDialog({
      message: text,
      onConfirm: () => {
        setText('');
        setDialog(undefined);
      },
    });
  }, [text]);

  return (
    <div>
      {dialog && <Dialog {...dialog} />}
      <input
        type="text"
        onChange={(e) => setText(e.target.value)}
        value={text}
      />
      <button onClick={handleDialogOpen}>Click Me!</button>
    </div>
  );
}

const ModalOverlay = styled.div`
  position: fixed;
  top: 0;
  bottom: 0;
  left: 0;
  right: 0;
  background-color: rgba(0, 0, 0, 0.4);
  display: flex;
  justify-content: center;
  align-items: center;
`;

const ModalContainer = styled.div`
  min-width: 300px;
  padding: 24px;
  background-color: white;
  box-shadow: rgba(0, 0, 0, 0.24) 0px 3px 8px;
  border-radius: 12px;

  > button {
    width: 100%;
  }
`;
```

**After**
```javascript
interface IDialog {
  message: string;
  onConfirm?: VoidFunction;
}

interface IDialogContext {
  showDialog: (paramaeters: IDialog) => void;
}

const DialogContext = React.createContext<IDialogContext>(undefined!);

const useDialog = () => React.useContext(DialogContext);

function DialogContextProvider({ children }: { children?: React.ReactNode }) {
  const [dialog, setDialog] = useState<IDialog | undefined>(undefined);

  const showDialog = useCallback(({ message, onConfirm }: IDialog) => {
    setDialog({
      message,
      onConfirm: () => {
        onConfirm && onConfirm();
        setDialog(undefined);
      },
    });
  }, []);

  return (
    <DialogContext.Provider value={{ showDialog }}>
      {dialog && <Dialog {...dialog} />}
      {children}
    </DialogContext.Provider>
  );
}

function Dialog({ message, onConfirm }: IDialog) {
  return (
    <ModalOverlay>
      <ModalContainer>
        <p>{message}</p>
        <button onClick={onConfirm}>OK</button>
      </ModalContainer>
    </ModalOverlay>
  );
}

function Page() {
  const [text, setText] = useState('');
  const { showDialog } = useDialog();

  const handleDialogOpen = useCallback(() => {
    showDialog({
      message: text,
      onConfirm: () => setText(''),
    });
  }, [text]);

  return (
    <div>
      <input
        type="text"
        onChange={(e) => setText(e.target.value)}
        value={text}
      />
      <button onClick={handleDialogOpen}>Click Me!</button>
    </div>
  );
}

function App() {
  return (
    <DialogContextProvider>
      <Page />
    </DialogContextProvider>
  );
}

const ModalOverlay = styled.div`
  position: fixed;
  top: 0;
  bottom: 0;
  left: 0;
  right: 0;
  background-color: rgba(0, 0, 0, 0.4);
  display: flex;
  justify-content: center;
  align-items: center;
`;

const ModalContainer = styled.div`
  min-width: 300px;
  padding: 24px;
  background-color: white;
  box-shadow: rgba(0, 0, 0, 0.24) 0px 3px 8px;
  border-radius: 12px;

  > button {
    width: 100%;
  }
`;
```

I made the page component for avoiding confusing where you should put the Provider. You don't have to take care all of the dialog code. Just get the show method from `useDialog` anywhere then use it. 

```showDialog({ message: 'hi!' });```

Also, you can make other dialogs and modals like this way.

---

# Conclusion

Here are 5 React useful tips. I hope the post helps someone. If you have better examples with these tips or if you have your useful tips, please let me know in the comment below.

Thank you for reading this and happy `React`!