- Stateless/Presentational
class ICauseHeadaches extends Component {
render() {
return <div />
}
}
const IRenderADiv = () => <div />;
class UserContainer extends Component {
constructor(props) {
super();
this.state = { users: {} };
}
addUser() {
return (user) => this.setState((state) => ({
...state,
users: {
...state.users,
[user.id]: user,
}
}));
}
updateUser() {
return (id, user) => this.setState((state) => ({
...state,
users: {
...state.users,
[user.id]: { ...state.user[id], ...user },
}
}));
}
removeUser() {
return (remove) => this.setState((state) => ({
...state,
users: Object.keys(state.users).reduce((newUsers, id) => id === remove ? newUsers : {
...newUsers,
[id]: users[id]
}, {}),
}));
}
render() {
return this.props.children({
users: Object.values(users),
addUser: this.addUser(),
updateUser: this.updateUser(),
removeUser: this.removeUser(),
});
}
}
class InputContainer extends Component {
constructor() {
super();
this.state = { value: "" };
}
change() {
return (e) => this.setState((state) => ({ ...state, value: e.target.value }));
}
render() {
return this.props.children({ value, change: this.change() });
}
}
const User = ({ user, remove }) => (
<>
<span>Name: {user.name}</span>
<button onClick={() => remove(user.id)}>Remove</button>
</>
);
const UsersPage = () => (
<UsersContainer>
{(users, addUser) => (
<div>
{users.map((user) => <User user={user} removeUser={removeUser} />)}
<InputContainer>
{({ value, change }) => (
<>
<input value={value} onChange={change} />
<button onClick={() => addUser({ name: value })}>Add</button>
</>
)}
</InputContainer>
</div>
)}
</UsersContainer>
)
- Loading/Normalizing Data
import { compose, lifecycle } from "recompose";
import { connect } from "react-redux";
import { withRouter } from "react-router";
const HydrateUsers = compose(
withRouter,
connect(({ users }) => ({
hydration: users.hydration,
}), { loadUsers }),
lifecycle({
componentDidMount() {
this.props.loadUsers(this.props.hydration);
}
}),
);
<Route path="/users" component={HydrateUsers(Users)} />
<Route path="/admin" component={HydrateUsers(Admin)} />
There's more to loading
users: {
items: {
"U001": { id: "U001", name: "Bob" },
"U002": { id: "U002", name: "Ruth" },
},
count: 2,
ids: ["U001", "U002"],
hydration: {
loading: true, // is this busy loading?
loaded: true, // has this already been loaded?
errors: [], // anything that might have broken
}
}
const loadUsers = (hydration) = async (dispatch) => {
try {
if (shouldLoad(hydration)) {
dispatch(setLoading(true));
dispatch(setLoaded(false));
const users = await fetchUsers();
dispatch(setUsers(users));
dispatch(setLoading(false));
dispatch(setLoaded(true));
}
} catch (e) {
dispatch(setErrors(e));
}
}
const usersReducer = combineReducers({
items(items = {}, action) {
switch (action.type) {
"SET_USERS":
return action.users.reduce((items, user) => ({
...items,
[user.id]: user
}, {});
}, items);
default:
return items;
}
},
count(count, action) {
switch (action.type) {
"SET_USERS":
return action.users.length;
default:
return count;
}
},
ids(ids, action) {
switch (action.type) {
"SET_USERS":
return action.users.map(user => user.id);
default:
return ids;
}
},
hydration: combineReducers({
loading(loading, action) {
switch (action.type) {
"SET_LOADING":
return action.loading;
default:
return loading;
}
},
loaded(loaded, action) {
switch (action.type) {
"SET_LOADED":
return action.loaded;
default:
return loaded;
}
},
errors(errors, action) {
switch (action.type) {
"SET_ERRORS":
return action.errors;
default:
return errors;
}
},
})
})
const items = (state) => Object.values(state.items);
const items = (state) => state.ids.map(id => items[id]);
const items = (state) => Object.keys(state.items).map(id => items[id]);
const usersForCurrentTeam = ({ teams, users }) => items(users).filter((user) => teams.active.id === user.teamId);
const shouldLoad = ({ loading, loaded, errors }) => !loading && !loaded && empty(errors);
const TeamUsers = connect((state) => ({ users: usersForCurrentTeam(state) }))(
({ children, ...props }) => children({ ...props, users })
);
<TeamUsers>
{(users) => (
<ul>
{users.map((user) => <User user={user} />}
</ul>
)}
</TeamUsers>
- Dom Turds
<body>
<div id="react-root"></div>
<script>
window.initialState = JSON.parse(<%= JSON.stringify(context.state) %>);
</script>
<script src="/app.js" />
</body>
app.get("/users", (req, res) => {
Users.all((users) => {
res.render('app', {
context: {
state: {
users: {
items: users.reduce((users, user) => ({ ...users, [user.id]: user }), {}),
count: users.length,
ids: users.map(user => user.id),
hydration: {
loading: false,
loaded: true,
errors: [],
}
}
}
}
})
});
});
const initalState = window.initalState || {};
export const store = createStore(reducers, initalState, composeEnhancers(applyMiddleware(
thunk,
)));
Thanks for the #2 snippet, @BenJanecke - its been really useful with cleaning up my scenes.