react_ujs/index.js (115 lines of code) (raw):

var React = require("react") var ReactDOM = require("react-dom") var ReactDOMServer = require("react-dom/server") var detectEvents = require("./src/events/detect") var constructorFromGlobal = require("./src/getConstructor/fromGlobal") var constructorFromRequireContextWithGlobalFallback = require("./src/getConstructor/fromRequireContextWithGlobalFallback") const { supportsHydration, reactHydrate, createReactRootLike } = require("./src/renderHelpers") var ReactRailsUJS = { // This attribute holds the name of component which should be mounted // example: `data-react-class="MyApp.Items.EditForm"` CLASS_NAME_ATTR: 'data-react-class', // This attribute holds JSON stringified props for initializing the component // example: `data-react-props="{\"item\": { \"id\": 1, \"name\": \"My Item\"} }"` PROPS_ATTR: 'data-react-props', // This attribute holds which method to use between: ReactDOM.hydrate, ReactDOM.render RENDER_ATTR: 'data-hydrate', // A unique identifier to identify a node CACHE_ID_ATTR: "data-react-cache-id", TURBOLINKS_PERMANENT_ATTR: "data-turbolinks-permanent", // If jQuery is detected, save a reference to it for event handlers jQuery: (typeof window !== 'undefined') && (typeof window.jQuery !== 'undefined') && window.jQuery, components: {}, // helper method for the mount and unmount methods to find the // `data-react-class` DOM elements findDOMNodes: function(searchSelector) { var classNameAttr = ReactRailsUJS.CLASS_NAME_ATTR // we will use fully qualified paths as we do not bind the callbacks var selector, parent; switch (typeof searchSelector) { case 'undefined': selector = '[' + classNameAttr + ']'; parent = document; break; case 'object': selector = '[' + classNameAttr + ']'; parent = searchSelector; break; case 'string': selector = searchSelector + '[' + classNameAttr + '], ' + searchSelector + ' [' + classNameAttr + ']'; parent = document; break default: break; } if (ReactRailsUJS.jQuery) { return ReactRailsUJS.jQuery(selector, parent); } else { return parent.querySelectorAll(selector); } }, // Get the constructor for a className (returns a React class) // Override this function to lookup classes in a custom way, // the default is ReactRailsUJS.ComponentGlobal getConstructor: constructorFromGlobal, // Given a Webpack `require.context`, // try finding components with `require`, // then falling back to global lookup. useContext: function(requireContext) { this.getConstructor = constructorFromRequireContextWithGlobalFallback(requireContext) }, // Render `componentName` with `props` to a string, // using the specified `renderFunction` from `react-dom/server`. serverRender: function(renderFunction, componentName, props) { var componentClass = this.getConstructor(componentName) var element = React.createElement(componentClass, props) return ReactDOMServer[renderFunction](element) }, // Within `searchSelector`, find nodes which should have React components // inside them, and mount them with their props. mountComponents: function(searchSelector) { var ujs = ReactRailsUJS var nodes = ujs.findDOMNodes(searchSelector); for (var i = 0; i < nodes.length; ++i) { var node = nodes[i]; var className = node.getAttribute(ujs.CLASS_NAME_ATTR); var constructor = ujs.getConstructor(className); var propsJson = node.getAttribute(ujs.PROPS_ATTR); var props = propsJson && JSON.parse(propsJson); var hydrate = node.getAttribute(ujs.RENDER_ATTR); var cacheId = node.getAttribute(ujs.CACHE_ID_ATTR); var turbolinksPermanent = node.hasAttribute(ujs.TURBOLINKS_PERMANENT_ATTR); if (!constructor) { var message = "Cannot find component: '" + className + "'" if (console && console.log) { console.log("%c[react-rails] %c" + message + " for element", "font-weight: bold", "", node) } throw new Error(message + ". Make sure your component is available to render.") } else { var component = this.components[cacheId]; if(component === undefined) { component = React.createElement(constructor, props); if(turbolinksPermanent) { this.components[cacheId] = component; } } if (hydrate && supportsHydration()) { component = reactHydrate(node, component); } else { const root = createReactRootLike(node) component = root.render(component); } } } }, // Within `searchSelector`, find nodes which have React components // inside them, and unmount those components. unmountComponents: function(searchSelector) { var nodes = ReactRailsUJS.findDOMNodes(searchSelector); for (var i = 0; i < nodes.length; ++i) { var node = nodes[i]; ReactDOM.unmountComponentAtNode(node); } }, // Check the global context for installed libraries // and figure out which library to hook up to (pjax, Turbolinks, jQuery) // This is called on load, but you can call it again if needed // (It will unmount itself) detectEvents: function() { detectEvents(this) }, } // These stable references are so that handlers can be added and removed: ReactRailsUJS.handleMount = function(e) { var target = undefined; if (e && e.target) { target = e.target; } ReactRailsUJS.mountComponents(target); } ReactRailsUJS.handleUnmount = function(e) { var target = undefined; if (e && e.target) { target = e.target; } ReactRailsUJS.unmountComponents(target); } if (typeof window !== "undefined") { // Only setup events for browser (not server-rendering) ReactRailsUJS.detectEvents() } // It's a bit of a no-no to populate the global namespace, // but we really need it! // We need access to this object for server rendering, and // we can't do a dynamic `require`, so we'll grab it from here: self.ReactRailsUJS = ReactRailsUJS module.exports = ReactRailsUJS