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);
Related
UPDATED: I added the whole code of the parent component. The idea is to get data from coponent DarbaiLT, store that data in the object named 'objektas'. Then to pass that data as a prop to component AnObject.
import React, { Component } from "react";
import { BrowserRouter as Router, Switch, Route } from "react-router-dom";
import DarbaiLT from "./DarbaiLT";
import AnObject from './AnObject';
const objektas = {
name: 'hello',
}
class App extends Component {
onObjectClick = (darbas) => {
objektas.value = darbas;
console.log('objektas: ', objektas.value);
}
render() {
return (
<Router>
<div>
<Switch>
<Route path="/object" exact component={AnObject} />
<DarbaiLT onObjectClick={this.onObjectClick} />
<AnObject info={objektas}/>
</Switch>
</div>
</Router>
);
}
}
export default App;
I console.log it and it still gives undefined. I also am using Router and Switch, but that shouldn't be any difference right?
import React from "react";
const AnObject =(props)=> {
console.log(props.info);
return (
<>
<div>
<img src='../images/slide4.jpg' width="100%" />
</div>
</>
);
}
export default AnObject;
When i enter my React Router-dom i point my routes to a validation page.
In event aim loged in or not, i push my route(history) to required page but i keep getting bellow error.
Error: Objects are not valid as a React child (found: object with keys {$$typeof, type, compare, WrappedComponent, displayName}). If you meant to render a collection of children, use an array instead.
in Unknown (at RequiredAuth.js:34)
in RequireAuth (created by ConnectFunction)
in ConnectFunction (created by Context.Consumer)
in Route (at App.js:23)
in Switch (at App.js:18)
in Router (at App.js:17)
in div (at App.js:16)
in App (created by ConnectFunction)
in ConnectFunction (at Dashbord.js:14)
in div (at Dashbord.js:14)
in DashBoard (created by ConnectFunction)
in ConnectFunction (at src/index.js:10)
in Provider (at src/index.js:9)
App.js
import React from 'react';
import { Router, Route, Redirect, Switch } from 'react-router-dom';
import { history } from './configureStore';
import { allRoutes } from './routes';
import NotFound from './pages/404';
import RequiredAuth from './components/RequiredAuth';
import NotRequiredAuth from './components/NotRequiredAuth';
import DashBoard from './pages/Dashbord';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
class App extends React.Component {
render() {
return (
<div style={{ height: '100%' }}>
<Router history={history}>
<Switch>
{allRoutes
.filter(route => route.visible)
.map((route, index) => {
return (
<Route
exact={route.exact}
path={route.path}
key={index}
component={RequiredAuth(route.component)}
/>
)
})}
<Route path={'/:404_path'} key={'404'} component={NotFound} />
<Redirect to="/dashboard" />
</Switch>
</Router>
</div>
);
}
}
App.displayName = 'App';
const mapDispatchToProps = dispatch => {
return bindActionCreators({ }, dispatch);
};
const mapStateToProps = state => {
return {
};
};
export default DashBoard(
connect(
mapStateToProps,
mapDispatchToProps
)(App)
);
RequiredAuth.js
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import PropTypes from 'prop-types';
import { history } from '../configureStore';
export default function (ComposedComponent) {
class Authentication extends Component {
constructor(props){
super(props);
this.props = props;
}
componentDidMount() {
const { auth } = this.props
if (!auth.success) {
history.push('/login');
}
}
componentDidUpdate() {
const { auth } = this.props
if (!auth.success) {
history.push('/login');
}
}
PropTypes = {
router: PropTypes.object
}
render() {
return <ComposedComponent {...this.props} />;
}
}
Authentication.propTypes = {
location: PropTypes.object
}
Authentication.displayName = 'RequireAuth'
function mapStateToProps(state) {
return { auth: state.auth };
}
const mapDispatchToProps = dispatch => bindActionCreators({ }, dispatch);
return connect(mapStateToProps, mapDispatchToProps)(Authentication);
}
Dashbord.js
import React, { Component } from 'react';
import { bindActionCreators } from 'redux';
import { connect } from 'react-redux';
import { ShouldRender } from '../components/basic/ShouldRender';
export default function(ComposedComponent) {
class DashBoard extends Component {
render() {
const {auth} = this.props
if (!auth.success) return <div>{ComposedComponent && <ComposedComponent />}</div>;
return (
<div>
<ShouldRender if={!auth.success}>
{ComposedComponent && <ComposedComponent />}
</ShouldRender>
<ShouldRender if={auth.success}>
<div style={{ height: '100%' }}>
<div>
<div className='page-container'>
<main className='main-content bgc-grey-100'>
<div id='mainContent'>
<div className='row gap-20 masonry pos-r'>
<div className='masonry-item col-12'>
{ComposedComponent && <ComposedComponent />}
</div>
</div>
</div>
</main>
<footer className='bdT ta-c p-30 lh-0 fsz-sm c-grey-600'>
<span>
Copyright © {new Date().getFullYear()}{' '}
<a
href='https://dataintegrated.co.ke'
target='_blank'
title='Data Integrated'
>
Data Integrated Limited
</a>
. All rights reserved.
</span>
</footer>
</div>
</div>
</div>
</ShouldRender>
</div>
);
}
}
const mapDispatchToProps = (dispatch) => {
return bindActionCreators({ }, dispatch);
};
function mapStateToProps(state) {
return {
auth: state.auth
};
}
DashBoard.propTypes = {
};
return connect(mapStateToProps, mapDispatchToProps)(DashBoard);
}
Have tried working with HOC react official page but when i implement this validation, i always land my self on this error, but when i remove HOC implementation everything renders perfectly.
try using a return statement in your HOC or change your function into an arrow function
I.E
export default function(ComposedComponent) {
return class DashBoard extends Component
OR
export default (ComposedComponent) =>{
class DashBoard extends Component
I found several errors in your code, first of all your function definition is wrong, at export default function(ComposedComponent) it must be export default function ComposedComponent () {} Do not put this class class Authentication extends Component {} inside the function, if you want it to be a component create another function for this. Also define which type of components you are going to use: Functional or Class based.
We cannot know how to help you while your code have these several mistakes. Please check them and back again with your updated code.
I'm having issues accessing a parameter called bookId from the Reader.js component. The parameter is passed down from BookCarouselItem.js using react-router. Reader.js is a connected component.
I'm not sure if that makes a difference, but does react-router work with redux connected components? Or do I need to use something like connected-react-router?
I've tried to refer to similar questions but wasn't able to find a solution, help would be greatly appreciated.
App.js
import React, { Component } from 'react'
import { BrowserRouter as Router, Route } from 'react-router-dom'
import { routes } from 'constants/index';
import Reader from 'components/reader/Reader'
Class App extends Component {
render() {
return (
<div className='container-fluid main-container'>
<Router>
<div>
<Route
path={'/reader/:bookId'}
component={() => <Reader />}
/>
</div>
</Router>
</div>
);
}
}
export default App;
BookCarouselItem.js
import React from 'react'
import { Link } from 'react-router-dom'
export class BookCarouselItem extends React.Component {
render() {
const { bookThumbnail } = this.props;
const { name, numberOfSections } = bookThumbnail;
const bookId = 0;
return (
<Link className='book-carousel-link' to={`/reader/${bookId}`}>
<div className='book-info-overlay'>
<h5>{name}</h5>
<span>{numberOfSections} Sections</span>
</div>
</Link>
);
}
}
export default BookCarouselItem;
Reader.js
import React from 'react'
import { connect } from 'react-redux'
import { compose } from 'recompose'
export class Reader extends React.Component {
render() {
const { match, pageLevel } = this.props;
console.log(match); // undefined
return (
<div>
<div className='reader-body'>
<Book bookId={match.params.bookId}
pageLevel={pageLevel}
bank={bank}/>
</div>
);
}
}
Const mapStateToProps = (state) => {
return {
metadata: state.book.metadata,
pageLevel: state.book.pageLevel
}
};
const authCondition = (authUser) => !!authUser;
export default compose(
withAuthorization(authCondition),
connect(mapStateToProps, mapDispatchToProps),
)(Reader);
You can just give the component to the component prop and the route props will be passed down to the component automatically.
<Route
path="/reader/:bookId"
component={Reader}
/>
If you want to render something that is not just a component, you have to pass down the route props manually.
<Route
path="/reader/:bookId"
render={props => <Reader {...props} />}
/>
I'm not sure but maybe mapStateToProps rewrite you props so could you please first read this issue
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 have an application that uses the same layout for all routes... except one.
One route will be completely different than all others.
So the entire application will have a menu, body, footer, etc.
The one-off route will not have any of that and be a completely separate thing.
How should I set this kinda thing up in a react app? Everything I've ever seen/done always has one main wrapping element that has the routes rendered as children.
index.js
import React from 'react'
import ReactDOM from 'react-dom'
import { BrowserRouter } from 'react-router-dom'
import { Provider } from 'react-redux'
import configureStore from './store'
import App from './components/App'
// import registerServiceWorker from './registerServiceWorker'
import { unregister } from './registerServiceWorker'
const preloadedState = window.__PRELOADED_STATE__ ? window.__PRELOADED_STATE__ : {}
// console.log('window.__PRELOADED_STATE__', window.__PRELOADED_STATE__)
delete window.__PRELOADED_STATE__
const Store = configureStore(preloadedState)
const rootEl = document.getElementById('root')
ReactDOM.hydrate(
<Provider store={Store}>
<BrowserRouter>
<App />
</BrowserRouter>
</Provider>,
rootEl
)
if(module.hot){
module.hot.accept('./components/App', () => {
ReactDOM.hydrate(
<Provider store={Store}>
<BrowserRouter>
<App />
</BrowserRouter>
</Provider>,
rootEl
)
})
}
// registerServiceWorker()
unregister()
App.js
import React, { Component } from 'react'
import { withRouter } from 'react-router-dom'
import { connect } from 'react-redux'
// Components
import AppHelmet from './AppHelmet'
import Notices from './Notices'
import Header from './Header'
import Body from './Body'
import Footer from './Footer'
// Site state
import { getSiteInfo } from '../store/actions/siteInfo'
import { REACT_APP_SITE_KEY } from '../shared/vars'
// CSS
import '../css/general.css'
class App extends Component {
initialAction() {
this.props.getSiteInfo(REACT_APP_SITE_KEY)
}
componentWillMount() {
// On client and site info has not been fetched yet
if(this.props.siteInfo.site === undefined){
this.initialAction()
}
}
render() {
return (
<div>
<AppHelmet {...this.props} />
<Notices />
<div className="body">
<Header />
<Body />
</div>
<Footer />
</div>
)
}
}
const mapStateToProps = (state) => {
return {
siteInfo: state.siteInfo,
user: state.user
}
}
const mapDispatchToProps = (dispatch) => {
return {
getSiteInfo: (siteKey) => dispatch(getSiteInfo(siteKey))
}
}
export default withRouter(connect(mapStateToProps, mapDispatchToProps)(App))
Body.js
import React, { Component } from 'react'
import { Switch, Route } from 'react-router-dom'
import routes from '../shared/routes'
class Body extends Component {
render() {
return (
<Switch>
{routes.map((route, i) => <Route key={i} {...route} />)}
</Switch>
)
}
}
export default Body
So, as you can see the index.js entry point will render <App />. <App /> will render the main layout, including <Body />, which renders all routes and content.
Cool.
But seeing as I don't want this one-off to render the <App /> layout, I'm not sure how to set this up from index.js. I'm sure it's simple and I'm just not seeing the answer.
One way to achieve what you want is to listen to the router.
You can add the listener into the components you want to hide.
When the listener detects you're on a view where you do not want the components to show, simply don't render them for that view.