React Unplugged

React Unplugged

A Q&A about React and friends. Presented at JS.Talks.

Links from presentation:

- slides
- https://facebook.github.io/react
- http://isfiberreadyyet.com
- https://www.youtube.com/watch?v=ZCuYPiUIONs
- http://redux.js.org
- https://github.com/erikras/ducks-modular-redux
- http://graphql.org/
- http://www.apollodata.com/
- styling
- https://github.com/css-modules/css-modules
- https://github.com/styled-components/styled-components
- ui
- https://github.com/Semantic-Org/Semantic-UI-React
- https://github.com/callemall/material-ui
- https://github.com/react-bootstrap/react-bootstrap
- https://github.com/gabrielbull/react-desktop
- https://github.com/palantir/blueprint
- https://github.com/grommet/grommet
- forms
- https://github.com/seeden/react-form-controlled
- https://github.com/erikras/redux-form
- https://github.com/25th-floor/revalidation
- https://github.com/codecks-io/react-reform
- https://github.com/tannerlinsley/react-form
- https://github.com/prometheusresearch/react-forms
- testing
- https://github.com/facebook/jest
- https://github.com/jasmine/jasmine
- https://github.com/airbnb/enzyme/
- https://github.com/chaijs/chai
- https://github.com/producthunt/chai-enzyme
- https://github.com/mochajs/mocha
- https://github.com/sinonjs/sinon
- react
- https://github.com/JedWatson/react-select
- https://github.com/ayrton/react-key-handler
- https://github.com/twitter-fabric/velocity-react
- https://github.com/ianstormtaylor/slate
- https://github.com/andreypopp/react-textarea-autosize
- https://github.com/brigade/react-waypoint
- https://github.com/acdlite/recompose
- https://github.com/cloudflare/react-gateway
- https://github.com/One-com/react-truncate
- https://github.com/react-component/progress
- https://github.com/react-component
- redux
- https://github.com/elgerlambert/redux-localstorage
- https://github.com/gaearon/redux-thunk
- https://github.com/redux-saga/redux-saga
- https://github.com/markdalgleish/redux-analytics
- https://www.npmjs.com/package/reselect
- https://www.npmjs.com/package/normalizr
- utilities
- https://github.com/lodash/lodash
- https://github.com/moment/moment
- https://github.com/JedWatson/classnames
- https://github.com/kolodny/immutability-helper

7a0e72a6f55811246bb5d9a946fd2e49?s=128

Radoslav Stankov

August 13, 2017
Tweet

