Hacker News
This example shows how to use redux-query, redux-query-react, and redux-query-interface-superagent to build a basic Hacker News client. You can run this example in the browser by clicking the button below:
Entry point
index.js
import React from 'react';
import { render } from 'react-dom';
import HackerNews from './components/HackerNews';
import store from './store';
render(<HackerNews store={store} />, document.getElementById('root'));
Redux store
store.js
import { applyMiddleware, createStore, combineReducers } from 'redux';
import { entitiesReducer, queriesReducer, queryMiddleware } from 'redux-query';
import superagentInterface from 'redux-query-interface-superagent';
export const getQueries = state => state.queries;
export const getEntities = state => state.entities;
const reducer = combineReducers({
entities: entitiesReducer,
queries: queriesReducer,
});
const store = createStore(
reducer,
applyMiddleware(queryMiddleware(superagentInterface, getQueries, getEntities)),
);
export default store;
Query configs
query-configs/stories.js
export const topStoriesRequest = () => {
return {
url: `https://hacker-news.firebaseio.com/v0/topstories.json`,
transform: body => ({
// The server responds with an array of IDs
topStoryIds: body,
}),
update: {
topStoryIds: (prev, next) => {
// Discard previous `response` value (we don't need it anymore).
return next;
},
},
};
};
export const itemRequest = itemId => {
return {
url: `https://hacker-news.firebaseio.com/v0/item/${itemId}.json`,
transform: body => ({
// The server responds with the metadata for that item
itemsById: {
[itemId]: body,
},
}),
update: {
itemsById: (prev, next) => {
return {
...prev,
...next,
};
},
},
};
};
Selectors
selectors/stories.js
const emptyArray = [];
export const getTopStoryIds = state => {
return state.entities.topStoryIds || emptyArray;
};
export const getItem = (state, itemId) => {
return (state.entities.itemsById || {})[itemId];
};
Components
components/Item.js
import * as React from 'react';
import { useSelector } from 'react-redux';
import { useRequest } from 'redux-query-react';
import * as storyQueryConfigs from '../query-configs/stories';
import * as storySelectors from '../selectors/stories';
const Item = props => {
const [{ isPending }] = useRequest(storyQueryConfigs.itemRequest(props.itemId));
const item = useSelector(state => storySelectors.getItem(state, props.itemId));
return (
<li>
{isPending && 'Loading…'}
{!!item && (
<div>
<div>
<a href={item.url} target="_blank" rel="noopener noreferrer">
{item.title}
</a>
</div>
<div>
{item.score} points by{' '}
<a
href={`https://news.ycombinator.com/user?id=${item.by}`}
target="_blank"
rel="noopener noreferrer"
>
{item.by}
</a>
</div>
</div>
)}
</li>
);
};
export default Item;
components/TopStories.js
import * as React from 'react';
import { useSelector } from 'react-redux';
import { useRequest } from 'redux-query-react';
import Item from '../components/Item';
import * as storyQueryConfigs from '../query-configs/stories';
import * as storySelectors from '../selectors/stories';
const TopStories = props => {
useRequest(storyQueryConfigs.topStoriesRequest());
const topStoryIds = useSelector(storySelectors.getTopStoryIds);
return (
<ol>
{topStoryIds.slice(0, 30).map(itemId => (
<Item itemId={itemId} key={itemId} />
))}
</ol>
);
};
export default TopStories;
components/HackerNews.js
import * as React from 'react';
import { Provider } from 'react-redux';
import { Provider as ReduxQueryProvider } from 'redux-query-react';
import TopStories from '../components/TopStories';
import { getQueries } from '../store';
const HackerNews = props => {
return (
<Provider store={props.store}>
<ReduxQueryProvider queriesSelector={getQueries}>
<>
<h1>Hacker News</h1>
<TopStories />
</>
</ReduxQueryProvider>
</Provider>
);
};
export default HackerNews;