Reputation: 2170
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
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
Upvotes: 4
Views: 2752
Reputation: 2170
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
Upvotes: 0
Reputation: 10421
try onSelect={(i,e)=>this.handleSelect(i,e)}
in Carousel component
Upvotes: 5