Skip to content

Instantly share code, notes, and snippets.

@BenJanecke
Last active March 5, 2018 09:03
Show Gist options
  • Save BenJanecke/a866106e7386d188334a5b00fdd4836d to your computer and use it in GitHub Desktop.
Save BenJanecke/a866106e7386d188334a5b00fdd4836d to your computer and use it in GitHub Desktop.
react-meetup-cpt
  1. 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>
)
  1. 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,
)));
@tseboho
Copy link

tseboho commented Mar 5, 2018

Thanks for the #2 snippet, @BenJanecke - its been really useful with cleaning up my scenes.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment