Skip to content

Instantly share code, notes, and snippets.

@eiriklv
Last active October 27, 2018 14:44
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save eiriklv/52af4cb05b4eb386b4f24f50a748fa34 to your computer and use it in GitHub Desktop.
Save eiriklv/52af4cb05b4eb386b4f24f50a748fa34 to your computer and use it in GitHub Desktop.
Idiomatic React and Redux
import { getSearchResults } from './services';
export const SEARCH_PENDING = 'SEARCH_PENDING';
export const SEARCH_SUCCEEDED = 'SEARCH_SUCCEEDED';
export const SEARCH_FAILED = 'SEARCH_FAILED';
export function searchPending() {
return {
type: SEARCH_PENDING
};
}
export function searchSucceeded(results) {
return {
type: SEARCH_SUCCEEDED,
results,
};
}
export function searchFailed(error) {
return {
type: SEARCH_FAILED,
error,
};
}
export async function performSearch(keyword) {
return async function(dispatch, getState) {
dispatch(searchPending());
try {
const results = await getSearchResults(keyword);
dispatch(searchSucceeded(results));
} catch (error) {
dispatch(searchFailed(error));
}
}
}
.App {
text-align: center;
}
.App-logo {
animation: App-logo-spin infinite 20s linear;
height: 10vmin;
}
.App-header {
background-color: #282c34;
min-height: 20vh;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
font-size: calc(10px + 2vmin);
color: white;
}
.App-link {
color: #61dafb;
}
@keyframes App-logo-spin {
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
}
import React, { Component } from 'react';
import './App.css';
import logo from './logo.svg';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import { performSearch } from './actions';
class App extends Component {
constructor(props) {
super(props);
this.searchInput = React.createRef();
}
handleSearch(event) {
const { performSearch } = this.props;
if (event.keyCode !== 13) {
return;
}
const keyword = this.searchInput.current.value;
performSearch(keyword);
}
render() {
const {
results,
isLoading,
error,
} = this.props;
return (
<div className="App">
<header className="App-header">
<img src={logo} className="App-logo" alt="logo" />
<p>Search Demo Application</p>
</header>
<div className="App-content-top">
Book search:
<input
ref={this.searchInput}
type="text"
onKeyDown={this.handleSearch.bind(this)}
/>
</div>
<div className="App-content">
<h3>Results:</h3>
{error && (
<p>Failed: {error.message}</p>
)}
{isLoading && (
<p>Loading..</p>
)}
{results && (
<ul>
{results.map((entry) => {
return (
<li key={entry.id}>Title: {entry.title}</li>
);
})}
</ul>
)}
</div>
</div>
);
}
}
function mapStateToProps(state) {
return {
isLoading: state.isLoading,
error: state.error,
results: state.results,
};
}
function mapDispatchToProps(dispatch) {
return bindActionCreators({
performSearch
}, dispatch);
}
export default connect(
mapStateToProps,
mapDispatchToProps
)(App);
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import { Provider } from 'react-redux';
import configureStore from './store';
const store = configureStore();
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root')
);
import {
SEARCH_PENDING,
SEARCH_SUCCEEDED,
SEARCH_FAILED,
} from './actions';
const initialState = {
results: [],
isLoading: false,
error: null,
};
export default function rootReducer(state = initialState, action) {
switch (action.type) {
case SEARCH_PENDING:
return {
...state,
results: [],
isLoading: true,
error: null,
};
case SEARCH_SUCCEEDED:
return {
...state,
results: action.results,
isLoading: false,
error: null,
};
case SEARCH_FAILED:
return {
...state,
isLoading: false,
error: action.error,
};
default:
return state;
}
}
/**
* Run this file with `node server.js`
*/
const express = require('express')
const app = express()
const cors = require('cors');
const port = 5000;
function delay(ms) {
return new Promise((resolve) => {
setTimeout(() => {
resolve();
}, ms);
});
}
const data = [
{ id: 1, title: 'SICP', description: 'Structure and Interpretation of Computer Programs is a textbook aiming to teach the principles of computer programming, such as abstraction in programming, metalinguistic abstraction, recursion, interpreters, and modular programming.' },
{ id: 2, title: 'JavaScript The Good Parts', description: 'Most programming languages contain good and bad parts, but JavaScript has more than its share of the bad, having been developed and released in a hurry before it could be refined.' },
];
app.use(cors());
app.get('/search', async (req, res) => {
await delay(3000);
res.send(data);
});
app.listen(port, () => console.log(`API listening on port ${port}!`))
import axios from 'axios';
const api = axios.create({
baseURL: 'http://localhost:5000',
timeout: 10000,
});
/**
* Search service
*/
export async function getSearchResults(keyword) {
const response = await api.get('/search', {
params: { keyword },
});
return response.data;
}
function delay(ms) {
return new Promise((resolve) => {
setTimeout(() => {
resolve();
}, ms);
});
}
/**
* Fake service example
* (waits 3 seconds before returning results)
*/
export async function getSearchResults(keyword) {
await delay(3000);
return [
{ id: 1, title: 'SICP', description: 'Structure and Interpretation of Computer Programs is a textbook aiming to teach the principles of computer programming, such as abstraction in programming, metalinguistic abstraction, recursion, interpreters, and modular programming.' },
{ id: 2, title: 'JavaScript The Good Parts', description: 'Most programming languages contain good and bad parts, but JavaScript has more than its share of the bad, having been developed and released in a hurry before it could be refined.' },
];
}
import { applyMiddleware, createStore } from 'redux';
import thunkMiddleware from 'redux-thunk';
import { composeWithDevTools } from 'redux-devtools-extension';
import rootReducer from './reducers';
export default function configureStore(preloadedState) {
const middlewares = [thunkMiddleware]
const middlewareEnhancer = applyMiddleware(...middlewares)
const enhancers = [middlewareEnhancer]
const composedEnhancers = composeWithDevTools(...enhancers)
const store = createStore(rootReducer, preloadedState, composedEnhancers)
return store;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment