Skip to content

Instantly share code, notes, and snippets.

@nonlogos
Last active August 20, 2017 16:30
Show Gist options
  • Save nonlogos/5e91fdf65352a68d1a2c90d9f9967bfc to your computer and use it in GitHub Desktop.
Save nonlogos/5e91fdf65352a68d1a2c90d9f9967bfc to your computer and use it in GitHub Desktop.
New React and React Router 4
//actions
const SET_SEARCH_TERM = 'SET_SEARCH_TERM';
export function setSearchTerm(searchTerm) {
return {
type: SET_SEARCH_TERM,
payload: seartchTerm
};
}
//reducers
const setSearchTerm = (state, action) => Object.assign({}, state, { searchTerm: action.payload })
//root reducer
import {SET_SEARCH_TERM} from ...
const DEFAULT_STATE = {
searchTerm: ''
}
const rootReducer = (state = DEFAULT_STATE, action) => {
switch (action.type) {
case SET_SEARCH_TERM:
return setSearchTerm(state, action);
default:
return state;
}
};
export default rootReducer;
class Details extends Component {
constructor(props) {
super(props);
state = {
apiData: { rating: '' }
};
}
componentDidMount() {
axios
.get(`http://localhost:3000/${this.props.show.imdgID}`)
.then(response => this.setState({apiData: response.data}));
}
props: {
show: Show
};
render() {
const { title, description, year, poster, trailer } = this.props.show;
let ratingComponent;
if (this.state.apiData.rating) {
ratingComponent = <h3>{this.state.apiData.rating}</h3>;
} else {
ratingComponent = <Spinner />;
}
return (
<div className="details">
<header>
<h1>svideo</h1>
<section>
<h1>{title}</h1>
<h2>({Year})</h2>
{ratingComponent}
<img src={`/public/img/posters/${poster}`} alt=`poster for ${title}` />
<p>{description}</p>
</section>
<div>
<iframe
src={`blah blah`}
frameBorder="0"
allowFullScreen
title={`Trailer for ${title}`}
/>
</div>
</header>
</div>
);
}
}
import React from 'react';
const Details = (props) => (
<div className="details">
<pre><code>{JSON.stringify(props, null, 4)}</code></pre>
</div>
);
import React from 'react';
import { connect } from 'react-redux';
import { setSearchTerm } from './actionCreators';
//if it was a class we can use decorator
@connect(mapStateToProps);
const Landing = (props) => (
<div className="Landing">
<input onChange={props.handleSearchTermChange} value={props.searchTerm}
type="text" placeholder="Search" />
</div>
);
const mapStateToProps = (state) => ({ searchTerm: state.searchTerm })
const mapDispatchToProps = (dispatch) => ({
handleSearchTermChange(event) {
dispatch(setSearchTerm(event.target.value));
}
})
// if using decorator
export default Landing
export default connect(mapStateToProps, mapDispatchToProps)(Landing);
import React from 'react';
const details = props => {
// destructuring
const { title, description, year, poster, trailer } = props.show;
return (
<div className="details">
<header>
<h1>svideo</h1>
<section>
<h1>{title}</h1>
<h2>({Year})</h2>
<img src={`/public/img/posters/${poster}`} alt=`poster for ${title}` />
<p>{description}</p>
</section>
<div>
<iframe
src={`blah blah`}
frameBorder="0"
allowFullScreen
title={`Trailer for ${title}`}
/>
</div>
</header>
</div>
);
}
// types.js
export Show = {
title: string,
description: string,
year: string,
imdbID: string,
trailer: string,
poster: string
}
// regular component
import show from '../types.js'
props: {
shows: Array<Show>
}
import React from 'react';
import { shallow } from 'enzyme';
import preload from '../../data.json';
import ShowCard from '../ShowCard';
test('search should render correct amount of shows', () => {
const component = shallow(<Search />);
expect(component.find(ShowCard.length)).toEqual(preload.shows.length));
});
// xtest or xit or xdescribe will stop it from running
test('Search sould render correct amount of shows based on search term', () => {
const searchWord = 'black';
const component = shallow(<Search />);
component.find('input').simulate('change', {target: {value: searchWord}});
const showCount = preload.shows.filter(
show => `${show.title} ${show.description}`.toUpperCase().indexOf(searchWord.toUpperCase()) >= 0
).length;
expect(component.find(ShowCard).length).toEqual(showCount);
});
// or use describe to create a test suite
describe('Search', () => {
it('renders correctly', () => {
const component = shallow(<Search />);
expect(component).toMatchSnapshot();
});
it('should render correct amount of shows', () => {
const component = shallow(<Search />);
expect(component.find(ShowCard.length)).toEqual(preload.shows.length));
});
});
import React from 'react';
import ShowCard from './showCard';
import preload from '../data.json';
const Search = () => (
{preload.shows.map(show => <showCard key={show.id} {...show} />)} // with spread operator
);
export default Search;
// child nested component
import React from 'react';
import { string } from 'prop-types';
const ShowCard = props => (
<div className ='show-card'>
<img alt={`${props.title} Show Poster`} src={`/public/img/posters/${props.poster}`} />
<div>
<h3>{props.title}</h3>
<h4>({props.year})</h4>
<p>{props.description}</p>
</div>
</div>
);
ShowCard.propTypes ={
poster: string.isRequired,
title: string.isRequired,
year: string.isRequired,
description: string.isRequired,
};
export default ShowCard
// map and filter
<div>
{preload.shows
.filter(
show =>
`${show.title} ${show.description}`.toUpperCase().indexOf(this.state.searchTerm.toUpperCase()) >= 0
)
.map(show => <ShowCard key={show.imbdID} {...show} />)}
</div>
// ownProps is the props that's passed in from parent props
import { getAPIData } from './actionCreators';
componentDidMount() {
if (!this.props.rating) {
this.props.getAPIData();
}
}
const mapStateToProps = (state, ownProps) => {
const apiData = state.apiData[ownProps.show.imdbID] ? state.apiData[ownProps.show.imdbId]
: {};
return {
rating: apiData.rating
}
}
const mapDispatchToProps = (dispatch, ownProps) => ({
getAPIData() {
dispatch(getAPIData(ownProps.show.imdbID));
}
});
export default connect(mapStateToProps, mapDispatchToProps);
//reducers
export const SET_SEARCH_TERM = 'SET_SEARCH_TERM';
export const ADD_API_DATA = 'ADD_API_DATA';
import { combineReducers } from 'redux';
import { SET_SEARCH_TERM, ADD_API_DATA } from './actions';
const searchTerm = (state = '', action) => {
if (action.type === SET_SEARCH_TERM) return action.payload;
return state;
};
const apiData = (state = {}, action) => {
if (action.type === ADD_API_DATA){
returnn Object.assign({}, state, { [action.payload.imdbID]: action.payload })
}
return state;
}
const rootReducer = combineReducers({ searchTerm, apiData });
export default rootReducer;
// action creators
import axios from 'axios';
import { ADD_API_DATA } from './actions';
export function addAPIData(apiData) {
return {type: ADD_API_DATA, payload: apiData};
};
// thunk that returns a function/deferred action
export function getAPIDetails(imdbID) {
return (dispatch) => {
axios
.get(`http://localhost:3000/${imdbID}`)
.then(response => {
// dispatch the action back to redux
dispatch(addAPIData(response.data))
})
.catch(error => {
console.error('axios error', error); //eslint-disable-line no-console
});
};
}
import { BrowserRouter, Route, Switch } from 'react-router-dom';
import Landing from './Landing';
import Search from './Search';
import Details from './Details';
const FourOhFour = () => <h1>404</h1>
const App = () => (
<BrowserRouter>
<div className="app">
<Switch>
<Route exact path="/" component={Landing} />
<Route path="/search" component={Search} />
<Route path="details/:id" component={props => <Details show={preload.shows.find(show => props.match.params.id === show.imdbID)} {...props} />} />
<Route component={FourOhFour} />
</Switch>
</div>
</BrowserRouter>
);
// spinner.jsx
import React from 'react';
import sytled, { keyframs } from 'styled-components';
const spin = keyframes`
from {
transform: rotate(0deg);
} to {
transform: rotate(360deg);
}
`;
const Image = styled.img`
animation: ${spin} 4s infinite linear;
`;
const Spinner = () => <Image src="/public/img/loading.png" alt="loading indicator" />
export default Spinner;
//inside of constructor
this.state = {
searchTerm: ''
};
this.handleSearchTermChange = this.handleSearchTermChange.bind(this);
// JSX
<input onChange={this.handleSearchTermChange} value={this.state.searchTerm} type="text" placeholder="search" />
// class methods
handleSearchTermChange(event) {
this.setState({searchTerm: event.target.value});
}
// with the babel class property - don't need constructor
this.state = {
searchTerm: ''
};
handleSearchTermChange = event => {
this.setState({searchTerm: event.target.value});
}
import styled from 'styled-components';
const Wrapper = styled.div`
width: 32%;
border: 2px solid #333;
border-radius: 4px;
margin-bottom: 25px;
padding-right: 10px;
overflow: hidden;
`;
const Image = Styles.img`
width: 46%;
float: left;
margin-right: 10px;
`;
<Wrapper>
<image alt={`${props.title} Show Poster`} src={`/public/img/posters/${props.poster}`} />
<div>
<h3>{props.title}</h3>
<h4>({props.year})</h4>
<p>{props.description}</p>
</div>
</Wrapper>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment