커스텀 hook 만들기

useInput

유효성을 검증하는 기능이 있는 useInput 이다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
import React, { useState } from "react";

const useInput = (initialValue, validator) => {
  const [value, setValue] = useState(initialValue);
  const onChange = (event) => {
    const {
      target: { value },
    } = event;
    //event.target = {value}

    let willUpdate = true;
    if (typeof validator === "function") {
      willUpdate = validator(value);
    }
    //validator에 함수가 들어오면 willUpdate에 해당 함수를 넣는다.
    if (willUpdate) {
      setValue(value);
    }
    //willUpdate가 true일 때 setValue는 value를 가리킨다.
  };

  return { value, onChange };
};

function App() {
  const maxLen = (value) => value.length < 10;
  //유효성 검사에 value의 길이가 10미만이라는 조건을 넣는다.
  const name = useInput("Mr.", maxLen);
  //useInput을 사용하는 방법
  return (
    <div>
      <h1>hello</h1>
      <input placeholder="name" {...name} />
      //{...name}은 value={name.value} onChange={name.onChange} 를 모두
      포함한다.
    </div>
  );
}

export default App;

useTabs

선택한 tab의 content를 보여주는 hooks

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
import React, { useState } from "react";

const content = [
  {
    tab: "Section 1",
    content: "Content 1 from Section 1",
  },
  {
    tab: "Section 2",
    content: "Content 2 from Section 2",
  },
];

const useTabs = (initialTab, allTabs) => {
  if (!allTabs || !Array.isArray(allTabs)) {
    return;
  }
  // tab이 없거나 tab이 배열이 아닐 때 아무 일도 일어나지 않는다.
  const [currentIndex, setCurrentIndex] = useState(initialTab);
  return {
    currentItem: allTabs[currentIndex],
    changeItem: setCurrentIndex,
  };
  //최근의 index가 currentItem으로, 새로 바뀔 아이템의 index가 setCurrentIndex로 간다.
};

function App() {
  const { currentItem, changeItem } = useTabs(0, content);
  //useTabs의 배열 0번을 초기값으로, content배열을 allTabs로 넣어준다.
  //{currentItme: content[0], changeItem: changeItem}

  return (
    <div className="App">
      {content.map(
        (section, index) => (
          <button onClick={() => changeItem(index)}>{section.tab}</button>
        )
        //각 tab에 map의 index를 넣어주고 click시 해당 tab의 index가 changeItem에 들어가서 setcurrentIndex값이 된다.
      )}
      <div>{currentItem.content}</div>
      //setCurrentIndex를 통해 currentIndex가 바뀌어서 currentItem의 내용도
      바뀐다.
    </div>
  );
}

export default App;

useTitle

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import React, { useState, useEffect } from "react";

const useTitle = (initialTitle) => {
  const [title, setTitle] = useState(initialTitle);
  const updateTitle = () => {
    const htmlTitle = document.querySelector("title");
    htmlTitle.innerText = title;
    //updateTitle은 html의 title element를 선택해서 현재의 title로 바꾸어주는 역할을 한다.
  };
  useEffect(updateTitle, [title]);
  //title이 바뀌면 title의 innerText를 바꾸는 updateTitle이 실행된다.
  return setTitle;
};

function App() {
  const titleUpdater = useTitle("loading...");
  setTimeout(() => titleUpdater("Home"), 5000);
  //5초 뒤에 titleUpdater, 즉 useTitle에 인자 값을 넣어 실행한다.
  return <div className="App"></div>;
}

export default App;

useClick

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
import React, { useEffect, useRef } from "react";

const useClick = (onClick) => {
  const element = useRef();
  useEffect(() => {
    if (element.current) {
      element.current.addEventListener("click", onClick);
    }
    //componentDidMount시에 일어나는 이벤트
    return () => {
      if (element.current) {
        element.current.removeEventLister("cilck", onClick);
      }
      //componentWillUnMount시에 일어나는 이벤트 : component가 mount되지 않았을 때 eventListner를 배치하지 않기 위해
    };
  }, []);
  //[]를 넣어서 componentDidMount시에만 렌더링되라는 의미가 된다.
  return element;
};

function App() {
  const sayHello = () => console.log("say hello");
  const title = useClick(sayHello);
  return (
    <div className="App">
      <h1 ref={title}>title</h1>
    </div>
  );
}

export default App;

useConfirm

삭제버튼 클릭 -> 정말로 삭제하시겠습니까? -> 예 : deleting / 아니오 : aborted

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
import React from "react";

const useConfirm = (message = "", onConfirm, onCancel) => {
  //alert창 내용, 확인시 실행할 callback, 취소시 실행할 reject
  if (!onConfirm && typeof onConfirm !== "function") {
    return;
  }
  if (onCancel && typeof onCancel !== "function") {
    return;
  }
  //onCancel은 필수적인 것이 아니라서 !안붙임
  const confirmAction = () => {
    if (confirm(message)) {
      onConfirm();
      //message를 확인시 실행
    } else {
      onCancel();
      //message를 취소시 실행
    }
  };
  return confirmAction;
};

function App() {
  const deletePost = () => console.log("Deleting post...");
  const abort = () => console.log("Aborted");
  const confirmDelete = useConfirm("Are you sure", deletePost, abort);
  return (
    <div className="App">
      <button onClick={confirmDelete}>delete</button>
    </div>
  );
}

export default App;
  • window confirm esult = window.confirm(message); alert처럼 뜨는 경고 대화 상자에 message를 넣을 수 있다.

usePreventLeave

페이지에서 무언가 작성하고 있을 때, 창 닫기를 누르면 나가시겠습니까? 변경된 내용이 저장되지 않을 수도 있습니다. 와 같은 메세지가 뜨는 것

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
import React from "react";

const usePreventLeave = () => {
  const listener = (event) => {
    event.preventDefault();
    event.returnValue = "";
    //이 부분을 써주지 않으면 크롬에서 작동하지 않는다. 별 의미는 없다.
  };
  const enablePrevent = () => window.addEventListener("beforeunload", listener);
  //beforeunload는 창을 닫기 전을 말한다.
  const disablePrevent = () =>
    window.removeEventListener("beforeunload", listener);
  return { enablePrevent, disablePrevent };
};

function App() {
  const { enablePrevent, disablePrevent } = usePreventLeave();
  return (
    <div className="App">
      <button onClick={enablePrevent}>Protect</button>
      <button onClick={disablePrevent}>unprotect</button>
    </div>
  );
}

export default App;

useBeforeLeave

window에서 마우스가 떠날 때 호출된다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
import React, { useEffect } from "react";

const useBeforeLeave = (onBefore) => {
  if (typeof onBefore !== "function") {
    return;
  }
  const handle = (event) => {
    const { clientY } = event;
    if(clientY =< 0) {
      onBefore();
    }
    // clientY는 console.log(event)를 해보면 나오는 mouse가 가진 객체 중 하난데, 마우스가 화면을 떠나기 전 마지막 y축의 좌표값을 반환한다. clientY가 0보다 작거나 같다는 것은 마우스가 화면을 떠났을 때를 의미한다.
  };
  useEffect(() => {
    document.addEventListener("mouseleave", handle);
    return () => document.removeEventListener("mouseleave", handle);
  }, []);
};

function App() {
  const begNotToLeave = () => console.log("plz don't leave");
  useBeforeLeave(begNotToLeave);
  //document에 주는 이벤트라서 element에 넣지 않아도 된다.
  return (
    <div className="App">
      <h1>hello</h1>
    </div>
  );
}

export default App;

useFadeIn

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
import React, { useEffect, useRef } from "react";

const useFadeIn = (duration = 1, delay = 0) => {
  if (typeof duration !== "number" || typeof delay !== "number") {
    return;
  }
  const element = useRef();
  useEffect(() => {
    if (element.current) {
      const { current } = element;
      current.style.trasition = `opacity ${duration}s ease-in-out ${delay}s`;
      current.style.opacity = 1;
    }
  }, []);
  return { ref: element, style: { opacity: 0 } };
};

function App() {
  const fadeInH1 = useFadeIn(1, 2);
  const fadeInP = useFadeIn(5, 10);
  return (
    <div className="App">
      <h1 {...fadeInH1}>hello</h1>
      <p {...fadeInP}>how are you?</p>
    </div>
  );
}

export default App;

useNetwork

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
import React, { useEffect, useState } from "react";

const useNetwork = (onChange) => {
  const [status, setStatus] = useState(navigator.onLine);
  const handleChange = () => {
    if (typeof onchange === "function") {
      onchange(navigator);
    }
    setStatus(navigator.onLine);
  };

  useEffect(() => {
    window.addEventListener("online", handleChange);
    window.addEventListener("offline", handleChange);
    () => {
      window.removeEventListener("online", handleChange);
      window.removeEventListener("offline", handleChange);
    };
  }, []);
  return status;
};

