Challenge application using React + Cat API for Hearst Communications
- React
- Redux Toolkit
- Redux Persist
- Material-UI
- Typescript
- Lodash
- Date-fns
- Jest + RTL
- Eslint + Prettier + Editorconfig
- Run local at port 3000:
npm run start
- Run tests with jest and coverage:
npm run test
- Deploy on Github Pages:
npm run deploy
- Run linting:
npm run lint:fix
- Get cat data from a public API (https://thecatapi.com/)
- Favourite a cat
- Upload a cat image
- Sort and Filter cat list
- Cache request for 30 minutes
- Lazy load images
- Persist data after reloads
- (Storage): Using redux-toolkit to store all data, divided into 3 reducers: Cat API (catSlice), filters/sort options(userPreferencesSlice) and upload images (uploadSlice)
const rootReducer = combineReducers({
cat: catReducer,
userPreference: userPreferenceReducer,
upload: uploadReducer,
})
- (Persist): Using redux-persist to store data into a local database and even after a page reloads, the data is avaliable to the user
import storage from 'redux-persist/lib/
storage';
const persistConfig = {
key: 'root',
storage,
}
const persistedReducer = persistReducer(persistConfig, rootReducer)
- (Lazy Load): Using react-lazy-load-image-component to lazy load images to increase performance, so when user scrolls down, the browser download the images
<LazyLoadImage
src={cardMediaImage}
width="100%"
height={expanded ? '500' : '200'}
placeholder={<Skeleton variant="rectangular" width={270} height={360} />}
loading="lazy"
style={{ objectFit: expanded ? 'contain' : 'cover' }}
/>
- (Cache): When user initially request the cat data from API, I save the current time of the user, so after 30 minutes, he will be using the cached data from redux instead of request again to the server
// on catSlice
.addCase(getBreeds.fulfilled, (state, action) => {
state.status = 'idle'
state.breeds = action.payload
state.cacheExpiresDate = new Date()
})
// using this function to calculate if is expired or not
export const selectShouldRevalidateData = (state: RootState) => {
const { cacheExpiresDate } = state.cat
const formatDate = typeof cacheExpiresDate === 'string'
? new Date(cacheExpiresDate)
: cacheExpiresDate
const now = new Date()
const expiration = addMinutes(formatDate, CACHE_EXPIRATION_MINUTES)
return isBefore(expiration, now)
}
// on Home.page.tsx
useEffect(() => {
if (breeds.length === 0 || shouldRevalidateData) {
dispatch(getBreeds())
dispatch(getFavourites())
}
}, [])