Axios get progress (react + redux + axios)

시작하기

React에서 백엔드와 통신하기 위해서 fetch, axios등을 이용합니다.
axios에서 데이터파일 post혹은 get을 지원하지만,
request와 response 진행상황에 대한 progress는 지원하지 않습니다.
그러기에 axios, react, redux를 이용하여 api의 진행상황을 나타내는 컴포넌트를 만들어 보겠습니다.

axiosProgress.ts Reducer 생성

src/store/modules폴더 내부에 리듀서를 생성합니다.
axios가 시작될때 끝났을때 초기화시켜주는 액션을 작성합니다.

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
48
49
import { action, ActionType, createReducer } from 'typesafe-actions';

const START_PROGRESS = `AXIOS_PROGRESS/START`;
const FINISH_PROGRESS = `AXIOS_PROGRESS/FINISH`;
const RESET_PROGRESS = `AXIOS_PROGRESS/RESET`;

const startProgress = (progress: number) => action(START_PROGRESS, { progress });
const finishProgress = () => action(FINISH_PROGRESS);
const resetProgress = () => action(RESET_PROGRESS);

const actionCreator = {
startProgress,
finishProgress,
resetProgress
};

export {
START_PROGRESS,
FINISH_PROGRESS,
RESET_PROGRESS,
startProgress,
finishProgress,
resetProgress,
actionCreator
}

export interface ProgressState {
progress: number;
isLoading: boolean;
}

export type ProgressAction = ActionType<typeof actionCreator>;

const initialState: ProgressState = {
progress: 0,
isLoading: false,
};

export default createReducer<ProgressState, ProgressAction>(initialState, {
[START_PROGRESS]: (state, action) => {
return { ...state, isLoading: true, progress: action.payload.progress }
},
[FINISH_PROGRESS]: state => {
return { ...state, isLoading: false, progress: 100 }
},
[RESET_PROGRESS]: state => {
return { ...state, isLoading: false, progress: 0 }
}
})

progressAxios.ts 생성

axios를 request할때와 response시에 intercept해서 store를 통해 액션을 dispatch합니다.
재귀함수를 통해 setTimeout을 반복적으로 호출해줍니다.
progress가 100이 되거나 넘을시에는 finish액션을 호출하여 100으로 고정하고
완료시에 reset액션을 통해서 초기화시켜줍니다.

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
48
49
50
51
import axios from 'axios';
import store from 'store/configureStore';
import { actionCreator } from 'store/modules/axiosProgress';

const progressAxios = axios.create();
progressAxios.defaults.timeout = 3000;

let progress = 0; // 0 ~ 100, request percent
let timerId: NodeJS.Timeout | null = null; // timer id

export const progressSpeed = 500;

const setProgress = (value: number): void => {
progress = value;
store.dispatch(actionCreator.startProgress(value));
if (value === 100) {
store.dispatch(actionCreator.finishProgress());
setTimeout(() => store.dispatch(actionCreator.resetProgress()), progressSpeed);
}
};

const timer = (): void => {
if (progress < 100) {
const diff = 100 - progress;
const inc = diff / (10 + progress * (1 + progress / 100)) // increment
setProgress(progress + inc);
}
timerId = setTimeout(timer, 50); // 50 ms
};

progressAxios.interceptors.request.use(config => {
setProgress(0);
timer();
return config;
}, error => {
store.dispatch(actionCreator.resetProgress());
return Promise.reject(error);
});

progressAxios.interceptors.response.use(response => {
if (timerId) {
clearTimeout(timerId);
timerId = null;
}
setProgress(100);
return response;
}, error => {
store.dispatch(actionCreator.resetProgress());
return Promise.reject(error);
});
export default progressAxios;

컴포넌트와 연동하기

axiosProgress를 이용해서 react 컴포넌트와 연동하겠습니다.

ProgressBar.tsx 셍성

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import React from 'react';
import { useSelector } from 'react-redux';
import { RootState } from "store/rootReducer";
import { progressSpeed } from "api/axios";
import './progress.css';

const ProgressBar = () => {
const state = useSelector((state: RootState) => state.axiosProgress);
return (<div className="progress-bar-wrapper"
style={{
transition: `${progressSpeed}ms`,
height: state.isLoading ? '4px' : '0px'
}}>
<div className="progress-bar" style={{width: `${state.progress}%`}}/>
</div>)
}
export default ProgressBar;

progress를 나타내고 숨겨주는 애니메이션을 구현할 wrapper와
progress를 보여줄 progress-bar로 나눕니다.

height는 로딩시에는 4px로, 로딩이 다되면 0px으로 숨겨주는 역할을합니다.
width는 api 진행상황을 %로 보여줍니다

1
2
3
4
5
6
7
8
9
10
11
12
13
.progress-bar-wrapper {
position: fixed;
width: 100%;
height: 4px;
z-index: 9999;
top: 0;
left: 0;
}
.progress-bar {
position: absolute;
height: 100%;
background: red;
}

스타일은 간단하게 표현합니다.
커스터마이징이 가능합니다.

사용하기

1
2
3
4
5
6
7
8
9
10
11
12
13
import React from 'react';
import './App.css';
import ProgressBar from './component/ProgressBar';

const App: React.FC = () => {
return (
<div className="App">
<ProgressBar/>
</div>
);
}

export default App;

ProgressBar 컴포넌트를 불러옵니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
//...
import progressAxios from 'api/axios';

const App: React.FC = () => {
React.useEffect(() => {
progressAxios('https://jsonplaceholder.typicode.com/todos')
},[])
return (
<div className="App">
<ProgressBar/>
</div>
);
}
//...

axios와 사용방법은 같습니다.
테스트를 위해 jsonplaceholder를 이용해보겠습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
//...
import progressAxios from 'api/axios';

const App: React.FC = () => {
const [todos,setTodos] = React.useState([{id:0, userId:0, title:''}]);
const getData = async () => {
try{
const res = await progressAxios('https://jsonplaceholder.typicode.com/todos');
setTodos(res.data);
}
catch(e){
console.log(e);
}
}
return (
<div className="App">
<ProgressBar/>
<button type='button' onClick={() => getData()}>GET DATAS</button>
{todos.map((todo:any) => <p key={todo.id}>{todo.title}</p>)}
</div>
);
}
//...

결과

결과물
이것으로 axios get progress를 구현해보았습니다.

Share