Contact Diagram




Read / Fetch



Constant

  • File: app\containers\SampleFullstackApps\Contact\reducers\contactConstants.js
  • Define constant:
                      
    export const FETCH_CONTACT = 'FETCH_CONTACT';
    export const FETCH_CONTACT_SUCCESS = 'FETCH_CONTACT_SUCCESS';
    export const FETCH_CONTACT_FAILED = 'FETCH_CONTACT_FAILED';
                      
                    

Actions

  • File: app\containers\SampleFullstackApps\Contact\reducers\contactActions.js
  • Put each constant in actions
                      
    /* Load */
    export const loadContactSuccess = payload => ({
      type: types.FETCH_CONTACT_SUCCESS,
      payload,
    });
                      
                    

Saga

  • File: app\containers\SampleFullstackApps\Contact\reducers\contactSagas.js
  • Call load task action in saga's function
                      
    function* read() {
      const channel = yield call(subscribe);
      while (true) {
        const action = yield take(channel);
        yield put(action);
      }
    }
    
    function* watchContact() {
      while (true) {
        contactList.path = 'contacts/';
        const job = yield fork(read);
    
        yield take([FETCH_CONTACT]);
        yield cancel(job);
      }
    }
                      
                    
  • Subscribe from models
                      
    import ContactModels from '../models';
    import Init from '../models/init';
    
    import { loadContactSuccess } from './contactActions';
    
    const contactList = new ContactModels({
      onLoad: loadContactSuccess,
    }, Init);
    
    function subscribe() {
      return eventChannel(emit => contactList.subscribe(emit));
    }
    
                      
                    

Reducer

  • File: app\containers\SampleFullstackApps\Contact\reducers\contactReducer.js
  • Put all payload action with constant FETCH_CONTACT_SUCCESS in reducer state. So the font-end part (React components) can get the state to modify ui or contents.
                      
    case FETCH_CONTACT_SUCCESS:
      return state.withMutations((mutableState) => {
        const items = fromJS(action.payload);
        mutableState
          .set('contactList', new List(items))
          .set('loading', false);
      });
                      
                    

Container

  • File: \app\containers\SampleFullstackApps\Contact\index.js
  • Load state data to container as prop
                      
    const { dataContact } = this.props;
    
    const mapStateToProps = state => ({
      dataContact: state.getIn([reducer, 'contactList']),
    });
    
    return {
      <ContactList
        addFn
        total={dataContact.size}
        addContact={add}
        clippedRight
        itemSelected={itemSelected}
        dataContact={dataContact}
        showDetail={showDetail}
        search={search}
        keyword={keyword}
        loading={loading}
      />
    }
                      
                    

Components

  • File: app\components\Contact\ContactList.js
  • Populate dataContact prop to component
                      
    const getItem = dataArray => dataArray.map(data => {
      const index = dataContact.indexOf(data);
      if (data.get('name').toLowerCase().indexOf(keyword) === -1) {
        return false;
      }
      return (
        <ListItem
          button
          key={data.get('key')}
          className={index === itemSelected ? classes.selected : ''}
          onClick={() => showDetail(data)}
        >
          <Avatar alt={data.get('name')} src={data.get('avatar')} className={classes.avatar} />
          <ListItemText primary={data.get('name')} secondary={data.get('title')} />
        </ListItem>
      );
    });
                      
                    


Create



Constant

  • File: app\containers\SampleFullstackApps\Contact\reducers\contactConstants.js
  • Define constant
                      
    export const CREATE_CONTACT = 'CREATE_CONTACT';
    export const CREATE_CONTACT_SUCCESS = 'CREATE_CONTACT_SUCCESS';
    export const CREATE_CONTACT_FAILED = 'CREATE_CONTACT_FAILED';                  
                      
                    

Actions

  • File: app\containers\SampleFullstackApps\Contact\reducers\contactActions.js
  • Put each constant in actions
                      
    export const createAction = payload => ({
      type: types.CREATE_CONTACT,
      payload,
    });
    
    export const createActionSuccess = payload => ({
      type: types.CREATE_CONTACT_SUCCESS,
      payload,
    });
    
    export const createActionFailed = error => ({
      type: types.CREATE_CONTACT_FAILED,
      payload: { error },
    });                
                      
                    

