S. Schenk S. Schenk - 3 months ago 20
React JSX Question

Trying to convert react-bootstrap carousel to es6 and redux

I've grabbed the example code for a controlled carousel from here: https://react-bootstrap.github.io/components.html#media-content

And I'm trying to make it work in es6 with my redux setup, but alas! despite all my noble efforts I keep getting the following error:

SlideShow.js?d20f:20 Uncaught TypeError: Cannot read property 'direction' of undefined


  1. What am I not binding/doing wrong in my following code?

  2. Is it ok for me to handle the state like that in my dumb component?

  3. Why oh why did the ungrateful brits leave us?? ;( (sorry scratch that)



Here's my dumb component (component/sldeshow.js):

import React, { Component, PropTypes } from 'react'
import { List } from 'immutable'
import { Link } from 'react-router'
import { Carousel } from 'react-bootstrap'

class Slides extends Component {

constructor(props) {
super(props)
this.state = {
index: 0,
direction: null
}

// Bind callback methods to make `this` the correct context.
this.handleSelect = this.handleSelect.bind(this)

}

handleSelect(selectedIndex, e) {
alert('selected=' + selectedIndex + ', direction=' + e.direction)
this.setState({
index: selectedIndex,
direction: e.direction
})
}

render() {
return (
<div>
{
//this.props.slides
this.props.slides.map((s)=>
{
let id = s.get('id')
let title = s.get('title')
let image = s.get('image')
let alt = s.get('alt')
let caption = s.get('caption')
return (

<Carousel activeIndex={this.state.index} direction={this.state.direction} onSelect={this.handleSelect} key={id}>
<Carousel.Item>
<img width={640} height={480} alt="640x480" src={image} alt={alt}/>
<Carousel.Caption>
<h3>{title}</h3>
<p>{caption}</p>
</Carousel.Caption>
</Carousel.Item>
<Carousel.Item>
<img width={640} height={480} alt="640x480" src={image} alt={alt}/>
<Carousel.Caption>
<h3>{title}</h3>
<p>{caption}</p>
</Carousel.Caption>
</Carousel.Item>
<Carousel.Item>
<img width={640} height={480} alt="640x480" src={image} alt={alt}/>
<Carousel.Caption>
<h3>{title}</h3>
<p>{caption}</p>
</Carousel.Caption>
</Carousel.Item>
</Carousel>
)
})
}
</div>
)
}
}

Slides.propTypes = {

slides: PropTypes.instanceOf(List).isRequired
}


export default Slides


smart container:

import React, { Component } from 'react'
import { connect } from 'react-redux'
import { Link } from 'react-router'

import Slides from 'components/SlideShow'
import { getInitalSlides } from 'actions/SlidesActions'


class Home extends Component {

static fetchData({ store }) {
return store.dispatch(getInitalSlides())
}

componentDidMount() {
this.props.getInitalSlides()
}

render() {
return (
<div className="Home">
<h1>Home Page</h1>
<Slides slides={this.props.slides} />
<div><Link to="/question">to question</Link></div>
<div><Link to="/posts">to posts</Link></div>
</div>
)
}
}

function mapStateToProps (state) {
return { slides: state.slides }
}

export { Home }
export default connect(mapStateToProps, { getInitalSlides })(Home)


and example carousel original code:

const ControlledCarousel = React.createClass({
getInitialState() {
return {
index: 0,
direction: null
};
},

handleSelect(selectedIndex, e) {
alert('selected=' + selectedIndex + ', direction=' + e.direction);
this.setState({
index: selectedIndex,
direction: e.direction
});
},

render() {
return (
<Carousel activeIndex={this.state.index} direction={this.state.direction} onSelect={this.handleSelect}>
<Carousel.Item>
<img width={900} height={500} alt="900x500" src="/assets/carousel.png"/>
<Carousel.Caption>
<h3>First slide label</h3>
<p>Nulla vitae elit libero, a pharetra augue mollis interdum.</p>
</Carousel.Caption>
</Carousel.Item>
<Carousel.Item>
<img width={900} height={500} alt="900x500" src="/assets/carousel.png"/>
<Carousel.Caption>
<h3>Second slide label</h3>
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit.</p>
</Carousel.Caption>
</Carousel.Item>
<Carousel.Item>
<img width={900} height={500} alt="900x500" src="/assets/carousel.png"/>
<Carousel.Caption>
<h3>Third slide label</h3>
<p>Praesent commodo cursus magna, vel scelerisque nisl consectetur.</p>
</Carousel.Caption>
</Carousel.Item>
</Carousel>
);
}
});


p.s
I'm pulling my slides from an api

Answer

For future interest here is my code for a controlled carousel with redux.

Take note that if you don't define the activeIndex, direction and onSelect as props on the component the slideshow will work (including previous and next buttons) but your app state won't change since you're not actually handling the action yourself, rather it's done locally by the react-bootstrap library.

As a beginner I was having problem with passing those props (activeIndex, etc.) from the container to the component, but after some more reading it become clearer.

The key point is to understand that the this.state and this.props on the container will be available on the component. See following code:

container-name.js:

import React, { Component } from 'react'
import { connect } from 'react-redux'
import { Link } from 'react-router'
import store from 'store/configureStore'
import Slides from 'components/SlideShow'
import { getInitalSlides, handleSelect } from 'actions/SlidesActions'

class Home extends Component {

constructor(props) {
    super(props)
    this.state = {
        index: null,
        direction: null
    }
    this.handleSelect = this.handleSelect.bind(this)    

static fetchData({ store }) {
        return store.dispatch(getInitalSlides())
    }

    componentDidMount() {
        this.props.getInitalSlides()
    }

handleSelect(selectedIndex, e) {
        //alert(e)
        this.props.handleSelect(selectedIndex, e)
    }

  render() {
    return (
      <div className="Home">
        <h1>Home Page</h1>
        <Slides 
        slides={this.props.slides}   
        getInitialState={this.state.index} 
        getInitialStateD={this.state.direction}
        slidesControl={this.handleSelect}
        />
        <div><Link to="/question">to question</Link></div>
        <div><Link to="/posts">to posts</Link></div>
      </div>
    )
  }
}

function mapStateToProps (state) {
  const { slides, handleSelect } = state

  return { slides: state.slides, onSelect: state.handleSelect } 
}

export { Home }
export default connect(mapStateToProps { getInitalSlides, handleSelect})(Home)

coponent-name.js:

import React, { Component, PropTypes } from 'react'
import { List } from 'immutable'
import { Link } from 'react-router'
import { Carousel } from 'react-bootstrap'

class Slides extends Component {

//A helper method to feed the list into the render. Prob. could be done much better.

listSlides(s){

    let id = s.get('id')
    let title = s.get('title')
    let image = s.get('image')
    let alt = s.get('alt')
    let caption = s.get('caption')
    return(

                    <Carousel.Item key={id} >
                    <img width={900} height={500} alt={s.get('alt')} src={image} alt={alt}/>
                    <Carousel.Caption>
                      <h3>{title}</h3>
                      <p>{caption}</p>
                    </Carousel.Caption>
                  </Carousel.Item>

              ) 
}

  render() {

    return (
      <Carousel 
      //The name of the method must match the name of the prop on the container parent
      activeIndex={this.props.getInitialState} 
      direction={this.props.getInitialStateD} // <-- Here's a good example
      onSelect={(selectedIndex,e)=>this.props.handleSelect(selectedIndex,e)}
      >
      {
      this.props.slides.map(s=>this.listSlides(s))
    }
    </Carousel>)
  }
}

Slides.propTypes = {

  slides: PropTypes.instanceOf(List).isRequired,
  handleSelect: PropTypes.func.isRequired
}


export default Slides

action-name.js:

export const HANDLE_SELECT = Symbol('HANDLE_SELECT')
export function handleSelect(selectedIndex, e) {
    //alert('selected=' + selectedIndex + ', direction=' + e.direction)

    return {
        index: selectedIndex,
        direction: e.direction,
        type: HANDLE_SELECT
    }
}

reducer-name.js:

import * as ActionType from 'actions/SlidesActions'
import Immutable from 'immutable'

let defaultState = Immutable.fromJS({

    index: null,
  direction: null
})

function handleSelect (state=defaultState, action) {
  switch(action.type) {

    case ActionType.HANDLE_SELECT:
      //alert(action.type + " " + ActionType.HANDLE_SELECT)
      return(state.merge({ updatedHandleSelect: action }))

    default:
      return state
  }
}

export default handleSelect //For the root reducer

Lastly at the current time, with the latest react-bootstrap version there is a bug (being fixed) which will throw an "Uncaught type error: cannot read property persist of undefined" on controlled carousels.

Here is the github issue you can roll back a version until it's fixed