Transcript

  1. 3.
  2. 4.
  3. 5.
  4. 7.
  5. 8.
  6. 9.
  7. 14.
  8. 20.

    Virtual Dom React Element React Element React Element React Element

    React Element React Element React Element React Element React Element React Element DOM Element DOM Element DOM Element DOM Element DOM Element DOM Element DOM Element DOM Element DOM Element DOM Element
  9. 21.
  10. 22.
  11. 24.
  12. 31.
  13. 32.

    export default function SubmissionForm() { return ( <form action="/submission"> <h2>Speaker</h2>

    <div> <label htmlFor="speakerName">Name: </label> <input type="text" id="speakerName" name="speakerName" defaultValue="" /> </div> <div> <label htmlFor="speakerEmail">Email: </label> <input type="email" id="speakerEmail" name="speakerEmail" defaultValue="" /> </div> <input type="submit" value="Submit" /> </form> ); }
  14. 33.

    class ExampleForm extends React.Component { state = { value: ''

    } handleChange = (event) => { this.setState({value: event.target.value}); } handleSubmit = (event) => { e.preventDefault(); remoteCall(this.target.value)/ }; render() { return ( <form onSubmit={this.handleSubmit}> <input type="text" value={this.state.value} onChange={this.handleChange} /> <input type="submit" value="Submit" /> </form> ); } }
  15. 34.

    class ExampleForm extends React.Component { state = { value: ''

    } handleChange = (event) => { this.setState({value: event.target.value}); } handleSubmit = (event) => { e.preventDefault(); remoteCall(this.target.value)/ }; render() { return ( <form onSubmit={this.handleSubmit}> <input type="text" value={this.state.value} onChange={this.handleChange} /> <input type="submit" value="Submit" /> </form> ); } } Input change
  16. 35.

    class ExampleForm extends React.Component { state = { value: ''

    } handleChange = (event) => { this.setState({value: event.target.value}); } handleSubmit = (event) => { e.preventDefault(); remoteCall(this.target.value)/ }; render() { return ( <form onSubmit={this.handleSubmit}> <input type="text" value={this.state.value} onChange={this.handleChange} /> <input type="submit" value="Submit" /> </form> ); } } Input change 
 handleChange setState
  17. 36.

    class ExampleForm extends React.Component { state = { value: ''

    } handleChange = (event) => { this.setState({value: event.target.value}); } handleSubmit = (event) => { e.preventDefault(); remoteCall(this.target.value)/ }; render() { return ( <form onSubmit={this.handleSubmit}> <input type="text" value={this.state.value} onChange={this.handleChange} /> <input type="submit" value="Submit" /> </form> ); } } Input change 
 handleChange setState 
 render New value
  18. 39.
  19. 45.
  20. 46.
  21. 49.

    function action() { return async (dispatch, getState) => { //

    do ... async stuff dispatch({ type: ACTION_1 }); // do ... async stuff dispatch({ type: ACTION_2 }); // ...so on }; } redux-thunk
  22. 50.

    import { createStore, applyMiddleware } from 'redux'; import thunk from

    'redux-thunk'; import reducer from './reducers'; const store = createStore(rootReducer, applyMiddleware(thunk));
  23. 51.

    function changeFilter(filterName, api) { return async function(dispatch, getState) { if

    (getState().filter == filter) { return; } dispatch({ type: 'TODO/LOAD', filter: filter }); const todos = await api.loadTodos({filter: filter}); dispatch({ type: 'TODO/LOADED', todos: todos }); }; }
  24. 53.

    function changeFilter(filterName, api) { return async function(dispatch, getState) { if

    (getState().filter == filter) { return; } dispatch({ type: 'TODO/LOAD', filter: filter }); const todos = await api.loadTodos({filter: filter}); dispatch({ type: 'TODO/LOADED', todos: todos }); }; }
  25. 54.

    import { createStore, applyMiddleware } from 'redux'; import thunk from

    'redux-thunk'; import reducer from './reducers'; import apiClient from './utils/apiClient'; const api = apiClient(); const store = createStore(rootReducer, applyMiddleware(thunk.withExtraArgument(api));
  26. 55.

    function changeFilter(filterName, api) { return async function(dispatch, getState) { if

    (getState().filter == filter) { return; } dispatch({ type: 'TODO/LOAD', filter: filter }); const todos = await api.loadTodos({filter: filter}); dispatch({ type: 'TODO/LOADED', todos: todos }); }; }
  27. 56.

    function changeFilter(filterName) { return async function(dispatch, getState, api) { if

    (getState().filter == filter) { return; } dispatch({ type: 'TODO/LOAD', filter: filter }); const todos = await api.loadTodos({filter: filter}); dispatch({ type: 'TODO/LOADED', todos: todos }); }; }
  28. 57.

    class Filters extends React.Component { filterHander(filterName) { return () =>

    { this.props.dispatch(changeFilter(filterName)); }; } render() { return ( <div> {Object.keys(FILTERS).map((filter) => ( <button key={filter} onClick={this.filterHander(filter)}> {filter} </button> ))} </div> ); } }
  29. 58.

    import createStore from 'tests/support/createStore'; import factory from 'tests/support/factory'; describe(changeFilter.name, ()

    => { it('loads todos', () => { const store = createStore({ filter: 'all' }); const todo = factory.todo(); store.api.stub('loadTodos', [todo]); store.dispatch(changeFilter('completed')); expect(store.getState().filter).to.equal('completed'); expect(store.getState().todos).to.deep.equal([todo]); }); it('does not reload todos when filter is not changing', () => { const store = createStore({ filter: 'all' }); const todo = factory.todo(); store.api.stub('loadTodos', [todo]); store.dispatch(changeFilter('all')); expect(store.getState().todos).to.deep.equal([]); }); });
  30. 62.

    import { createReducer } from 'ph/redux'; import type { Dispatchable,

    Reducer } from 'ph/types/redux'; import type { Notice } from 'ph/types/Notice'; // Constants const RECEIVE_NOTICE = 'NOTICE/RECEIVE'; // Actions export function receiveNotice(notice: Notice): Dispatchable { return { type: RECEIVE_NOTICE, payload: { notice } }; } export function clearNotice(): Dispatchable { return { type: RECEIVE_NOTICE, payload: { notice: null } }; } // Reducer
 const reducer: Reducer<?Notice> = function createReducer(null, { [RECEIVE_NOTICE]: (state, { payload: { notice } }) => notice, });
 export default reducer;
  31. 63.

    import createTestStore from 'ph/createTestStore'; import { clearNotice, receiveNotice } from

    'ph/modules/notice'; describe('notice', () => { const getState = (store) => store.getState().notice; describe(receiveNotice.name, () => { it('sets a notice object', () => { const store = createTestStore(); const notice = { type: 'notice', message: 'submitted' } store.dispatch(receiveNotice(notice)); expect(getState(store)).to.deep.equal(notice); }); }); describe(clearNotice.name, () => { it('sets a notice object', () => { const store = createTestStore(); store.dispatch(clearNotice()); expect(getState(store)).to.equal(null); }); }); });
  32. 67.

    
 query { topic(id: 1) { id name description isFollowed

    image } } POST /graphql { "data": { "topic": { "id": 1, "name": "Tech", "description": "Hardware or "isFollowed": true, "image": "assets.producthun } } }
  33. 68.

    query { topic(id: 1) { id ...Item } } fragment

    Item on Topic { id name description ...Button ...Image } fragment Button on Topic { id name isFollowed } fragment Image on Topic { image } POST /graphql { "data": { "topic": { "id": 1, "name": "Tech", "description": "Hardware or "isFollowed": true, "image": "assets.producthun } } }
  34. 69.

    query { topic(id: 1) { id ...Item } } fragment

    Item on Topic { id name description ...Button ...Image } fragment Button on Topic { id name isFollowed } fragment Image on Topic { image } POST /graphql { "data": { "topic": { "id": 1, "name": "Tech", "description": "Hardware or "isFollowed": true, "image": "assets.producthun } } }
  35. 70.

    query { topic(id: 1) { id ...Item } } fragment

    Item on Topic { id name description ...Button ...Image } fragment Button on Topic { id name isFollowed } fragment Image on Topic { image } POST /graphql { "data": { "topic": { "id": 1, "name": "Tech", "description": "Hardware or "isFollowed": true, "image": "assets.producthun } } }
  36. 71.

    query { topic(id: 1) { id ...Item } } fragment

    Item on Topic { id name description ...Button ...Image } fragment Button on Topic { id name isFollowed } fragment Image on Topic { image } POST /graphql { "data": { "topic": { "id": 1, "name": "Tech", "description": "Hardware or "isFollowed": true, "image": "assets.producthun } } }
  37. 72.

    
 mutation FollowTopic($input) { followTopic(input: $input) { node { id

    isFollowed } } } POST /graphql { "data": {
 "followTopic": { "node": { "id": 1, "isFollowed": true } } } }
  38. 74.
  39. 75.
  40. 76.
  41. 79.

    // pages/Post/Discussion/Fragment.graphql
 fragment PostDiscussion on Post { comments { id

    created_at parent_comment_id state votes_count body user { id username name headline } } }
  42. 81.

    // pages/Post/PostPage.graphql #import "ph/lib/meta/MetaTags.graphql" #import "./Discussion/Fragment.graphql" #import "./Header/Fragment.graphql" #import "./Makers/Fragment.graphql"

    #import "./Media/Fragment.graphql" #import "./RecommendedPosts/Fragment.graphql" query PostPage($id: String!) { post(id: $id) { id
 ...MetaTags ...PostDiscussion ...PostHeader ...PostMedia ...PostRecommendedPosts } }
  43. 82.

    import QUERY from './Query.graphql'; import { graphql } from 'react-apollo';

    // ... export default graphql(QUERY, { options: ({ params: { id } }) => ({ variables: { id, }, }), });
  44. 83.
  45. 91.