I'm trying to use redux, react-engine, and react-router.
The issue or question I have is that react-engine provides an object of props that come from the server. How do I access these props from within my ProvidedApp?
ProvidedApp.js
import React from 'react'
import { connect, Provider } from 'react-redux'
import App from './app'
import { mapStateToProps, mapDispatchToProps, store } from './redux-stuff'
// Connected Component
let ConnectedApp = connect(
mapStateToProps,
mapDispatchToProps
)(App)
let ProvidedApp = () => (
<Provider store={store}>
<ConnectedApp/>
</Provider>
)
export default ProvidedApp
Routes.js
import React from 'react'
import { Router, Route } from 'react-router'
import Layout from './views/Layout'
import App from './views/ProvidedApp'
module.exports = (
<Router>
<Route path='/' component={Layout}>
<Route path='/app' component={App} />
<Route path='/app/dev' component={App} />
</Route>
</Router>
)
I also think my configuration is a little wonky, I couldn't get Provider working any other way. If theres a way to have Provider wrap the Router I'd love to get that working.
Here's some code of what it looks like when I move Provider above Router
ConnectedApp.js
import React from 'react'
import { connect, Provider } from 'react-redux'
import App from './app'
import { mapStateToProps, mapDispatchToProps} from './redux-stuff'
let ConnectedApp = connect(
mapStateToProps,
mapDispatchToProps
)(App)
export default ConnectedApp
Routes.js
import React from 'react'
import { Provider } from 'react-redux'
import { Router, Route } from 'react-router'
import { store } from './redux-stuff'
import Layout from './views/Layout'
import App from './views/ConnectedApp'
module.exports = (
<Provider store={store}>
<Router>
<Route path='/' component={Layout}>
<Route path='/app' component={App} />
</Route>
</Router>
</Provider>
)
I get this error:
Could not find "store" in either the context or props of "Connect(App)". Either wrap the root component in a <Provider>, or explicitly pass "store" as a prop to "Connect(App)".
Update
I found that I can access from he code in my first example within ProvidedApp. But I have no clue how I'm supposed to pass it into Redux.
let ProvidedApp = (props) => {
console.log(props)
return (
<Provider store={store}>
<ConnectedApp/>
</Provider>
)
}
Seems like I need to wrap the reducer and store and pass in the ServerProps to the default state like this.
let getDefaultState = (serverProps) => {
return {
'appName': serverProps.appName
}
}
let getReducer = (serverProps) => {
let defaultState = getDefaultState(serverProps)
return (state = defaultState, action) => {
}
}
let getStore = (serverProps) => {
let reducer = getReducer(serverProps)
return store = createStore(reducer)
}
let ConnectedApp = connect(
mapStateToProps,
mapDispatchToProps
)(App)
let ProvidedApp = (serverProps) => {
return (
<Provider store={getStore(serverProps)}>
<ConnectedApp/>
</Provider>
)
}
This is super ugly :/
Related
I have information in the state (true or false) that I want to display if is true this Navbar component, but when I use the hook, I get an error message:
hook error
My code:
import React from 'react';
import { BrowserRouter } from 'react-router-dom';
import { Provider } from 'react-redux';
import { ConnectedRouter } from 'connected-react-router';
import store, { history } from './reduxStore';
import AppRouterContainer from './pages/AppRouterContainer';
import Feedback from './pages/feedback/Feedback';
import Navbar from './components/Navbar/Navbar';
import { useTypedSelector } from '../src/hooks/useTypedSelector';
const isAuth = useTypedSelector((state) => state.auth.isAuth);
const App = () => (
<BrowserRouter>
<Provider store={store}>
<ConnectedRouter history={history}>
<AppRouterContainer />
{isAuth && (
<Navbar />
)}
<Feedback />
</ConnectedRouter>
</Provider>
</BrowserRouter>
);
export default App;
You need to create a wrapper component to have access to store in your context (I think your useTypedSelector() hook needs that access).
You can use hooks only inside a function, not just inside a module.
Check out this example:
import React from 'react';
import { Provider } from 'react-redux';
import { BrowserRouter } from 'react-router-dom';
import { ConnectedRouter } from 'connected-react-router';
import { useTypedSelector } from '../src/hooks/useTypedSelector';
import Navbar from './components/Navbar/Navbar';
import AppRouterContainer from './pages/AppRouterContainer';
import Feedback from './pages/feedback/Feedback';
import store, { history } from './reduxStore';
const NavbarWrapper = () => {
const isAuth = useTypedSelector((state) => state.auth.isAuth);
if (!isAuth) {
return null;
}
return <Navbar />;
};
const App = () => (
<BrowserRouter>
<Provider store={store}>
<ConnectedRouter history={history}>
<AppRouterContainer />
<NavbarWrapper />
<Feedback />
</ConnectedRouter>
</Provider>
</BrowserRouter>
);
export default App;
Also, I think you should move the NavbarWrapper component to a separate file.
I am trying to implement redux in this component but I get the following error how could I do this?
the error it shows me is the following:
Uncaught Invariant Violation: Could not find "store" in the context of "Connect(App)". Either wrap the root component in a , or pass a custom React context provider to and the corresponding React context consumer to Connect(App) in connect options.
I know it is possible to do it this way but I don't want to inject the store into the component
store.dispatch(doResetStore());
import React, { Component } from 'react';
import { Provider } from 'react-redux';
import { Switch, Route, BrowserRouter } from 'react-router-dom';
import environment from '../../commons/enviroment.const';
import Loader from '../loader/Loader';
import {connect} from "react-redux";
import store from '../../store/store';
import { routes as routesConst, context } from '../../commons/routes/routes.const';
import PropTypes from 'prop-types';
import MandateConsulting from '../mandate-consulting/MandateConsulting';
import { doResetStore } from '../../store/config/actions/actions';
class App extends Component {
componentWillMount(){
this.props.doResetStore();
}
render() {
return (
<Provider store={store}>
<BrowserRouter basename={context()}>
<div id={environment.appName} className="ui-kit-reset">
<Loader />
<Switch>
<Route exact path={routesConst.etd} component={MandateConsulting} />
<Route exact path={routesConst.default} component={MandateConsulting} />
</Switch>
</div>
</BrowserRouter>
</Provider>
);
}
}
App.propTypes = {
reset: PropTypes.any,
doResetStore: PropTypes.any,
};
export const mapStateToProps = state => ({
reset: state.config.reset
});
export const mapDispatchToProps = (dispatch) => ({
doResetStore: () => dispatch(doResetStore()),
});
export default connect(mapStateToProps, mapDispatchToProps)(App);
If you want your App component to be connected to the redux store, you need to wrap App component with <Provider>.
For example - It can be done with the parent component of App i.e. index.js:
index.js
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root')
);
App component
...
render() {
return (
<BrowserRouter basename={context()}>
<div id={environment.appName} className="ui-kit-reset">
<Loader />
<Switch>
<Route exact path={routesConst.etd} component={MandateConsulting} />
<Route exact path={routesConst.default} component={MandateConsulting} />
</Switch>
</div>
</BrowserRouter>
);
}
...
I have a component that is used persistently across my spa. I want it to be aware of my router and the various paths that my spa is on. Is there an easy way to do this, or do I have to bandaid some redux (or something similar) state solution that is always listening to my router changes? Thanks! You can see the below for an example.
index.jsx:
import 'babel-polyfill';
import React from 'react';
import { render } from 'react-dom';
import { Provider } from 'react-redux';
import { ConnectedRouter } from 'connected-react-router';
import { Route, Switch } from 'react-router-dom';
import { history, store } from './redux/store';
import Navigation from './navigation';
const UserReport = () => <h2>User Report</h2>;
const UserPage = () => <h2>User Page</h2>;
const Routes = () => (
<React.Fragment>
<Route component={Navigation} />
<Switch>
<Route exact path="/users/:startDate" component={UserReport} />
<Route exact path="/users/:userId" component={UserPage} />
</Switch>
</React.Fragment>
);
render(
<Provider store={store}>
<ConnectedRouter history={history}>
<Routes />
</ConnectedRouter>
</Provider>, document.getElementById('app'),
);
navigation.jsx:
import React from 'react';
import { withRouter } from 'react-router-dom';
const Navigation = (props) => {
console.log(props.match.path);
// expected: "/users/:startDate"
// received: "/"
return (
<h2>Navigation</h2>
);
};
export default withRouter(Navigation);
Since the Navigation route doesn't have any path specified, it always matches whatever path you're on but the match.path only shows you the minimum path required to match for the navigation. That's why it's always /.
You can use location.pathname but it gives you the matched value and not the matched path.
const Navigation = props => {
console.log(props.location.pathname);
// prints `/users/1` if you're on https://blah.com/users/1
// prints `/users/hey` if you're on https://blah.com/users/hey
return <h2>Navigation</h2>;
};
Not sure that's what you want but if you expand what exactly you're trying to achieve, maybe I can help more.
Moreover, your second route to path="/users/:userId" overshadows the first route. Meaning there is no way to tell if hey in /users/hey is startDate or userId. You should introduce a separate route like path="/users/page/:userId".
I ended up using this react-router github discussion as my solution.
An example of my implementation:
index.jsx:
import 'babel-polyfill';
import React from 'react';
import { render } from 'react-dom';
import { Provider } from 'react-redux';
import { ConnectedRouter } from 'connected-react-router';
import { Route, Switch } from 'react-router-dom';
import { history, store } from './redux/store';
import Layout from './layout';
const home = () => <h2>Home Page</h2>;
const users = () => <h2>Users</h2>;
const userPage = () => <h2>User Page</h2>;
const layoutRender = component => route => <Layout component={component} route={route} />;
const Routes = () => (
<Switch>
<Route exact path="/" component={layoutRender(home)} />
<Route exact path="/users" component={layoutRender(users)} />
<Route exact path="/users/:id" component={layoutRender(userPage)} />
</Switch>
);
render(
<Provider store={store}>
<ConnectedRouter history={history}>
<Routes />
</ConnectedRouter>
</Provider>, document.getElementById('app'),
);
layout.jsx:
import React from 'react';
const Layout = (props) => {
const {
component: Component,
route,
} = props;
return (
<div>
<h1>This is the layout</h1>
<Component route={route} />
</div>
);
};
export default Layout;
I'm building a react & redux application and the problem I'm having is that after I do browserHistory.push('/myroute') and being successfully routed, I see that my state has been cleared, and while I need some data that's on the state from the previous route.. I still haven't found out if this is natural or not
My case is that I need to transfer data between routes.. I thought that is what state is for
this is my log:
action # 16:21:35.917 ON_ACTIVATE_FINISHED
counter.js:68 Object {counter: Object, registerReducer: Object, routing: Object}
core.js:97 action # 16:21:35.928 ##router/LOCATION_CHANGE
register.js:9 Object {}
core.js:97 action # 16:21:38.840 INPUT_PW_CHANGED
routes.js:
// #flow
import React from 'react';
import { Route, IndexRoute } from 'react-router';
import App from './containers/App';
import HomePage from './containers/HomePage';
import CounterPage from './containers/CounterPage';
import RegisterPage from './containers/RegisterPage';
export default (
<Route path="/" component={App}>
<IndexRoute component={CounterPage} />
<Route path="/counter" component={CounterPage} />
<Route path="/home" component={HomePage} />
<Route path="/register" component={RegisterPage} />
</Route>
);
CounterPage.js:
import { bindActionCreators } from 'redux';
import { connect } from 'react-redux';
import Counter from '../components/Counter';
import * as CounterActions from '../actions/counter';
function mapStateToProps(state) {
return {
counter: state.counter,
key: state.key
};
}
function mapDispatchToProps(dispatch) {
return bindActionCreators(CounterActions, dispatch);
}
export default connect(mapStateToProps, mapDispatchToProps)(Counter);
RegisterPage.js:
import { bindActionCreators } from 'redux';
import { connect } from 'react-redux';
import Register from '../components/Register';
import * as RegisterActions from '../actions/register';
function mapStateToProps(state) {
return {
pw: state.pw
};
}
function mapDispatchToProps(dispatch) {
return bindActionCreators(RegisterActions, dispatch);
}
export default connect(mapStateToProps, mapDispatchToProps)(Register);
reducers/counter.js:
import { Router, hashHistory } from 'react-router';
export const INPUT_KEY_CHANGED = 'INPUT_KEY_CHANGED';
export const ACTIVATE_KEY = 'ACTIVATE_KEY';
export const ON_ACTIVATE_FINISHED = 'ON_ACTIVATE_FINISHED';
export function onInputChanged(e) {
return {
type: INPUT_KEY_CHANGED,
data: e.target.value
};
}
export function activate() {
return {
type: ACTIVATE_KEY
};
}
export function onActivateFinished(json) {
return {
type: ON_ACTIVATE_FINISHED,
json
}
}
const SERVER_ADDRESS = 'http://*******:9001';
export const fetchActivationKey = () => (dispatch, getState) => {
var request = require('request-promise');
dispatch(activate())
const key = getState().counter.key;
return request(`${SERVER_ADDRESS}/invite/${key}`)
.then((fHost) => {
return request(`http://${fHost}:9000/api/start/${key}`)
})
.then(json => {
dispatch(onActivateFinished(json))
let currentState = getState();
if (currentState.counter.model.status === 0) {
hashHistory.push('/register');
}
});
}
Any ideas?
Based on https://github.com/reactjs/react-redux/blob/master/docs/api.md#provider-store and on my own app, it should look like this:
ReactDOM.render(
<Provider store={store}>
<Router history={history}>
<Route path="/" component={App}>
<Route path="foo" component={Foo}/>
<Route path="bar" component={Bar}/>
</Route>
</Router>
</Provider>,
document.getElementById('root')
)
But I don't see the Provider and the Router in your code, the Provider is here to pass the main state to all components and without it I'm not sure it would work properly.
Normally, you can’t use connect() without wrapping the root component in Provider.
Let's have a try at this.
Take this code
.then(json => {
dispatch(onActivateFinished(json))
let currentState = getState();
if (currentState.counter.model.status === 0) {
hashHistory.push('/register');
}
});
and change it to this:
.then(json => {
dispatch(onActivateFinished(json))
let currentState = getState();
this.setState({counter: currentState.counter});
if (currentState.counter.model.status === 0) {
hashHistory.push('/register');
}
});
I'm still learning React and by no means am any kind of expert. So I am not 100% certain this will work. But it is an effort to assist based off of what I currently know.
I am trying to get react-router and redux to work together.
When my component mounts, I make an ajax-call and until this one finishes I want to show a loading-message. Once the ajax-call returns successfully, I reroute to the correct page.
However, whenever I change routes programmatically, react-router doesn't update the shown page. Also, I don't want to use the browser's history but an internal one (memory history) and can't find any examples of this working together with redux.
import React, {Component, PropTypes} from 'react';
import { render } from 'react-dom'
import { createStore } from 'redux'
import { Provider } from 'react-redux'
import { Router, Route, IndexRoute, createMemoryHistory, useRouterHistory, routerReducer } from 'react-router'
import { syncHistoryWithStore } from 'react-router-redux'
let appHistory;
export default class Transfer extends React.Component {
componentWillMount(){
myAjaxCall(function(){
// Reroute
appHistory.push('/projectrepresentation'); // changes a route change in the router, but doesn't display /projectrepresentation
})
}
constructor(){
const reducers = combineReducers({
routing: routerReducer
})
store = createStore(reducers);
// Create an enhanced internal (!) history that syncs navigation events with the store
let createAppHistory = useRouterHistory(createMemoryHistory);
appHistory = createMemoryHistory();
history = syncHistoryWithStore(appHistory, store);
}
render () {
return <Provider store={store}>
<Router history={history}>
<Route path="/" component={() => <div>Loading</div>}>
<Route path="projectrepresentation" component={() => <div>Project Representation</div>}/>
<Route path="export" component={() => <div>Export</div>}/>
</Route>
</Router>
</Provider>
}
}