function App() {
  const handleNetworkChange = (online) => {
    console.log(online ? "we just went online" : "we are online");
  };
  const online = useNetwork(handleNetworkChange);
  return (
    <div className="App">
      <h1>{online ? "online" : "offline"}</h1>
    </div>
  );
}

export default App;

useScroll

아래로 스크롤하면 h1의 색상이 빨강으로, 위로 스크롤하면 파랑으로 변함

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
import React, { useEffect, useState } from "react";

const useScroll = () => {
  const [state, setState] = useState({
    x: 0,
    y: 0,
  });
  const onScroll = () => {
    setState({ y: window.scrollY, x: window.scrollX });
  };
  useEffect(() => {
    window.addEventListener("scroll", onScroll);
    return () => window.removeEventListener("scroll", onScroll);
  }, []);
  return state;
};

function App() {
  const { y } = useScroll();
  return (
    <div className="App" style=>
      <h1 style=>
        tian
      </h1>
    </div>
  );
}

export default App;

useFullscreen

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
import React, { useRef } from "react";

const useFullscreen = (callback) => {
  const element = useRef();
  const triggerFullscreen = () => {
    if (element.current) {
      element.current.requestFullscreen();
      if (callback && typeof callback === "function") {
        callback(true);
      }
    }
  };
  const exitFullscreen = () => {
    document.exitFullscreen();
    if (callback && typeof callback === "function") {
      callback(false);
    }
  };
  return { element, triggerFullscreen, exitFullscreen };
};

function App() {
  const onFullscreen = (isFull) => {
    console.log(isFull ? "we are full" : "we are small");
  };
  const { element, triggerFullscreen, exitFullscreen } =
    useFullscreen(onFullscreen);
  return (
    <div className="App" style=>
      <div ref={element}>
        <img
          src="https://images.unsplash.com/photo-1612875895771-76bba1a61a49?ixid=MXwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHw%3D&ixlib=rb-1.2.1&auto=format&fit=crop&w=1350&q=80"
          alt="image"
        />
        <button onClick={triggerFullscreen}>make fullscreen </button>
        <button onClick={exitFullscreen}>exit fullscreen </button>
      </div>
    </div>
  );
}

export default App;

useNotification

위치정보 같은 것을 수집할 때 브라우저에 등장하는 허용/차단 알림창 Notification web api

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
import React from "react";

const useNotification = (title, options) => {
  if (!("Notification" in window)) {
    return;
  }
  const fireNotif = () => {
    if (Notification.permission !== "granted") {
      Notification.requestPermission().then((permission) => {
        if (permission === "granted") {
          new Notification(title, options);
        } else {
          return;
        }
      });
    } else {
      new Notification(title, options);
    }
  };
  return fireNotif;
};

function App() {
  const triggerNotif = useNotification("may I help you?", "description");
  return (
    <div className="App" style=>
      <button onClick={triggerNotif}>hello</button>
    </div>
  );
}

export default App;

useAxios

app.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import React from "react";
import useAxios from "./hooks/useAxios";

function App() {
  const { loading, data, error, refetch } = useAxios({
    url: `${url}`,
  });
  console.log(
    `loading: ${loading}\n data:${JSON.stringify(data)} \n error: ${error}`
  );
  return (
    <div className="App" style=>
      <h1>{data && data.status}</h1>
      <h2>{loading && "Loading"}</h2>
      <button onClick={refetch}>refetch</button>
    </div>
  );
}

export default App;

useAxios.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
import defaultAxios from "axios";
import { useEffect, useState } from "react";

const useAxios = (options, axiosInstance = defaultAxios) => {
  const [state, setState] = useState({
    loading: true,
    error: null,
    data: null,
  });

  const [trigger, setTrigger] = useState(0);

  if (!options.url) {
    return;
  }

  const refetch = () => {
    setState({
      ...state,
      loading: true,
    });
    setTrigger(Date.now());
  };

  useEffect(() => {
    axiosInstance(options)
      .then((data) => {
        setState({
          ...state,
          loading: false,
          data,
        });
      })
      .catch((error) => {
        setState({ ...state, loading: false, error });
      });
  }, [trigger]);
  return { ...state, refetch };
};

export default useAxios;