Saga

  • File: app\containers\SampleFullstackApps\Contact\reducers\contactSagas.js
  • Define create models
                      
    const createContact = write.bind(null, contactList, contactList.push, createActionFailed);             
                      
                    
  • Call create contact action in saga's function
                      
    function* watchCreateContact() {
      while (true) {
        const { payload } = yield take(CREATE_CONTACT);
        yield fork(createContact, payload);
      }
    }           
                      
                    

Reducer

  • File: app\containers\SampleFullstackApps\Contact\reducers\todoReducer.js
  • Put all payload action with constant CREATE_CONTACT_SUCCESS in reducer state. So the font-end part (React components) can get the state to modify ui or contents.
                      
    case CREATE_CONTACT_SUCCESS:
      return state.withMutations((mutableState) => {
        const items = action.payload;
        mutableState.set('contactList', state.get('contactList').unshift(items));
        mutableState
          .set('formValues', Map())
          .set('avatarInit', '')
          .set('openFrm', false)
          .set('notifMsg', notif.saved);
      });        
                      
                    

Container

  • File: app\containers\SampleFullstackApps\Contact\index.js
  • Dispatch create action in container
                      
    import { createAction } from './reducers/contactActions';    
    
    const constDispatchToProps = dispatch => ({
      create: bindActionCreators(createAction, dispatch),
    });
    
    return (
      <AddContact
        addContact={add}
        openForm={open}
        closeForm={close}
        submit={this.submitContact}
        avatarInit={avatarInit}
        processing={uploadSubmiting}
      />
    )
                      
                    

Component

  • File: app\components\Contact\AddContactForm.js
  • Get and submit New Contact
                      
    sendValues = (values) => {
      const { submit } = this.props;
      const { img } = this.state;
      setTimeout(() => {
        submit(values, img);
        this.setState({ img: null });
      }, 500);
    }
    
    return (
      <form onSubmit={this.handleSubmit} noValidate>
         ...
      </form>
    )
                      
                    


Update


Constant


  • File: app\containers\SampleFullstackApps\Contact\reducers\contactConstants.js
  • Define constant
                      
    export const UPDATE_CONTACT = 'UPDATE_CONTACT';
    export const UPDATE_CONTACT_SUCCESS = 'UPDATE_CONTACT_SUCCESS';
    export const UPDATE_CONTACT_FAILED = 'UPDATE_CONTACT_FAILED';
                      
                    

  • File: app\containers\SampleFullstackApps\Contact\reducers\contactActions.js
  • Put each constant in actions
                      
    export const updateAction = (contact, changes) => ({
      type: types.UPDATE_CONTACT,
      payload: { contact, changes },
    });
    
    export const updateActionSuccess = payload => ({
      type: types.UPDATE_CONTACT_SUCCESS,
      payload
    });
    
    export const updateActionFailed = error => ({
      type: types.UPDATE_CONTACT_FAILED,
      payload: { error },
    });
                      
                    

Saga

  • File: app\containers\SampleFullstackApps\Contact\reducers\contactSagas.js
  • Define update models
                      
    const updateContact = write.bind(null, contactList, contactList.update, updateActionFailed);
                      
                    
  • Call update contact action in saga's function
                      
    function* watchUpdateContact() {
      while (true) {
        const { payload } = yield take(UPDATE_CONTACT);
        yield fork(updateContact, payload.contact.key, payload.changes);
      }
    }
                      
                    

Reducer

  • File: app\containers\SampleFullstackApps\Contact\reducers\todoReducer.js
  • Put all payload action with constant UPDATE_CONTACT_SUCCESS in reducer state. So the font-end part (React components) can get the state to modify ui or contents.
                      
    case UPDATE_CONTACT_SUCCESS:
      return state.withMutations((mutableState) => {
        mutableState.set(
          'contactList',
          state.get('contactList').map(contact => (contact.key === action.payload.key ? action.payload : contact))
        );
        mutableState
          .set('formValues', Map())
          .set('avatarInit', '')
          .set('openFrm', false)
          .set('notifMsg', notif.updated);
      });
                      
                    

