diff --git a/frontend/src/components/TopicCard/Links.js b/frontend/src/components/TopicCard/Links.js
index 93bebd82..1eb85a44 100644
--- a/frontend/src/components/TopicCard/Links.js
+++ b/frontend/src/components/TopicCard/Links.js
@@ -1,7 +1,7 @@
 import React, { PropTypes, Component } from 'react'
 
-import DataModel from '../../Metamaps/DataModel'
-import Visualize from '../../Metamaps/Visualize'
+import Metacode from './Metacode'
+import Permission from './Permission'
 
 const inmaps = (topic) => {
   const inmapsArray = topic.get('inmaps') || []
@@ -31,128 +31,6 @@ const inmaps = (topic) => {
 
 // TODO all of these should be largely turned into passed-in callbacks
 const bindShowCardListeners = (topic, ActiveMapper) => {
-  var authorized = topic.authorizeToEdit(ActiveMapper)
-  var selectingMetacode = false
-  // attach the listener that shows the metacode title when you hover over the image
-  $('.showcard .metacodeImage').mouseenter(function() {
-    $('.showcard .icon').css('z-index', '4')
-    $('.showcard .metacodeTitle').show()
-  })
-  $('.showcard .linkItem.icon').mouseleave(function() {
-    if (!selectingMetacode) {
-      $('.showcard .metacodeTitle').hide()
-      $('.showcard .icon').css('z-index', '1')
-    }
-  })
-
-  var metacodeLiClick = function() {
-    selectingMetacode = false
-    var metacodeId = parseInt($(this).attr('data-id'))
-    var metacode = DataModel.Metacodes.get(metacodeId)
-    $('.CardOnGraph').find('.metacodeTitle').html(metacode.get('name'))
-      .append('<div class="expandMetacodeSelect"></div>')
-      .attr('class', 'metacodeTitle mbg' + metacode.id)
-    $('.CardOnGraph').find('.metacodeImage').css('background-image', 'url(' + metacode.get('icon') + ')')
-    topic.save({
-      metacode_id: metacode.id
-    })
-    Visualize.mGraph.plot()
-    $('.metacodeSelect').hide().removeClass('onRightEdge onBottomEdge')
-    $('.metacodeTitle').hide()
-    $('.showcard .icon').css('z-index', '1')
-  }
-
-  var openMetacodeSelect = function(event) {
-    var TOPICCARD_WIDTH = 300
-    var METACODESELECT_WIDTH = 404
-    var MAX_METACODELIST_HEIGHT = 270
-
-    if (!selectingMetacode) {
-      selectingMetacode = true
-
-      // this is to make sure the metacode
-      // select is accessible onscreen, when opened
-      // while topic card is close to the right
-      // edge of the screen
-      var windowWidth = $(window).width()
-      var showcardLeft = parseInt($('.showcard').css('left'))
-      var distanceFromEdge = windowWidth - (showcardLeft + TOPICCARD_WIDTH)
-      if (distanceFromEdge < METACODESELECT_WIDTH) {
-        $('.metacodeSelect').addClass('onRightEdge')
-      }
-
-      // this is to make sure the metacode
-      // select is accessible onscreen, when opened
-      // while topic card is close to the bottom
-      // edge of the screen
-      var windowHeight = $(window).height()
-      var showcardTop = parseInt($('.showcard').css('top'))
-      var topicTitleHeight = $('.showcard .title').height() + parseInt($('.showcard .title').css('padding-top')) + parseInt($('.showcard .title').css('padding-bottom'))
-      var distanceFromBottom = windowHeight - (showcardTop + topicTitleHeight)
-      if (distanceFromBottom < MAX_METACODELIST_HEIGHT) {
-        $('.metacodeSelect').addClass('onBottomEdge')
-      }
-
-      $('.metacodeSelect').show()
-      event.stopPropagation()
-    }
-  }
-
-  var hideMetacodeSelect = function() {
-    selectingMetacode = false
-    $('.metacodeSelect').hide().removeClass('onRightEdge onBottomEdge')
-    $('.metacodeTitle').hide()
-    $('.showcard .icon').css('z-index', '1')
-  }
-
-  if (authorized) {
-    $('.showcard .metacodeTitle').click(openMetacodeSelect)
-    $('.showcard').click(hideMetacodeSelect)
-    $('.metacodeSelect > ul > li').click(function(event) {
-      event.stopPropagation()
-    })
-    $('.metacodeSelect li li').click(metacodeLiClick)
-  }
-
-  var hidePermissionSelect = function() {
-    selectingPermission = false
-    $('.showcard .yourTopic .mapPerm').removeClass('minimize') // this line flips the pull up arrow to a drop down arrow
-    $('.showcard .permissionSelect').remove()
-  }
-
-  var permissionLiClick = function(event) {
-    selectingPermission = false
-    var permission = $(this).attr('class')
-    topic.save({
-      permission: permission,
-      defer_to_map_id: null
-    })
-    $('.showcard .mapPerm').removeClass('co pu pr').addClass(permission.substring(0, 2))
-    hidePermissionSelect()
-  }
-
-  var openPermissionSelect = function(event) {
-    if (!selectingPermission) {
-      selectingPermission = true
-      $(this).addClass('minimize') // this line flips the drop down arrow to a pull up arrow
-      if ($(this).hasClass('co')) {
-        $(this).append('<ul class="permissionSelect"><li class="public"></li><li class="private"></li></ul>')
-      } else if ($(this).hasClass('pu')) {
-        $(this).append('<ul class="permissionSelect"><li class="commons"></li><li class="private"></li></ul>')
-      } else if ($(this).hasClass('pr')) {
-        $(this).append('<ul class="permissionSelect"><li class="commons"></li><li class="public"></li></ul>')
-      }
-      $('.showcard .permissionSelect li').click(permissionLiClick)
-      event.stopPropagation()
-    }
-  }
-  // ability to change permission
-  var selectingPermission = false
-  if (topic.authorizePermissionChange(ActiveMapper)) {
-    $('.showcard .yourTopic .mapPerm').click(openPermissionSelect)
-    $('.showcard').click(hidePermissionSelect)
-  }
-
   $('.links .mapCount').unbind().click(function(event) {
     $('.mapCount .tip').toggle()
     $('.showcard .hoverTip').toggleClass('hide')
@@ -181,22 +59,18 @@ class Links extends Component {
   }
 
   render = () => {
-    const { topic } = this.props
+    const { topic, ActiveMapper } = this.props
     const topicId = topic.isNew() ? topic.cid : topic.id // TODO should we really be using cid here?!?
-    const permission = topic.get('permission')
-    // the code for this is stored in /views/main/_metacodeOptions.html.erb
-    const metacodeSelectHTML = $('#metacodeOptions').html()
+    const metacode = topic.getMetacode()
 
     return (
       <div className="links">
-        <div className="linkItem icon">
-          <div className={`metacodeTitle mbg${topic.get('metacode_id')}`}>
-            {topic.getMetacode().get('name')}
-            <div className="expandMetacodeSelect"></div>
-          </div>
-          <div className="metacodeImage" style={{backgroundImage: `url(${topic.getMetacode().get('icon')})`}} title="click and drag to move card"></div>
-          <div className="metacodeSelect" dangerouslySetInnerHTML={{ __html: metacodeSelectHTML }} />
-        </div>
+        <Metacode
+          topic={topic}
+          metacode={metacode}
+          ActiveMapper={ActiveMapper}
+          updateTopic={this.props.updateTopic}
+        />
         <div className="linkItem contributor">
           <a href={`/explore/mapper/${topic.get('user_id')}`} target="_blank"><img src={topic.get('user_image')} className="contributorIcon" width="32" height="32" /></a>
           <div className="contributorName">{topic.get('user_name')}</div>
@@ -205,14 +79,18 @@ class Links extends Component {
           <div className="mapCountIcon"></div>
           {topic.get('map_count').toString()}
           <div className="hoverTip">Click to see which maps topic appears on</div>
-          <div className="tip"><ul>{inmaps(this.props.topic)}</ul></div>
+          <div className="tip"><ul>{inmaps(topic)}</ul></div>
         </div>
         <a href={`/topics/${topicId}`} target="_blank" className="linkItem synapseCount">
           <div className="synapseCountIcon"></div>
           {topic.get('synapse_count').toString()}
           <div className="tip">Click to see this topics synapses</div>
         </a>
-        <div className={`linkItem mapPerm ${permission.substring(0, 2)}`} title={permission}></div>
+        <Permission
+          topic={topic}
+          ActiveMapper={ActiveMapper}
+          updateTopic={this.props.updateTopic}
+        />
         <div className="clearfloat"></div>
       </div>
     )
@@ -221,7 +99,8 @@ class Links extends Component {
 
 Links.propTypes = {
   topic: PropTypes.object, // backbone object
-  ActiveMapper: PropTypes.object
+  ActiveMapper: PropTypes.object,
+  updateTopic: PropTypes.func
 }
 
 export default Links
diff --git a/frontend/src/components/TopicCard/Metacode.js b/frontend/src/components/TopicCard/Metacode.js
new file mode 100644
index 00000000..574759f1
--- /dev/null
+++ b/frontend/src/components/TopicCard/Metacode.js
@@ -0,0 +1,124 @@
+import React, { PropTypes, Component } from 'react'
+
+import DataModel from '../../Metamaps/DataModel'
+import Visualize from '../../Metamaps/Visualize'
+
+// TODO all of these should be largely turned into passed-in callbacks
+const bindShowCardListeners = (topic, ActiveMapper) => {
+  var authorized = topic.authorizeToEdit(ActiveMapper)
+  var selectingMetacode = false
+  // attach the listener that shows the metacode title when you hover over the image
+  $('.showcard .metacodeImage').mouseenter(function() {
+    $('.showcard .icon').css('z-index', '4')
+    $('.showcard .metacodeTitle').show()
+  })
+  $('.showcard .linkItem.icon').mouseleave(function() {
+    if (!selectingMetacode) {
+      $('.showcard .metacodeTitle').hide()
+      $('.showcard .icon').css('z-index', '1')
+    }
+  })
+
+  var metacodeLiClick = function() {
+    selectingMetacode = false
+    var metacodeId = parseInt($(this).attr('data-id'))
+    var metacode = DataModel.Metacodes.get(metacodeId)
+    $('.CardOnGraph').find('.metacodeTitle').html(metacode.get('name'))
+      .append('<div class="expandMetacodeSelect"></div>')
+      .attr('class', 'metacodeTitle mbg' + metacode.id)
+    $('.CardOnGraph').find('.metacodeImage').css('background-image', 'url(' + metacode.get('icon') + ')')
+    topic.save({
+      metacode_id: metacode.id
+    })
+    Visualize.mGraph.plot()
+    $('.metacodeSelect').hide().removeClass('onRightEdge onBottomEdge')
+    $('.metacodeTitle').hide()
+    $('.showcard .icon').css('z-index', '1')
+  }
+
+  var openMetacodeSelect = function(event) {
+    var TOPICCARD_WIDTH = 300
+    var METACODESELECT_WIDTH = 404
+    var MAX_METACODELIST_HEIGHT = 270
+
+    if (!selectingMetacode) {
+      selectingMetacode = true
+
+      // this is to make sure the metacode
+      // select is accessible onscreen, when opened
+      // while topic card is close to the right
+      // edge of the screen
+      var windowWidth = $(window).width()
+      var showcardLeft = parseInt($('.showcard').css('left'))
+      var distanceFromEdge = windowWidth - (showcardLeft + TOPICCARD_WIDTH)
+      if (distanceFromEdge < METACODESELECT_WIDTH) {
+        $('.metacodeSelect').addClass('onRightEdge')
+      }
+
+      // this is to make sure the metacode
+      // select is accessible onscreen, when opened
+      // while topic card is close to the bottom
+      // edge of the screen
+      var windowHeight = $(window).height()
+      var showcardTop = parseInt($('.showcard').css('top'))
+      var topicTitleHeight = $('.showcard .title').height() + parseInt($('.showcard .title').css('padding-top')) + parseInt($('.showcard .title').css('padding-bottom'))
+      var distanceFromBottom = windowHeight - (showcardTop + topicTitleHeight)
+      if (distanceFromBottom < MAX_METACODELIST_HEIGHT) {
+        $('.metacodeSelect').addClass('onBottomEdge')
+      }
+
+      $('.metacodeSelect').show()
+      event.stopPropagation()
+    }
+  }
+
+  var hideMetacodeSelect = function() {
+    selectingMetacode = false
+    $('.metacodeSelect').hide().removeClass('onRightEdge onBottomEdge')
+    $('.metacodeTitle').hide()
+    $('.showcard .icon').css('z-index', '1')
+  }
+
+  if (authorized) {
+    $('.showcard .metacodeTitle').click(openMetacodeSelect)
+    $('.showcard').click(hideMetacodeSelect)
+    $('.metacodeSelect > ul > li').click(function(event) {
+      event.stopPropagation()
+    })
+    $('.metacodeSelect li li').click(metacodeLiClick)
+  }
+}
+
+class Links extends Component {
+  componentDidMount = () => {
+    bindShowCardListeners(this.props.topic, this.props.ActiveMapper)
+  }
+
+  render = () => {
+    const { metacode } = this.props
+    // the code for this is stored in /views/main/_metacodeOptions.html.erb
+    const metacodeSelectHTML = $('#metacodeOptions').html()
+
+    return (
+      <div className="linkItem icon">
+        <div className={`metacodeTitle mbg${metacode.get('id')}`}>
+          {metacode.get('name')}
+          <div className="expandMetacodeSelect"></div>
+        </div>
+        <div className="metacodeImage" style={{backgroundImage: `url(${metacode.get('icon')})`}} title="click and drag to move card"></div>
+        <div className="metacodeSelect" dangerouslySetInnerHTML={{ __html: metacodeSelectHTML }} />
+      </div>
+    )
+  }
+}
+
+Links.propTypes = {
+  topic: PropTypes.object, // backbone object
+  metacode: PropTypes.object, // backbone object
+  ActiveMapper: PropTypes.object,
+  updateTopic: PropTypes.func
+}
+
+export default Links
+
+
diff --git a/frontend/src/components/TopicCard/Permission.js b/frontend/src/components/TopicCard/Permission.js
new file mode 100644
index 00000000..cef32cc8
--- /dev/null
+++ b/frontend/src/components/TopicCard/Permission.js
@@ -0,0 +1,81 @@
+import React, { PropTypes, Component } from 'react'
+
+// TODO how do we make it so that clicking elsewhere on the topic
+// card cancels this
+class Permission extends Component {
+  
+  constructor(props) {
+    super(props)
+    this.state = {
+      selectingPermission: false
+    }
+  }
+  
+  componentDidMount = () => {
+    bindShowCardListeners(this.props.topic, this.props.ActiveMapper, this)
+  }
+  
+  togglePermissionSelect = () => {
+    this.setState({selectingPermission: !this.state.selectingPermission})
+  }
+  
+  openPermissionSelect = () => {
+    this.setState({selectingPermission: true})
+  }
+  
+  closePermissionSelect = () => {
+    this.setState({selectingPermission: false})
+  }
+
+  render = () => {
+    const self = this
+    const { topic, ActiveMapper, updateTopic } = this.props
+    const { selectingPermission } = this.state
+    const permission = topic.get('permission')
+    const canChange = topic.authorizePermissionChange(ActiveMapper)
+    const onClick = canChange ? this.togglePermissionSelect : () => {}
+    let classes = `linkItem mapPerm ${permission.substring(0, 2)}`
+    if (selectingPermission) classes += ' minimize'
+    const liClick = value => {
+      return event => {
+        self.closePermissionSelect()
+        updateTopic({
+          permission: value,
+          defer_to_map_id: null
+        })
+        // prevents it from also firing the event listener on the parent
+        event.preventDefault()
+      }
+    }
+    const selectCommons = <li key='1' className='commons' onClick={liClick('commons')}></li>
+    const selectPublic = <li key='2' className='public' onClick={liClick('public')}></li>
+    const selectPrivate = <li key='3' className='private' onClick={liClick('private')}></li>
+    let permOptions
+    if (permission === 'commons') {
+      permOptions = [selectPublic, selectPrivate]
+    } else if (permission === 'public') {
+      permOptions = [selectCommons, selectPrivate]
+    } else if (permission === 'private') {
+      permOptions = [selectCommons, selectPublic]
+    }
+
+    return (
+      <div
+          className={classes}
+          title={permission}
+          onClick={onClick}>
+        {selectingPermission && <ul className="permissionSelect">
+          {permOptions}
+        </ul>}
+      </div>
+    )
+  }
+}
+
+Permission.propTypes = {
+  topic: PropTypes.object, // backbone object
+  ActiveMapper: PropTypes.object,
+  updateTopic: PropTypes.func
+}
+
+export default Permission
diff --git a/frontend/src/components/TopicCard/index.js b/frontend/src/components/TopicCard/index.js
index 1cd6f3f9..8af548c7 100644
--- a/frontend/src/components/TopicCard/index.js
+++ b/frontend/src/components/TopicCard/index.js
@@ -26,6 +26,7 @@ class ReactTopicCard extends Component {
           <Title name={topic.get('name')} onChange={this.props.updateTopic} />
           <Links topic={topic}
             ActiveMapper={this.props.ActiveMapper}
+            updateTopic={this.props.updateTopic}
           />
           <Desc desc={topic.get('desc')}
             authorizedToEdit={topic.authorizeToEdit(ActiveMapper)}