#100DaysOfCode Day42: Hacker News Clone — Front-End with React and Redux
By: Richard Russell
Tags: Redux, Reactjs, Web Developer, Programming, JavaScript

After going through several blog posts on the topic of how to grow as a computer programmer, I noticed that a project that is often mentioned is a Hacker News clone. And so, today I decided to get my hands dirty by following a tutorial that will teach me how to get a working Hacker News clone. Right now, I am still going to tackle the easy tutorials — React and Redux clones. In the next few projects, I’ll try and work on tutorials that will also teach me how to properly deploy websites and write production-like code.

Redux

Obviously, we are familiar with the definition of React.js — a JavaScript library that is used to make beautiful user interfaces. In this article, we will be putting a special focus on Redux.

According to their website, Redux is a Predictable State Container for JavaScript applications. They are predictable because Redux helps developers write applications that behave consistently, run in different environments (client, server, and native), and are easy to test. They are also centralized, as centralizing your application’s state and logic enables powerful capabilities like undo/redo, state, persistence, and much more. Redux is also debuggable, as their own Redux DevTools make it easy to trace when, where, why, and how your application’s state has changed. Redux’s architecture lets you log changes, use “time-traveling debugging”, and even send complete error reports to a server. Lastly, Redux is flexible, as it works with any UI layer, and has a large ecosystem of addons to fit the specific needs of the developer.

Initialize the Project

Like any other React project, we will be using create-react-app to initialize the project. To do this, open your terminal and write down the following command:

npx create-react-app hackernews-clone

The hackernews-clone will be the name of your project’s directory; you can give it any name you want.

Once create-react-app has finished with initializing the project, it is time to install several dependencies! I have been using npm as a package manager to install Node dependencies. However, today I will be trying a new package manager, yarn . Run the following command to install the required dependencies:

yarn add redux styled-components react-redux redux-logger redux-thunk axios

Afterwards, inside your project directory, head over to your src folder and make these new directories:

components : this folder will hold all of your React components (both container and presentational components)

: this folder will hold all of your React components (both container and presentational components) services : this directory will contain API calls using Axios and provide extended functionality to the application (such as adding Markdown support)

: this directory will contain API calls using Axios and provide extended functionality to the application (such as adding Markdown support) store : this stores all of the Redux logic to manage state

: this stores all of the Redux logic to manage state styles : this folder will hold the palette and style patterns that will be used in the components

: this folder will hold the palette and style patterns that will be used in the components utils : helper functions that can be reused throughout the application.

Since we are using the style-components dependency, we can delete index.css file and the App.css file and replace them with two new files in src/styles : global.js and palette.js .

Your palette.js file will hold the hexadecimal coloring code. They contain grouping of colors that will be used in the application to create the themes for the User Interface. The code is as follows:

export const colorsDark = {

background: '#272727',

backgroundSecondary: '#393C3E',

text: '#bfbebe',

textSecondary: '#868886',

border: '#272727',

};

The global.js file is used to generate our default base styling shared across the application. The code is as follows:

import { createGlobalStyles } from 'styled-components'; const GlobalStyles = createGlobalStyle`

*{

box-sizing: border-box;

} html, body {

font-family: Lato, Helvetica-Neue, Helvetica, Arial, sans-serif;

width: 100vw;

overflow-x: hidden;

margin: 0;

padding: 0;

} ul {

list-style: none;

padding: 0;

} a {

text-decoration: none;

&: visited{

color: inherit;

}

}

`; export default GlobalStyles;

Now, inside your components folder, create a new directory called App , and move your App.js , App.test.js and logo.svg default files to this directory. Rename App.js here into index.js . This will allow us to import from component/App .

Now, open src/index.js and update the content to use the new folder structure:

import React from 'react';

import ReactDOM from 'react-dom';

import App from 'components/App';

import GlobalStyles from 'styles/globals';

import * as serviceWorker form './serviceWorker'; const renderApp = () => {

ReactDOM.render(

<div>

<GlobalStyles />

<App />

</div>,

document.getElementById('root')

);

}; renderApp(); serviceWorker.unregister();

Adding Redux to the Application

Now, we will be implementing Redux. To do this, open src/store and create three files and a directory: index.js , reducer.js , middleware.js , and a app folder.

Inside index.js , we will create a configureStore function which is how we will initialize Redux in the application:

import { createStore } from 'redux';

import reducer from './reducer';

import middleware from './middleware'; const configureStore = initialState => {

const store = createStore(reducer, initialState, middleware);

return store;

}; export default configureStore;

Next, inside your reducer.js file, create the root reducer using combineReducers . This function combines all your reducer functions to build a single state tree:

import { combineReducer } from 'redux';

import app from './app/reducer'; const rootReducer = combineReducers({

app

}); export default rootReducer;

Next, create a middleware in the middleware.js file. The code is as follows:

import { applyMiddleware, compose } from 'redux';

import thunk from 'react-redux';

import { createLogger } from 'redux-logger'; const isProd = process.env.NODE_ENV === 'production';

const middlewareList = []; middlewareList.push(thunk);

if(!isProd){

middlewareList.push(createLogger());

} const middleware = compose(applyMiddleware(...middlewareList)); export default middleware;

Now, let’s create an action script that can toggle between day mode or night mode. Inside action.js , write the following script:

