reactjsreduxreact-reduxreact-routerconnected-react-router

Error: Invariant failed: You should not use <Route> outside a <Router>


I am doing a login page using react-redux. After the user provides the correct id and password to the app, it should redirect the user to the homepage. The error above shows when I using . If I change to using the no error show but the page not able to redirect the user to the homepage.

index.js

import ReactDOM from 'react-dom';
import 'semantic-ui-css/semantic.min.css';
import {BrowserRouter, Route,Switch,Router }from 'react-router-dom'
import {ConnectedRouter,routerMiddleware, connectRouter } from 'connected-react-router'

import { init } from '@rematch/core'
import logger from 'redux-logger'
import * as models from './models'
import { Provider } from 'react-redux'

import * as serviceWorker from './serviceWorker';
import Dashboard from './component/Dashboard';
import MasterForm from './component/MasterForm';
import LoginForm from './component/LoginForm';
import MobilityForm from './component/MobilityForm';
import CaseRecord from './component/CaseRecord';
import MovementMap from './component/MovementMap';

const middleware = routerMiddleware(history)
const history = require("history").createBrowserHistory()
const store = init({
    redux: {
        middlewares: [
            middleware,
            logger
        ]
    },
    models
})

ReactDOM.render((
    <Provider store={store}>
      <ConnectedRouter history={history}>
        <>
            <Route exact path='/' component={Dashboard} />
            <Route exact path='/dashboard' component={Dashboard}/>
            <Route exact path='/form' component={MasterForm}/>
            <Route path='/mob' component={MobilityForm}/>
            <Route path='/record' component={CaseRecord}/>
            <Route path='/login' component={LoginForm}/>
            <Route path='/map' component={MovementMap}/>
        </>
        </ConnectedRouter>
    </Provider>), document.getElementById('root'));


serviceWorker.register()

effects.js

import { dispatch } from '@rematch/core'
import { push } from 'react-router-redux'

const headers = {
    'Accept': '*/*',
    'Content-Type': 'application/json; charset=utf-8'
}

export const user = {
    login(body, state) {
        console.log(body)
        fetch(`${config.url}/login`, {
            method: 'POST',
            headers,
            body: JSON.stringify(body)
        })
        .then(res => res.json())
        .then(data => {
            let nextState = this.loginDone(data)
            console.log(data)
            dispatch(push('/'))

            return nextState
        })
    }
}

reducers.js

export const user = {
    loginDone: (state, data) => {
        document.cookie = `token=${data.token}`

        return {
            ...state,
            auth_token: data.token
        }
    }
}

models.js

import * as reducers  from './reducers'
import * as effects from './effects'

export const user = {
    state: {},
    reducers: reducers.user,
    effects: effects.user
}

LoginForm.js

import React from 'react'
import { connect } from 'react-redux'

import { Button, Form, Grid, Header, Image, Message, Segment } from 'semantic-ui-react'

class LoginForm extends React.Component{

  state = {
    username: '',
    password: ''
}

  render() {
    const { username, password } = this.state

    return (
        <div className='login-form'>
            <style>{`
                body > div,
                body > div > div,
                body > div > div > div.login-form {
                    height: 100%;
                } 
            `}</style>
            <Grid
                textAlign='center'
                style={{ height: '100%' }}
                verticalAlign='middle'
            >
                <Grid.Column style={{ maxWidth: 450 }}>
                    <Header as='h2' color='teal' textAlign='center'>
                        Log-in to your account
                    </Header>
                    <Form size='large'
                    onSubmit={event => {
                        event.preventDefault()
                        this.props.login(username, password)
                    }}>
                        <Segment stacked>
                            <Form.Input
                                fluid
                                icon='user'
                                iconPosition='left'
                                placeholder='User ID'
                                onChange={event => this.setState({ username: event.target.value })}
                            />
                            <Form.Input
                                fluid
                                icon='lock'
                                iconPosition='left'
                                placeholder='Password'
                                type='password'
                                onChange={event => this.setState({ password: event.target.value })}
                            />

                            <Button color='teal' fluid size='large'>Login</Button>
                        </Segment>
                    </Form>
                </Grid.Column>
            </Grid>
        </div>
    )
}

}
const mapDispatch = dispatch => ({
  login: (username, password) => dispatch.user.login({username, password})
})
export default connect(null,mapDispatch)(LoginForm)

I had tried many solutions found in StackOverflow and also checked the package version.

package.json

{
  "name": "Mozzhub",
  "version": "0.1.0",
  "homepage": "./",
  "dependencies": {
    "@rematch/core": "^1.1.0",
    "axios": "^0.21.0",
    "browserslist": "^4.14.5",
    "caniuse-lite": "^1.0.30001150",
    "chart.js": "^2.8.0",
    "connected-react-router": "^6.9.1",
    "create-react-class": "^15.6.3",
    "history": "^4.10.1",
    "leaflet": "^1.4.0",
    "prop-types": "^15.7.2",
    "react": "^17.0.2",
    "react-chartjs-2": "^2.7.6",
    "react-dom": "^16.14.0",
    "react-keyboard-time-input": "^2.1.1",
    "react-leaflet": "^2.2.1",
    "react-leaflet-heatmap-layer": "^2.0.0",
    "react-new-window": "^0.1.3",
    "react-redux": "^6.0.1",
    "react-router-dom": "^5.2.0",
    "react-router-redux": "^5.0.0-alpha.9",
    "react-scripts": "2.1.1",
    "react-simple-timefield": "^2.0.3",
    "react-step-builder": "^1.1.14",
    "redux": "^4.0.1",
    "redux-logger": "^3.0.6",
    "redux-thunk": "^2.3.0",
    "semantic-ui-calendar": "0.0.8",
    "semantic-ui-calendar-react": "^0.12.0",
    "semantic-ui-css": "^2.4.1",
    "semantic-ui-react": "^0.83.0",
    "wellknown": "^0.5.0"
  },
  "scripts": {
    "start": "react-scripts start",
    "build": "react-scripts build",
    "test": "react-scripts test",
    "eject": "react-scripts eject"
  },
  "eslintConfig": {
    "extends": "react-app"
  },
  "browserslist": [
    ">0.2%",
    "not dead",
    "not ie <= 11",
    "not op_mini all"
  ]
}

Solution

  • Finally, I had solved this problem. I make a new js file called store.js to declared the store uses in this application. I add a line of code to indicate the reducers in the init() function as shown below:

    store.js

    import createHistory from 'history/createBrowserHistory'
    import {routerMiddleware, connectRouter } from 'connected-react-router'
    
    import { init } from '@rematch/core'
    import logger from 'redux-logger'
    import * as models from './models'
    
    
    export const history = createHistory()
    const middleware = routerMiddleware(history)
    export const store = init({
        models,
        redux: {
            reducers:{
                router: connectRouter(history)
            },
            middlewares: [middleware,logger]
            }
        })
    
    export const { getState, dispatch } = store
    

    I followed this https://github.com/rematch/rematch/pull/446 to define the dispatch function.