Reputation: 724
I am designing a frontEnd App in which I need to understand the industry standard or best practices to make a call to backend API during the Page Refresh.
Here is a simplistic setup.
Data is Saved on the store courtesy redux-persist (saved on local storage . To be available during page-refresh). This data is used commonly across both DisplayRepo, RedirectToRepo links with some normalization performed on both pages differently before displaying on UI. When User clicks on DisplayRepo or RedirectToRepo Link, Data is available on the store. User fetches data using useSelector and displays it.
So far so good.
I need some inputs and clarification on the following points:-
Home Page:-
const Login = (props) => {
const classes = useStyles(props);
const dispatch = useDispatch();
const history = useHistory();
const [uname, setUname] = useState('');
const { successNotific } = buttonNotification;
const { enqueueSnackbar } = props;
const inputRef = useRef();
const submitBtn = useRef();
useEffect(() => {
inputRef.current.focus();
}, []);
const onChangeHandler = (e) => setUname(e.target.value);
const handleKeyDown = (e) => {
if (e.key === 'Enter') {
e.target.name === 'email' && submitBtn.current.focus();
}
};
return (
<FormControl className={classes.signInForm}>
<Header />
<Box className={classes.inputContainer}>
<input
type='email'
name='email'
ref={inputRef}
className='username'
onKeyDown={handleKeyDown}
placeholder='Enter GitHub UserName'
value={uname}
onChange={onChangeHandler}
/>
<Button
key={successNotific}
disabled={uname.length === 0}
className={classes.btn}
variant='contained'
color='primary'
name='submit'
ref={submitBtn}
onClick={() =>
fetchRepoForNewUser(uname, enqueueSnackbar, dispatch, history)
}
>
Submit
</Button>
</Box>
</FormControl>
);
};
Api call which gets initialized when user click on Submit:-
export const fetchRepoForNewUser = async (
userNameInUrl,
enqueueSnackbar,
dispatch,
history
) => {
const { successNotific, errorNotific, loadingNotific } = buttonNotification;
enqueueSnackbar(loadingNotific.message, { variant: loadingNotific.variant });
window.__MUI_USE_NEXT_TYPOGRAPHY_VARIANTS__ = true;
const isValidGitHubUser = await dispatch(getRepoAction(userNameInUrl));
if (isValidGitHubUser) {
enqueueSnackbar(successNotific.message, {
variant: successNotific.variant,
});
history.push(`/displayuserrepo/${userNameInUrl.toLowerCase()}`);
} else {
enqueueSnackbar(errorNotific.message, { variant: errorNotific.variant });
}
};
Request comes to Redux thunk with dispatch action:-
`export const getRepoAction = (uname) => {
const url = `${GITHUB_API_URL}/${uname}/repos`;
return async (dispatch) => {
dispatch({ type: GET_USER_REPO_LOADING });
try {
alert('calleddd');
const response = await axios.get(url, axiosHeader);
if (response?.status && response.data.length > 0) {
dispatch({ type: GET_USER_REPO_SUCCESS, data: response.data });
dispatch({ type: GET_USERNAME_SUCCESS, data: uname });
return true;
} else {
dispatch({ type: GET_USER_REPO_FAILED });
return false;
}
} catch (error) {
dispatch({ type: GET_USER_REPO_FAILED });
dispatch({ type: GET_USERNAME_FAILED });
return false;
}
};
};
`
ListOfRepo Component:-
const ListOfRepo = (props) => {
const { enqueueSnackbar } = props;
const history = useHistory();
const getUserNameFromRedux = (state) => state.loginReducer.uname;
const username = useSelector(getUserNameFromRedux);
const getUserRepoFromRedux = (state) => state.appReducer.data;
const rowData = useSelector(getUserRepoFromRedux);
const userNameInUrl =
history.location.pathname !== '/' &&
history.location.pathname.split('/')[2];
useEffect(() => {
userNameInUrl !== username &&
fetchRepoForNewUser(userNameInUrl, enqueueSnackbar, dispatch, history);
}, []);
const classes = useStyles(props);
const dispatch = useDispatch();
const onClickHandler = () =>
dispatch({ type: GET_USER_REPO_SUCCESS, data: [] });
return (
<Box className={classes.listOfRepoContainer}>
<GridDisplay
rowData={rowData}
columnHeaders={columnHeaders}
LinkComponent={LinkComponent}
/>
<Button
disabled={rowData.length === 0}
className={classes.resetBtn}
variant='contained'
color='primary'
onClick={onClickHandler}
>
Clear Repositories
</Button>
</Box>
);
};
Upvotes: 0
Views: 1098
Reputation: 42288
Whenever the user refreshes the page although data is already available on store, redux-persist/redux-thunk internally makes a call to backend. My understanding is since data is already available on localstorage, is it ok to make a call to the backend to fetch the Data?
There is no conditional check in your code. Whenever ListOfRepo
is mounted, the useEffect
hook runs and calls fetchRepoForNewUser
. That thunk always calls the GitHub API.
If you want to make use of existing data in your redux state (whether or not it's persisted) then you need to check if that data already exists before fetching. One easy way to do this is by using createAsyncThunk
from redux-toolkit and using the condition
setting.
What should be the best practice here? One alternate way is to check the data on localstorage and if it's not available then a call to the backend should be made using API. But what if LS has stale data?
You are dealing with a situation where changes on GitHub will affect the data. That it outside of your app so you won't be able to know whether the data has become stale or not.
I see two solutions that make sense to me.
Don't persist the data. You would keep the data in redux for when they navigate between pages, but each visit to the website would cause a new fetch.
Persist the data and store a timestamp along with the results. At the top of your page you can have a note like "last synced x minutes/hours/days" ago that tells the user the age of the data. Next to that have a "Sync" button. When they click that button you will call the API and update your persisted data with new results.
Upvotes: 1