When I first came across , I liked how easy it was to get started and that it’s built with React. I write React code on a daily basis and why not use it to create presentations? All went well for a while until I realized that the file started to exceed a few hundred lines. Spectacle ./presentation/index.js There must be a way to modularize my slides! The code is available at tl;dr: https://github.com/mikenikles/presentations/tree/master/packages/blog-post-source Get Started With `spectacle-boilerplate` As recommended in the Spectacle Github repo, I started by cloning the repo and removed the folder: spectacle-boilerplate .git $ git clone git@github.com:FormidableLabs/spectacle-boilerplate.git blog-post-source$ cd blog-post-source$ rm -fr .git At this point, you can install dependencies with and start the presentation with . It will be available at npm install npm start http://localhost:3000. Create a Folder For Each Slide There are four slides in the boilerplate presentation. Let’s create a directory where we’ll move each slide in its own subfolder. ./presentation/slides $ cd ./presentation$ mkdir slides && cd slides$ mkdir 1 && mkdir 2 && mkdir 3 && mkdir 4 We also want a file in each folder. This is where each slide’s content is going to be. index.js $ touch ./1/index.js && touch ./2/index.js && touch ./3/index.js && touch ./4/index.js This leaves us with the following directory structure: : Commit https://github.com/mikenikles/presentations/commit/5d208f669c633da7d95424d48e62588081bd7d56 Populate Each Slide’s Content The spectacle-boilerplate repo already provides each slide’s content in . All we need to do is move each React component into its corresponding file. ./presentation/index.js <Slide /> ./presentation/slides/[slide-number]/index.js Let’s do that together for the first slide. Cut and paste that to ./presentation/slides/1/index.js We also have to add a few import statements to . Also, let’s make sure we export the code for this slide. The final file looks like this: ./presentation/slides/1/index.js import React from "react";import { Heading, Slide, Text } from "spectacle"; export default (<Slide transition={["zoom"]} bgColor="primary"><Heading size={1} fit caps lineHeight={1} textColor="secondary">Spectacle Boilerplate</Heading><Text margin="10px 0 0" textColor="tertiary" size={1} fit bold>open the presentation/index.js file to get started</Text></Slide>); Follow the same steps for the remaining slides. : Commit https://github.com/mikenikles/presentations/commit/a45f144247d0e4f0c39d922a9a23cf73c05c0a32 Load Slides Dynamically Lastly, we have to load each slide dynamically. This sounds trickier than it is. At a high-level, the following steps are required: Load all slides dynamically with . import() Provide the loaded slides to the component’s state. Presentation Populate the state with placeholder components while step 1 and 2 above are in progress. Provide a unique prop to each dynamically loaded slide. key 1. Load all slides dynamically with import() In , we define a list of all slides and their order. ./presentation/index.js const slidesImports = [import("./slides/1"),import("./slides/2"),import("./slides/3"),import("./slides/4")]; Each statement returns a promise. So is an array of Promises. We can leverage that and use the to wait until all slides have been imported. More on that shortly. import() slidesImports Promise.all() function 2. Provide the loaded slides to the component’s state. Presentation The component needs a where we provide the loaded slides once they’re available. We populate an empty array in the and replace it with the actual slides’ content in the lifecycle method. The new component now looks like this: Presentation state constructor() componentDidMount() Presentation export default class Presentation extends React.Component {constructor(props) {super(props); this.state = { slides: \[\] // A placeholder for slides once they're loaded. }; } componentDidMount() {const importedSlides = [];Promise.all(slidesImports).then((slidesImportsResolved) => {slidesImportsResolved.forEach((slide) => {importedSlides.push(slide.default);});this.setState({ slides: importedSlides });});} render() {return (<Deck transition={["zoom", "slide"]} transitionDuration={500} theme={theme}></Deck>);}} We’re almost done. Next up, let’s update the function and actually render all slides. render() render() {const { slides } = this.state;return (<Deck transition={["zoom", "slide"]} transitionDuration={500} theme={theme}>{slides.map((slide) => {return slide;})}</Deck>);} When we look at , we see a blank screen and the following error in the browser console: http://localhost:3000/ Browser console error based on the current code A closer look at on line 415 reveals the error is caused by the following line of code: manager.js children: _react.Children.toArray(child.props.children), Based on the error message, we know that is . That’s an easy fix. child undefined 3. Populate the state with placeholder components while step 1 and 2 above are in progress When the component’s function is called for the first time, is set to an empty array. Spectacle doesn’t like that, so let’s provide some placeholder slides until our real slides are imported and added to the state. Presentation render() this.state.slides We could provide an empty slide until is available, along the lines of: this.state.slides render() {const { slides } = this.state;return (<Deck transition={["zoom", "slide"]} transitionDuration={500} theme={theme}>{slides.length ? slides.map((slide) => {return slide;}) : <Slide />}</Deck>);} That actually works fine when we load the first slide at . However, try to navigate to the second slide and reload the page at . . http://localhost:3000/ http://localhost:3000/#/1 Error What this teaches us is that Spectacle needs to know the exact number of slides a presentation requires upfront upon first calling the function of the component. render() Presentation Easy, let’s make it happen by changing the function from: constructor() constructor(props) {super(props); this.state = {slides: []};} to: constructor(props) {super(props); this.state = {slides: Array(slidesImports.length).fill(<Slide key="loading" />)};} We basically populate the property of the state with the exact number of slides that we’ll import. In the code snippet above, we render an empty component, but we could just as well design a nice slide that displays a “Loading…” spinner. slides <Slide /> Now head back to and enjoy the second slide of your presentation without the nasty error we saw earlier. http://localhost:3000/#/1 Wait a minute, I spoke too soon… Each <Slide /> component requires a unique “key” prop Oh yeah, that’s right. in the error message explains why that prop is important. The link key 4. Provide a unique prop to each dynamically loaded slide key We have two options to do that: Add a prop to each component within . key <Slide /> ./presentation/slides/[slide-number]/index.js Provide the prop dynamically in the component’s function. key Presentation render() Option 1 sounds simple, but we’ll go for option 2 because it makes it easier to rearrange slides later. If the individual slide files are unaware of their position within the presentation, we can simply rename the slide’s folder from to to move a slide from the first to the third position in the presentation. index.js 1 3 The function’s updated component now looks like this: render() <Deck /> <Deck transition={["zoom", "slide"]} transitionDuration={500} theme={theme}>{slides.map((slide, index) => {return React.cloneElement(slide, {key: index});})}</Deck> : Commit https://github.com/mikenikles/presentations/commit/2c8630086548405e7d7ac2394d087fcfe504b06c Conclusion With this approach, I can now easily modularize my Spectacle presentations. In fact, I can take this to a whole new level… I could create a NPM module with a collection of commonly used slides, such as an “About Me” slide I use for every presentation. Whenever I want to use that in a presentation, I could simply add it as a dependency to my file and import it into my presentation at the correct index in my deck. package.json If you have any questions, don’t hesitate to reach out!