Container

  • File: app\containers\SampleFullstackApps\Contact\index.js
  • Dispatch update action in container. Basically create and update has same value and data type, so we create condition to handle it.
                      
    import { updateAction } from './reducers/contactActions';
    
    const constDispatchToProps = dispatch => ({
      update: bindActionCreators(updateAction, dispatch),
    });
    
    submitContact = (item, avatar) => {
        const {
          create, update,
          dataContact, itemSelected, selectedId,
        } = this.props;
    
        const value = item.toJS();
        this.setState({ uploadSubmiting: true });
    
        if (value.key === selectedId) { // Update contact
          const contact = dataContact.get(itemSelected);
          uploadImg(avatar, async (url) => {
            value.avatar = url || null;
            update(contact, value);
            this.setState({ uploadSubmiting: false });
          });
        } else { // Create new contact
          uploadImg(avatar, async (url) => {
            value.avatar = url || null;
            create(value);
            this.setState({ uploadSubmiting: false });
          });
        }
      }
      
    return (
      <AddContact
        addContact={add}
        openForm={open}
        closeForm={close}
        submit={this.submitContact}
        avatarInit={avatarInit}
        processing={uploadSubmiting}
      />
    )
                      
                    

Component

  • File: app\components\Contact\AddContactForm.js
  • Get and submit Updated Contact
                      
    sendValues = (values) => {
      const { submit } = this.props;
      const { img } = this.state;
      setTimeout(() => {
        submit(values, img);
        this.setState({ img: null });
      }, 500);
    }
    
    return (
      <form onSubmit={this.handleSubmit} noValidate>
         ...
      </form>
    )
                      
                    


Delete


Constant

  • File: app\containers\SampleFullstackApps\Contact\reducers\contactConstants.js
  • Define constant
                      
    export const DELETE_CONTACT = 'DELETE_CONTACT';
    export const DELETE_CONTACT_SUCCESS = 'DELETE_CONTACT_SUCCESS';
    export const DELETE_CONTACT_FAILED = 'DELETE_CONTACT_FAILED';
                      
                    

Actions

  • File: app\containers\SampleFullstackApps\Contact\reducers\contactActions.js
  • Put each constant in actions
                      
    /* Delete */
    export const deleteAction = payload => ({
      type: types.DELETE_CONTACT,
      payload,
    });
    
    export const deleteActionSuccess = payload => ({
      type: types.DELETE_CONTACT_SUCCESS,
      payload,
    });
    
    export const deleteActionFailed = error => ({
      type: types.DELETE_CONTACT_FAILED,
      payload: { error },
    });
                      
                    

Saga

  • File: app\containers\SampleFullstackApps\Contact\reducers\contactSagas.js
  • Define update models
                      
    const removeContact = write.bind(null, contactList, contactList.remove, deleteActionFailed);
                      
                    
  • Call remove contact action in saga's function
                      
    function* watchRemoveContact() {
      while (true) {
        const { payload } = yield take(DELETE_CONTACT);
        yield fork(removeContact, payload.key);
      }
    }
                      
                    

Reducer

  • File: app\containers\SampleFullstackApps\Contact\reducers\todoReducer.js
  • Put all payload action with constant DELETE_CONTACT_SUCCESS in reducer state. So the font-end part (React components) can get the state to modify ui or contents.
                      
    return state.withMutations((mutableState) => {
        mutableState
          .set(
            'contactList',
            state.get('contactList').filter(contact => contact.key !== action.payload.key)
          )
          .set('notifMsg', notif.removed);
      });
                      
                    

Container

  • File: app\containers\SampleFullstackApps\Contact\index.js
  • Dispatch remove action in container
                      
    import { deleteAction } from './reducers/contactActions';
    
    const constDispatchToProps = dispatch => ({
      remove: bindActionCreators(deleteAction, dispatch),
    });
    
    return (
      <ContactDetail
        showMobileDetail={showMobileDetail}
        hideDetail={hideDetail}
        dataContact={dataContact}
        itemSelected={itemSelected}
        edit={edit}
        remove={remove}
        favorite={update}
        loading={loading}
      />
    )
    
                      
                    

Component

  • File: app/components/Contact/ContactDetail.js
  • Call remove function by onClick remove button
                      
    deleteContact = (item) => {
      const { remove } = this.props;
      remove(item);
      this.setState({ anchorElOpt: null });
    }
    
    return (
      <MenuItem onClick={() => this.deleteContact(dataContact.get(itemSelected))}>
        <FormattedMessage {...messages.delete} />
      </MenuItem>
    )