3

I am trying to build an image slider app using react and react-router. The idea is to maintain a state for each image slide.

Whenever the image slide changes, the url is updated. Everything works fine. However when I use browser back button or reload the page, the page load fails:

Warning: Location "/react-snap/4" did not match any routes


Code:

'use strict';


var ReactRouter = require('react-router'),
    Router = ReactRouter.Router,
    Route = ReactRouter.Route,
    Link = ReactRouter.Link,
    History = ReactRouter.History,
    history = require('history'),
    React = require('react'),
    ReactDOM = require('react-dom'),
    lastSnap,
    ssInterval,
    basePath = 'resources/gallery/',
    snapList = [
        {
            path: basePath + 'Koala.jpg',
            cls: 'shown',
        },
        {
            path: basePath + 'Chrysanthemum.jpg',
            cls: 'next',
        },
        {
            path: basePath + 'Desert.jpg',
            cls: '',
        },
        {
            path: basePath + 'Penguins.jpg',
            cls: '',
        },
        {
            path: basePath + 'Tulips.jpg',
            cls: '',
        },
        {
            path: basePath + 'Lighthouse.jpg',
            cls: 'left'
        }
    ];

var AppHistory = history.useBasename(history.createHistory)({
    basename: ''
});

var NewSlide = React.createClass({
      render: function() {

          return <Link to={(this.props.state).toString()}>
                    <div className={'slide-container ' + this.props.data.cls}>
                        <div style={{backgroundImage: 'url(' + this.props.data.path + ')'}}
                            className="slide"></div>
                    </div>
                </Link>;
      }
});

var SnapViewer = React.createClass({
    mixins: [ History ],
    getStateFromStore: function (props) {
        var currentIdx = props ? +props.params.id - 1 : 0;

        lastSnap = snapList.length - 1;

        return {
            snData: snapList,
            action: 'Play',
            animationProgress: false,
            currentIdx: currentIdx,
            nextIdx: (currentIdx === lastSnap ? 0 : currentIdx + 1),
            prevIdx: (currentIdx === 0 ? lastSnap : currentIdx - 1)
        }
    },
    getInitialState: function () {
        return this.getStateFromStore();
    },
    componentWillReceiveProps(nextProps) {
        this.setState(this.getStateFromStore(nextProps));
    },
    prev: function (event) {
        this.fnslideShow(true, true);
    },
    slideShow: function (event) {
        if (this.pause()) {
            return;
        }

        this.setState({action: 'Pause'});
        ssInterval = setInterval(this.fnslideShow, 5000);
    },
    next: function (event) {
        this.fnslideShow(true);
    },
    pause: function () {
        if (!!ssInterval) {
            clearInterval(ssInterval);
            ssInterval = null;
            this.setState({action: 'Play'});

            return true;
        }
    },
    fnslideShow: function (stopSS, isPrev) {

        if (stopSS) {
            this.pause();
        }

        if (this.state.animationProgress) {
            return;
        }

        this.setState({animationProgress: true});

        if (isPrev) {
            setTimeout(function () {
                this.state.snData[this.state.prevIdx].cls = 'animate shown';
                this.state.snData[this.state.currentIdx].cls = 'next';
                this.state.snData[this.state.nextIdx].cls = '';
                this.setState({nextIdx: this.state.currentIdx});
                this.setState({currentIdx: this.state.prevIdx});
                this.setState({prevIdx: (this.state.currentIdx === 0 ?
                        lastSnap: this.state.prevIdx - 1)});
            }.bind(this), 1);
        } else {
            setTimeout(function () {
                this.state.snData[this.state.prevIdx].cls = '';
                this.state.snData[this.state.currentIdx].cls = 'animate shown left';
                this.setState({prevIdx: this.state.currentIdx});
                this.setState({currentIdx: this.state.nextIdx});
                this.setState({nextIdx: (this.state.currentIdx === lastSnap ?
                        0 : this.state.nextIdx + 1)});
            }.bind(this), 1);
        }

        // on animation complete
        setTimeout(function () {
            this.state.snData[this.state.prevIdx].cls = 'left';
            this.state.snData[this.state.currentIdx].cls = 'shown';
            this.state.snData[this.state.nextIdx].cls = 'next';
            this.setState({animationProgress: false});
            this.history.pushState(null, (this.state.currentIdx + 1).toString());
        }.bind(this), 1500);
    },
    render: function() {
        return <div>
                    <div className="controls">
                        <button onClick={this.prev}>&lt;</button>
                        <button onClick={this.slideShow}>
                            Slide Show {this.state.action}
                        </button>
                        <button onClick={this.next}>&gt;</button>
                    </div>
                    {this.state.snData.map(function(item) {
                        return <NewSlide state={arguments[1] + 1} key={arguments[1]} data={item}/>;
                    })}
              </div>;
    }
});

ReactDOM.render(
    <Router history={AppHistory}>
        <Route path='/' component={SnapViewer}>
            <Route path=":id" component={NewSlide} />
        </Route>
    </Router>,
    document.getElementById('snapContainer')
);

Server.js

var express = require('express');
var path = require('path');
var port = process.env.PORT || 8085;
var app = express();

// serve static assets normally
app.use(express.static(__dirname));

// handle every other route with index.html, which will contain
// a script tag to your application's JavaScript file(s).
app.get('*', function (request, response) {
  response.sendFile(path.resolve(__dirname, 'react-snap', 'index.html'))
});

app.listen(port);
console.log("server started on port " + port);

This is my first app using Reactjs.Please let me know what am I missing?

Thanks.

Sree.Bh
  • 1,751
  • 2
  • 19
  • 25
  • Possible duplicate of [Client Routing (using react-router) and Server-Side Routing](http://stackoverflow.com/questions/28553904/client-routing-using-react-router-and-server-side-routing) – Jordan Running Nov 24 '15 at 07:53
  • I don't want to use server side routing. – Sree.Bh Nov 24 '15 at 09:22
  • You don't need to use server side routing, but you do need to have a server that will handle requests for `/react-snap/1` just like it handles requests for `/`. – Jordan Running Nov 24 '15 at 12:49
  • The same output can be achieved entirely with client side code written in javascript, jquery or angular. In case of react, why should the server handle such requests? – Sree.Bh Nov 24 '15 at 17:07
  • 1
    Because when the browser requests `/react-snap/1`, which is what it will do when the user reloads the page, you need the server to return something that's not a 404. You don't need to render anything on the server, you just need the server to respond to that request the same way it responds to `/`. The linked page explains all of this in detail. – Jordan Running Nov 24 '15 at 17:53
  • Please correct me if I am wrong. My understanding is: In the linked page example, I can see that there is a route.js file maintained to route each page from the server. So there will always be two copies of the routing information maintained for client and server side. – Sree.Bh Nov 25 '15 at 05:32
  • But what I want is: the server configuration to be such that for any requests with pattern: /react-snap/*, it should be able to route to the index.html without caring about *. Also react-router in the client side should be able to read the url and route accordingly. In case of any junk values for *, it should route to the default page (which is configurable). – Sree.Bh Nov 25 '15 at 05:32

0 Answers0