Skip to content

Instantly share code, notes, and snippets.

@fat

fat/email.txt Secret

Created January 18, 2017 22:19
Show Gist options
  • Save fat/9ab5325ab39acfe242bc7849eb9512c4 to your computer and use it in GitHub Desktop.
Save fat/9ab5325ab39acfe242bc7849eb9512c4 to your computer and use it in GitHub Desktop.
nicolas email
Hey,
This is the redux video series that helped me learn how to use it: https://egghead.io/courses/getting-started-with-redux. And here's a section on react "stateless functions": https://facebook.github.io/react/docs/reusable-components.html#stateless-functions
Later you might want to play around with React Native
React Native for Web: https://github.com/necolas/react-native-web
Try it out here: http://codepen.io/necolas/pen/PZzwBR/?editors=0010
And I thought you might get some ideas from what we're doing for mobile.twitter.com, which is a bit like this...
Application design
This is the kind of directory structure we use to reflect the division of responsibilities. Pretty similar to what you already have.
src/
├── api/
├── components/
├── containers/
├── modules/
├── redux/
├── server/
├── screens/
├── client.js
├── index.js
├── routes.js
├── theme.js
api
The Twitter API client is here. We're moving towards using it on the client like this:
// in redux/createMiddleware.js an instance is created
import ApiClient from './api'
const apiClient = new ApiClient(config)
// in redux/modules/*.js the instance is available to thunks
const fetchPosts = (params) => (twitterApiClient) => {
return twitterApiClient.Users.fetchPosts({ cursor, userId }).then(doShit)
}
components
This is basically your "Bootstrap"-like component library. It doesn't know anything about your app state. Mainly pure components (no internal state). Only imports npm, 'modules', or 'components'. No importing from 'redux'. Probably quite a few components with props that are callbacks, e.g., onPressPlay, which the containers will provide.
containers
These are also known as "controller views". They glue together UI (components) and data (redux). They might also define a few layout styles and use private, pure components for finishing touches (e.g., adding some spacing between the posts in a list of posts).
Our containers are essentially broken into 2 parts that are easy to test separately.
1) a 'connect' function that converts redux state to react props
// connect.js
import * as Users from '../../redux/modules/users'
import { connect } from 'react-redux'
import logout from '../../modules/logout'
// we test these pure functions
export const mapStateToProps = (state, props) => {
const user = Users.selectById(state, props.userId)
return { logout, user }
}
export const mapDispatchToProps = {
fetchUser: Users.fetch
}
export default connect(mapStateToProps, mapDispatchToProps)
2) a component that receives the extra props from 'connect'
import connect from './connect'
import theme from '../../theme'
import UserCard from '../../components/UserCard'
import { View } from 'react-native'
import React, { Component, PropTypes } from 'react'
// we export the class for testing purposes, passing stubs for props
export class AccountContainer extends Component {
static propTypes = {
// these come from 'connect'
fetchUser: PropTypes.func.isRequired,
logout: PropTypes.func.isRequired,
user: PropTypes.object.isRequired,
// this is provided by the screen or route handler
userId: PropTypes.string.isRequired
};
render() {
const { user } = this.props;
return (
<View className={styles.root}>
<UserCard onLongPress={this.handleUserCardLongPress_} user={user} />
<View className={styles.divider} />
<Button onPress={this.handleLogout_}>Logout</Button>
</View>
);
}
// the equivalent to binding 'this' to a class method
handleLogout_ = (event) => {
event.preventDefault()
this.props.logout()
}
handleUserCardLongPress_ = () => {
fetchUser(this.props.userId)
}
}
const styles = StyleSheet.create({
root: {
padding: '1em'
},
divider: {
backgroundColor: theme.borderColor,
height: 1,
marginVertical: '1.5em'
}
})
// this is the connected class the app renders
export default connect(AccountContainer)
modules
This is where we keep our modules. Some of them could become open source packages, others are quite app-specific. Examples: error handling logic, image processing and resizing, timers, http client, dom utilities.
redux
All state is managed here, and all means of accessing and modifying state are controlled here. It only imports from 'api' and 'modules'.
"Selectors" are used to access data from the store without knowledge of the shape of the store data. "Action creators" are used to trigger changes to the state (which may initiate app re-renders).
Here's an example of a basic redux module:
// the key to which the reducer is assigned when the store is created using 'combinedReducers'
const GLOBAL_STATE_KEY = 'session';
// unique action name prefix
const action = (name) => `bumpers/${GLOBAL_STATE_KEY}/${name}`;
export const initialState = {
guestToken: false,
userId: undefined
};
export default function reducer(state = initialState, action) {
switch (action.type) {
case FETCH_GUEST_TOKEN_SUCCESS:
return {
...state,
guestToken: action.payload.guest_token
};
default:
return state;
}
}
// selectors
export const selectGuestToken = (state) => state[GLOBAL_STATE_KEY].guestToken;
export const selectLoggedInUserId = (state) => state[GLOBAL_STATE_KEY].userId;
// action creators and actions
export const FETCH_GUEST_TOKEN_SUCCESS = action('FETCH_GUEST_TOKEN_SUCCESS')
export const fetchGuestTokenSuccess = (payload) => ({ payload, type: FETCH_GUEST_TOKEN_SUCCESS })
// this is a thunk (redux-thunk)
export const fetchGuestToken = (params) => (dispatch, getState, apiClient) => {
return apiClient.Auth.requestGuestToken(params).then(
(payload) => dispatch(fetchGuestTokenSuccess(payload)),
(errors) => errorLogger(errors)
)
}
screens
Screens or scenes – these are top-level components the router delegates rendering to. They combine thins like 'containers' (like nav bar + profile), analytics, setting page meta data like document title (using react-document-meta), and may use url data to seed containers with their props.
routes.js
Maps routes to screens.
client.js
Glues together things like 'React.render', the router, routes.js, redux store
import { Provider } from 'react-redux'
import { browserHistory, Router } from 'some-router'
import configureRoutes from './configureRoutes.js'
import configureStore from './redux/configureStore'
import React from 'react'
import ReactDOM from 'react-dom'
const store = configureStore(initDataFromServer)
const routes = configureRoutes(store)
const application = (
<Provider store={store}>
<Router history={browserHistory} routes={routes}/>
</Provider>
)
const node = document.getElementById('react-root')
ReactDOM.render(application, node)
theme.js
A JS object with some theme variables for the app – colors, spacing units, font sizes, etc. You could feed bits of this into the postcss loaders (variables, media queries) to make them available as custom variables in css-land.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment