I'm using the following:
react v16.2.0,
react-redux v5.0.7,
react-router-dom v4.2.2,
redux v3.7.2
What I am trying to achieve is to update some props from the Auth component, and when the user navigates to /user (Which loads the Userpage component), the modified props should be displayed.
Here is a simplified version of my code:
In App.js:
import React, { Component } from 'react';
import { Provider } from 'react-redux';
import Store from './components/Store';
import Home from './components/Home';
import Auth from './components/auth';
import Userpage from './components/Userpage';
import { BrowserRouter as Router, Route } from 'react-router-dom';
class App extends Component {
render() {
return (
<Provider store={Store}>
<Router>
<div>
<Route exact path="/" component={Home}/>
<Route path="/login" component={Auth}/>
<Route path="/user" component={Userpage}/>
</div>
</Router>
</Provider>
);
}
}
export default App;
In Store.js:
import { createStore } from 'redux';
const reducer = (state,action) => {
if(action.type == 'TEST'){
return Object.assign({},state,{test:action.payload.test});
} else
return state;
}
export default createStore(reducer,{
test:'DOES NOT WORK',
})
In Auth.js :
import React from 'react';
import { connect } from 'react-redux';
import Userpage from './Userpage';
class Auth extends React.Component {
componentWillMount() {
this.props.update('TEST',{test:'WORKS'});
}
render() {
return(
<div>
<Userpage/>
</div>
);
}
}
export default connect(
(store) => {
return store;
},
(dispatch) => {
return {
update:(dispatchType, dispatchPayload) => {
dispatch({type:dispatchType,payload:dispatchPayload});
}
}
}
)(Auth);
In Userpage.js:
import React, { Component } from 'react';
import { connect } from 'react-redux';
class Userpage extends Component{
componentDidMount(){
console.log(this.props.test);
}
render(){
return null;
}
}
export default connect(
(store) => {
return store;
}
)(Userpage);
Now, when I navigate to /login, the store is updated and test is set to "WORKS". But if I navigate to /userpage, console.log(this.props.test) prints "DOES NOT WORK", instead of the updated prop.
Just to test, I included Userpage in the render function of Auth and it console logs "WORKS" correctly.
So why is the redux store apparently being reset to default values on navigation to another page using react-router? How can I fix this?
Found the solution. Apparently the whole application re-renders when you manually navigate (by typing in the URL in the browser), hence resetting the redux store.
Using Link as mentioned in react-router docs or redirecting using this.props.history.push('/user') works without any issues.
Related
I'm designing a website that just like facebook or anyother website that I seen I assume works (disclosure: I'm new to web programing), what I want to do is to route to different routes but in one of my routes or possibly even more I need to pass info to the next screen route when I to the new page for example: (I'm in
www.website.com/page1 then move to www.website.com/page1/page2) whilst passing data through the state say I want to pass a date or a name but I would not want it to be shown in url. So I found that react can pass with:
<Link {to={pathname:"/page2", state:{statetopass:datatopass}}}>
However,when I do pass the state once I'm in (www.website.com/page1/page2) I'm unable to read the data only when I refresh which i find weird will I ever see the data passed, I read that history is mutable but I can't really understand what that means its probably something to do with what my problem is.
The code that I have tried so far is here:
<-------------------- APP--------------------------->
import React, { Component } from 'react';
import { BrowserRouter as Router, Route, Switch, IndexRoute, HashRouter } from 'react-router-dom';
import Page1 from './Page1'
import Page2 from './Page2'
import { createBrowserHistory } from "history";
const history = createBrowserHistory();//idk if history should be here seems
class App extends Component {//to make no difference
constructor(props) {
super(props);
this.state = {
}
}
render() {
return (
<Router>
<Switch>
<Route exact path='/hom/Page1' component={({ match }) => { return (<Page1 />) }} />
<Route exact path='/hom/Page1/Page2' component={({ match }) => { return (<Page2 />) }} />
</Switch>
</Router>
)
}
}
export default App;
<--------------------Page1----------------->
import React, { Component } from 'react';
import { Link } from 'react-router-dom'
class Page1 extends Component {
constructor(props) {
super(props);
this.state = {}
}
render() {
return (
<div>
<Link to={{
pathname: `Page1/Page2`,
state: { dataneed: "testme" }
}}><button>Check </button>
</Link>
</div>
)
}
}
export default Page1;
<-------------------------------Page2----------------------------->
import React, { Component } from 'react';
import { createBrowserHistory } from "history";
const history = createBrowserHistory();
class Page2 extends Component {
constructor(props) {
super(props);
this.state = {}
}
render() {
console.log(history.location.state.dataneed)
return (
<div>
<h1>{history.location.state.dataneed}</h1>
</div>
)
}
}
export default Page2;
So you will see that at first you get an error but then once you refresh you see the text being displayed. If anyone could suggest the best way to go about doing whta I'm trying and if anyone could help me shed some light on the matter I would greatly appreciate this.
PS: I'm using 4.3.1 version there are videos out there but those seem to use version lower than 4.0 and completely different.
I believe the issue here is the mix of React Router and the History package. React Router uses History and has a history built in to its routers, so there is no need to explicitly use createBrowserHistory or anything from History directly. Specifically the issue is that the state is passed to the Link, from React Router, but then you attempt to access the data from the createBrowserHistory() object.
What you can do to resolve this issue and keep your code a bit cleaner is basically not use createBrowserHistory directly and instead rely on the built-in history from React Router. The routing data can be accessed through props.location, props.history, and props.match, which are injected into any component wrapped in the higher-order component withRouter from React Router.
What this will look like:
In index.js:
import React from 'react';
import ReactDOM from 'react-dom';
import { BrowserRouter } from 'react-router-dom';
import App from './App';
ReactDOM.render((
<BrowserRouter>
<App/>
</BrowserRouter>
), document.getElementById('root'));
In App.js:
import React, { Component } from 'react';
import { Route, Switch, withRouter } from 'react-router-dom';
import Page1 from './Page1'
import Page2 from './Page2'
class App extends Component {
constructor(props) {
super(props);
this.state = {
}
}
render() {
return (
{/* Note no need for a Router because we wrapped the App component in the BrowserRouter in index.js */}
<Switch>
{/* Note the component attribute can be simplified */}
<Route exact path='/hom/Page1' component={ Page1 } />
<Route exact path='/hom/Page1/Page2' component={ Page2 } />
</Switch>
)
}
}
export default withRouter(App);
Page1 is fine.
In Page2:
import React, { Component } from 'react';
import { withRouter } from 'react-router-dom';
class Page2 extends Component {
constructor(props) {
super(props);
this.state = {}
}
render() {
const { dataneed } = this.props.location.state;
return (
<div>
<h1>{ dataneed }</h1>
</div>
)
}
}
export default withRouter(Page2);
Hopefully this helps, let me know if you have questions!
I am unable to make the store available to children components.
The setup is a SPA with Symfony as back-end, though this should not make a difference for this matter.
The entry point for Webpack is the file:
/client/index.js
import React from 'react';
import ReactDOM from 'react-dom';
import { createStore, applyMiddleware, compose } from 'redux';
import ReduxPromise from 'redux-promise';
import Root from './App';
import registerServiceWorker from './registerServiceWorker';
import reducers from './pages/combine_reducers';
let composeEnhancers = typeof(window) !== 'undefined' && window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose
const store = createStore(
reducers,
composeEnhancers(
applyMiddleware(ReduxPromise)
)
)
ReactDOM.render(
<Root store={store} />
, document.querySelector('#root')
);
registerServiceWorker();
The apps as such is at:
/client/App.js
import React from 'react';
import PropTypes from 'prop-types';
import { Provider } from 'react-redux';
import {
BrowserRouter as Router,
Route,
Link,
Switch
} from 'react-router-dom';
import HomePage from './pages/home/';
import AccountPage from './pages/account/';
const Root = ({ store }) => {
return(
<Provider store={store}>
<div className="App">
<header className="App-header">
<h1 className="App-title">Welcome to React</h1>
</header>
<Router>
<div>
<Link to="/account">Account</Link>
<Link to="/">Home</Link>
<div>
<Switch>
<Route path="/account" component={AccountPage} />
<Route path="/" component={HomePage} />
</Switch>
</div>
</div>
</Router>
</div>
</Provider>
)
}
Root.propTypes = {
store: PropTypes.object.isRequired
}
export default Root;
So far so good. The store is available in App.js.
But that's not the case at the next level. As you can see I'm attempting to make the store available using connect().
/client/pages/home/index.js
import React from 'react';
import { connect } from 'react-redux';
import Register from '../common/register/';
import PropTypes from 'prop-types';
class Home extends React.Component {
constructor(props){
super(props)
console.log(props);
}
render() {
return (
<div>
<h1> Hello World from home! </h1>
<Register />
</div>
);
}
}
Home.propTypes = {
store: PropTypes.object.isRequired
}
const mapStateToProps = (state) => {
return {
store: state.store,
}
}
export default connect(mapStateToProps)(Home)
At the lower level, the Register component, I'm able to submit the form, but the store not being available, I am unable to capture the response coming from the server.
/client/pages/common/register/index.js
import React from 'react';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import RegisterForm from './containers/register';
import { actionSubmitRegister } from './actions/';
import PropTypes from 'prop-types';
class Register extends React.Component{
constructor (props) {
super(props);
this.state = {
registerResponse: '',
}
this.onSubmitRegister = this.onSubmitRegister.bind(this);
}
onSubmitRegister (event) {
event.preventDefault();
let submitForm = new Promise((resolve, reject) => {
actionSubmitRegister(this.props.form.RegisterForm.values);
});
submitForm.then((response) => {
console.log('response',response);
this.setState({registerResponse: this.props.submit_register.data});
console.log('registerResponse', this.state.registerResponse);
}).catch((error) => {
console.log(error);
});
}
render(){
return (
<div>
<div>
<RegisterForm
submitRegister={this.onSubmitRegister}
/>
<h3>{this.state.registerResponse}</h3>
</div>
</div>
)
}
}
/*
Register.propTypes = {
store: PropTypes.object.isRequired
}
*/
const mapStateToProps = (state) => {
return {
form: state.form,
submit_register: state.submit_register,
}
}
function mapDispatchToProps(dispatch){
return bindActionCreators({actionSubmitRegister}, dispatch);
}
export default connect(mapStateToProps, mapDispatchToProps)(Register);
In mapStateToProps you map store: state.store but in general you use this method to map single props from your state to props in your component, not map the entire store (if this is even possible).
Eg:
form: state.form
The reason you are not able to access the store object in props is because you are not passing it down via props.
Provider from the react-redux library, makes it available to all children down the element tree. Store is made available via React's context API, NOT via props.
"Context is designed to share data that can be considered “global” for a tree of React components."
So in a child component of Provider, we can now do something like
render() {
const { store } = this.context;
console.log(store)
return(
...
)
}
This is the same way that react-redux's connect HOC is able to access the store and subsequently mapStateToProps or utilise the store's dispatch method to mapDispatchToProps.
Also I think Provider requires that it’s child element is a React component.
Check out this tutorial for a more in-depth explanation.
After the input I received above, I reviewed my code and got it to work.
Actually the main issue was on the /client/pages/common/register/index.js file, but I am posting the whole chain for reference:
/client/index.js
nothing to change
/client/App.js
The references to propTypes do not seem to be necessary, so I took them out.
import React from 'react';
import { Provider } from 'react-redux';
import {
BrowserRouter as Router,
Route,
Link,
Switch
} from 'react-router-dom';
import HomePage from './pages/home/';
import AccountPage from './pages/account/';
const Root = ({ store }) => {
return(
<Provider store={store}>
<div className="App">
<header className="App-header">
<h1 className="App-title">Welcome to React</h1>
</header>
<Router>
<div>
<Link to="/account">Account</Link>
<Link to="/">Home</Link>
<div>
<Switch>
<Route path="/account" component={AccountPage} />
<Route path="/" component={HomePage} />
</Switch>
</div>
</div>
</Router>
</div>
</Provider>
)
}
export default Root;
/client/pages/home/index.js
Here both propTypes and connect() do not seem to be required.
import React from 'react';
import Register from '../common/register/';
class Home extends React.Component {
constructor(props){
super(props)
}
render() {
return (
<div>
<h1> Hello World from home! </h1>
<Register />
</div>
);
}
}
export default Home;
/client/pages/common/register/index.js
The main issue here was the onSubmitRegister() method. The promise was not properly setup and I was referencing the action directly instead of using this.props. React do not seem to like that.
import React from 'react';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import RegisterForm from './containers/register';
import { actionSubmitRegister } from './actions/';
class Register extends React.Component{
constructor (props) {
super(props);
this.state = {
registerResponse: '',
}
this.onSubmitRegister = this.onSubmitRegister.bind(this);
}
onSubmitRegister (event) {
event.preventDefault();
let submitForm = new Promise((resolve) => {
resolve(this.props.actionSubmitRegister(this.props.form.RegisterForm.values));
});
submitForm.then((result) => {
let data = result.payload.data;
this.setState({registerResponse: data.message});
}).catch((error) => {
console.log(error);
});
}
render(){
return (
<div>
<div>
<RegisterForm
submitRegister={this.onSubmitRegister}
/>
<h3>{this.state.registerResponse}</h3>
</div>
</div>
)
}
}
const mapStateToProps = (state) => {
return {
form: state.form,
submit_register: state.submit_register,
}
}
function mapDispatchToProps(dispatch){
return bindActionCreators({actionSubmitRegister}, dispatch);
}
export default connect(mapStateToProps, mapDispatchToProps)(Register);
I have reactjs setup with routes but my routing is not working. When I load the page it works but when I click on the links the URL changes but the component does not render. I tried to put as much as I can in the sandbox. load with URL/admin and click on logout etc.
https://codesandbox.io/s/o5430k7p4z
index
import React, { Component } from 'react'
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';
import { createStore, applyMiddleware, combineReducers } from 'redux';
import { BrowserRouter, Route, browserHistory } from 'react-router-dom';
import promise from 'redux-promise';
import { createLogger } from 'redux-logger';
import App from './App'
import reducers from './reducers';
require("babel-core/register");
require("babel-polyfill");
import 'react-quill/dist/quill.snow.css'; // ES6
const logger = createLogger();
const initialState = {};
const createStoreWithMiddleware = applyMiddleware(promise)(createStore);
ReactDOM.render(
<Provider store={createStoreWithMiddleware(reducers)}>
<BrowserRouter>
<App/>
</BrowserRouter>
</Provider>
, document.getElementById('root'));
App
import React, { Component } from 'react'
import { Switch, Route } from 'react-router-dom';
import ReactGA from 'react-ga';
ReactGA.initialize('UA-101927425-1');
import { connect } from 'react-redux';
import { fetchActiveUser } from './actions/index';
import { bindActionCreators } from 'redux';
import {getHttpRequestJSON} from './components/HTTP.js'
import Header from './components/header';
import Logout from './components/logout';
import SideBar from './components/sidebar';
import HomeContent from './containers/home';
import Ldapuser from './components/ldapuser';
import Admin from './components/admin/admin';
function fireTracking() {
ReactGA.pageview(window.location.pathname + window.location.search);
}
class App extends Component {
constructor(props){
super(props);
this.state = {
isGuest : false,
isSupp : false,
loading: true,
version: '',
};
}
initData = () => {
let self = this;
getHttpRequestJSON('/api/user/get/user/method/is/guest/format/json?quiet=1')
.then((response) => {
let isGuest = response.body.recordset.record.isGuest;
if(isGuest){
/*$(".logo").trigger('click');
//$("#overlay").show();
$('#modalIntro').modal('toggle');
$("#modalIntro").on("hidden.bs.modal", function () {
$(".logo").trigger('click');
});*/
}
self.props.isGuest = isGuest;
self.props.loading = false;
//self.props.version = response.header.version;
self.setState({
loading : false,
version : response.header.version,
isGuest : isGuest
});
})
.catch(error => {
console.log("Failed!", error);
//$('#myModalError .modal-body').html(error);
//$('#myModalError').modal('show');
});
getHttpRequestJSON('/api/user/get/user/method/is/supp/format/json?quiet=1')
.then((response) => {
self.setState({
isSupp : response.body.recordset.record.isSupp
});
})
.catch(error => {
console.log("Failed!", error);
//$('#myModalError .modal-body').html(error);
//$('#myModalError').modal('show');
});
}
componentDidMount() {
this.props.fetchActiveUser();
this.initData();
}
render() {
return (
<div>
<Header activeUser={this.props.activeUser} loading={this.state.loading} version={this.state.version} title={`Home`} />
<SideBar />
<main>
<Switch>
<Route path='/index.html' render={()=><HomeContent activeUser={this.props.activeUser} isGuest={this.state.isGuest} isSupp={this.state.isSupp} />} />
<Route path='/home' render={()=><HomeContent activeUser={this.props.activeUser} isGuest={this.state.isGuest} isSupp={this.state.isSupp} />} />
<Route path='/logout' component={Logout}/>
<Route path='/ldapuser' component={Ldapuser}/>
<Route path='/admin' render={()=><Admin isGuest={this.state.isGuest} isSupp={this.state.isSupp}/>} />
</Switch>
</main>
</div>
);
}
}
//export default App;
function mapStateToProps(state) {
if(state.activeUser.id > 0){
ReactGA.set({ userId: state.activeUser.id });
}
// Whatever is returned will show up as props
// inside of the component
return {
activeUser: state.activeUser
};
}
// Anything returned from this function will end up as props
// on this container
function mapDispatchToProps(dispatch){
// Whenever getUser is called, the result should be passed
// to all our reducers
return bindActionCreators({ fetchActiveUser }, dispatch);
}
//Promote component to a container - it needs to know
//about this new dispatch method, fetchActiveUser. Make it available
//as a prop
export default connect(mapStateToProps, mapDispatchToProps)(App);
The codesandbox is not working, but I think what is happening to you is a very common problem when using react-redux and react-router. The connect HOC of react-redux has a builtin SCU (shouldComponentUpdate), so for it to know to rerender is requires to receive new props. This can be done using the withRouter hoc of react-router. Simply wrap connect(..)(MyComponent) with withRouter(connect(..)(MyComponent)) or do it clean and use compose (from recomponse for example);
const enhance = compose(
withRouter,
connect(mapStateToProps)
)
export default enhance(MyComponent)
Make sure not to do it the other way around, because that does not work.
I'am using redux, react-router-redux and redux-form in my code. Code has a Provider, Connected router and Mini component. Mini component includes Switch and some components, which depends on route.
Index.js
...
import { Provider } from 'react-redux'
import { ConnectedRouter, routerMiddleware } from 'react-router-redux'
import createBrowserHistory from 'history/createBrowserHistory'
import Reducers from './reducers'
const history = createBrowserHistory({ basename: 'mini' })
const middlewareRouter = routerMiddleware(history)
const store = createStore(Reducers, applyMiddleware(middlewareRouter))
render(<Provider store={store}>
<ConnectedRouter history={history}>
<Mini/>
</ConnectedRouter>
</Provider>, document.getElementById('root'))
Mini.js
import React, { Component } from 'react'
import { Switch, Route } from 'react-router-dom'
...
import NavigationContainer from './containers/navigation'
import CategoryContainer from './containers/category'
class Mini extends Component {
render () {
return (<main>
<Switch>
<Route path="/navigation" component={NavigationContainer}/>
<Route path="/category" component={CategoryContainer}/>
...
</Switch>
<LoadContainer/>
<div id="form"></div>
</main>)
}
}
All components in Switch section has a button. Clicking on the button can render a form.
...
import FormCreate from './formcreate'
class Topbar extends Component {
constructor (props) {
super(props)
this.handleClickCreate = this.handleClickCreate.bind(this)
}
handleClickCreate (e) {
e.preventDefault()
render(<FormCreate/>, document.getElementById('form'))
}
...
}
But when I click on button error appear Uncaught Error: Could not find "store" in either the context or props of "Connect(Form(FormCreate))"
How can I fix the problem? Thanks in advance!
PS Reducers.js
import { combineReducers } from 'redux'
import { routerReducer as reducerRouter } from 'react-router-redux'
import { reducer as reducerForm } from 'redux-form'
const Reducers = combineReducers({
...
router: reducerRouter,
form: reducerForm
})
PSS FormCreate.js
import React from 'react'
import { Field, reduxForm } from 'redux-form'
...
const FormCreate = (props) => {
const { error, handleSubmit, pristine, reset, submitting } = props
return (
...
)
}
export default reduxForm({
form: 'create',
validate
}) (FormCreate)
I think the problem here is that you are trying to render FormCreate create another app within html element form that does not have access to the redux store, resulting in the error that you see.
What I would do is set up a reducer that handle whether or not I should render FormCreate then render it in component in your app like in LoadContainer.
Topbar.js
class Topbar extends Component {
constructor (props) {
super(props)
this.handleClickCreate = this.handleClickCreate.bind(this)
}
handleClickCreate (e) {
e.preventDefault()
// dispatch action to reducer to tell store to display FormCreate
}
...
}
LoadContainer.js
class LoadContainer extends Component {
// ... rest of code
render() {
// get shouldDisplayForm from redux store
const { shouldDisplayForm } = this.props;
return (
//... rest of component
{ shouldDisplayForm && <FormCreate> }
);
}
}
Alternatively, if you want to render FormCreate in html element 'form', you can put the store in a file so that you can require it in many files. Then render FormCreate with Provider like what you've done Index.js.
Okay, I've spent three hours driving myself crazy trying to figure this out. I'm sure I'm doing something wrong, but this is my first foray into React and I'm not sure exactly what the problem is.
My main app file looks like this:
import React, { PropTypes } from 'react';
import { Provider } from 'react-redux';
import { BrowserRouter as Router, Route, Switch } from 'react-router-dom';
import createHistory from 'history/createBrowserHistory'
import routes from './routes.jsx';
import map from 'lodash/map';
import MainLayout from './components/layouts/main-layout.jsx';
const history = createHistory();
const App = ({store}) => (
<Provider store={store}>
<Router history={history} basename="/admin-panel">
<Route component={MainLayout}>
<Switch>
{map(routes, (route, name) => (
<Route key={name} path={route.path} exact={route.exact} component={route.component} />
))}
</Switch>
</Route>
</Router>
</Provider>
);
App.propTypes = {
store: PropTypes.object.isRequired,
};
export default App;
The route is simply a JSON object that contains route info. Example:
import React from 'react';
import DashboardContainer from './components/containers/dashboard-container.jsx';
import AdminUserContainer from './components/containers/admin-users-container.jsx';
export default {
dashboard: {
order: 1,
path: '/',
exact: true,
name: 'Dashboard',
icon: 'tachometer',
component: DashboardContainer,
},
users: {
order: 2,
path: '/admin-users',
name: 'Admin Users',
icon: 'users',
component: AdminUserContainer,
}
};
(There's irrelevant stuff in the object; that's for the sidebar rendering, which also uses this object to render itself)
dashboard-component.jsx looks like this:
import React, { Component } from 'react';
import { connect } from 'react-redux';
import Dashboard from '../views/dashboard.jsx';
class DashboardContainer extends Component {
render () {
return (<Dashboard />);
}
}
const mapStateToProps = function (store) {
return store;
};
export default connect(mapStateToProps)(DashboardContainer);
And dashboard.jsx looks like this:
import React, { Component } from 'react';
class Dashboard extends Component {
render () {
return (
<p>This is a test.</p>
);
}
}
export default Dashboard;
But for some reason, no matter what, the returned component is UnknownComponent. Apparently, nothing is matching on /admin-panel, and I'm not sure why. I've tried moving components around, merging the Routing stuff with MainLayout (which just contains the stuff that won't change every request, like the sidebar and the header), even tried the HashRouter instead of the BrowserRouter to see if that would do anything. No dice.
Can someone familiar with React tell me just what it is I'm not doing right here?