ryan
ryan

Reputation: 724

How to handle async api calls to backend during page refresh in react/redux?

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.

  1. Three Navlinks Home, DisplayRepo, RedirectToRepo served by react-router.
  2. Home Link is a landing page.
  3. On Home page during componentMount, data needs to be fetched from backend API using Axios, redux-thunk.

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:-

  1. 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?
  2. 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?

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

Answers (1)

Linda Paiste
Linda Paiste

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.

  1. 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.

  2. 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

Related Questions