const NS = '@hnReader/app'; export const actionTypes = {

SET_THEME: `${NS}/SET_THEME`,

}; const actions = {

setTheme: (payload = {}) => ({type: actionTypes.SET_THEME, payload})

}; export default actions;

Finally, we can create our reducer:

// src/store/app/reducer.js

import { actionTypes } from './actions'; const getInitialState = () => {

theme: 'dark',

}); const app = (state = getInitialState(), {type, payload }) => {

switch(type){

case actionTypes.SET_THEME:

return{

...state,

...payload,

};

default:

return state;

}

}; export default app;

Now, return back to src/index.js and update the codebase to the following:

//..beginning of the code

import { Provider } from 'react-redux';

import configureStore from 'store';

import App from 'components/App'; const renderApp = () => {

const store = configureStore({}); if(process.env.NODE_ENV !== 'production'){

console.log('Initial state -> ');

console.log(store.getState());

} ReactDOM.render(

<Provider store={store}>

<div>

<GlobalStyles />

<App />

</div>

</Provider>,

document.getElementById('root')

);

); renderApp();

//... rest of the code

Build the UI with React and Styled Components

Now that Redux has been implemented, it is time to work on the UI with React. First, declare several style constants that will be used inside our components. Create a new file src/styles/mediaqueries.js to hold mobile-responsiveness constant. The code is as follows:

export const mobile = '@media only screen and (max-width: 480px)';

export const tablet = '@media only screen and (max-width: 768px)';

export const monitor = '@media only screen and (max-width: 1400px)';

Now, return back to index.js and update your code to the following:

import React, { Component } from 'react';

import { ThemeProvider } from 'styled-components';

import List from 'components/List';

import { colorsDark } from 'styles/palette'; import { Wrapper, Title } from './styles'; class App extends Component {

render(){

return(

<ThemeProvider theme={colorsDark}>

<div>

<Wrapper>

<Title>Hacker News Reader</Title>

<List />

</Wrapper>

</div>

</ThemeProvider>

);

}

} export default App;

Now, let’s add a few styles to our components. Create style.js inside the src/components/App folder:

import styled from 'styled-components';

import { tablet } from 'styles/mediaQueries'; export const Wrapper = styled.div`

width: 85%;

margin-left: auto;

margin-right: auto;

height: 100%;

overflow: hidden;

padding-bottom: 200px; ${tablet} {

width: 96%;

}

`; export const Title = styled.h1`

color: ${({theme}) => theme.textSecondary};

font-size: 20px;

font-weight: 300;

margin-top: 24px;

margin-bottom: 26px;

`;



Now, after making our styles, let us make our components. Fist, create a List component by inside src/components/List and add index.js and styles.js . Inside index.js , add the following:

import React from 'react';

import ListItem from 'components/ListItem';

import { ListWrapper } from './styles'; const List = () => (

<ListWrapper>

<ListItem />

</ListWrapper>

}; export default List;

Inside styles.js for List, add the following code:

import styled from 'styled-components'; export const ListWrapper = styled.ul`

background-color: ${({theme}) => theme.backgroundSecondary};

border-radius: 4px;

margin-left: auto;

margin-right: auto;

margin-botton: 20px;

display: flex;

flex-direction: column;

`;

Finally, make the ListItem component which will display the individual posts. To do this, create src/components/ListItem and also an index.js and style.js file inside.

Your index.js file should look like the following:

import React from 'react';

import { Item, Title, Host, ExternalLink, Description, CommentLink } from './styles'; const LINK_REL = 'nofollow noreferrer noopener'; const ListItem = () => {

return(

<Item>

<ExternalLink href="https://medium.com/cold-brew-code" rel={LINK_REL} target="_blank">

<Title>

The Developer Community <Host>(https://medium.com)</Host>

</Title>

</ExternalLink>

<Description>

9000 points by {' '}

<CommentLink href="#" rel={LINK_REL} target="_blank">

Test user

</CommentLink>{' '}

1 Hour ago { ' | ' }

<CommentLink href="#" rel={LINK_REL} target="_blank">

42 Comments

</CommentLink>

</Description>

</Item>

);

}; export default ListItem;



Lastly, add the following code for styles.js :

import styled from 'styled-components'; export const Item = styled.li`

border-botton: 1px solid ${({theme}) => theme.border};

padding: 14px 24px;

&:last-child{

border-bottom: none;

}

`; export const Title = styled.h3`

color: ${({theme}) => theme.text};

margin-top: 0'

margin-bottom: 6px;

font-weight: 400;

font-sie: 16px;

letter-spacing: 0.4px;

`; export const Host = styled.span`

color: ${({ theme }) => theme.textSecondary};

font-size: 12px;

`; export const ExternalLink = styled.a`

color: ${({ theme }) => theme.text};

display: flex;

width: 100%;

height: 100%;

flex-direction: row;

align-items: center;

text-decoration: none;

`; export const Description = styled.div`

font-size: 14px;

color: ${({ theme }) => theme.textSecondary};

`; export const CommentLink = styled.a`

color: ${({ theme }) => theme.textSecondary};



&:visited {

color: ${({ theme }) => theme.textSecondary};

}

`;

In the end, your code should look something like this:

If you are correct, then the background should also be black because of the night mode theme. However, as of now, I could not figure out why the background won’t render properly.