this.props.children dosent render - reactjs

Im new to react and Im trying to understand context.
So I tried to create a provider and a consumer for user.
Ftm Im just trying to show the value but im going to pass it into the router and other components later.
This is the provider
const UserContext = React.createContext();
// Create an exportable consumer that can be injected into components
export const UserConsumer = UserContext.Consumer
// Create the provider using a traditional React.Component class
class UserProvider extends React.Component {
render() {
return (
// value prop is where we define what values
// that are accessible to consumer components
<UserContext.Provider value={{
username: 'Crunchy Crunch',
dateJoined: '9/1/18',
membershipLevel: 'Silver'
}}>
{this.props.children}
</UserContext.Provider>
)
}
}
And this is the app:
class App extends React.Component {
render() {
return (
<UserProvider>
<Fragment>
<Router>
<Nav />
<UserConsumer>
{state => (
<p>Username: {state.username}</p>
)}
</UserConsumer>
<Switch>
<ProtectedRoute exact path="/profile" component={Profile} />
<Route exact path="/" component={Index} />
</Switch>
</Router>
</Fragment>
</UserProvider>
);
}
}
ReactDOM.render(<App />, document.getElementById('root'));
I dont get any errors in the terminal but I get this message in the browser:
Functions are not valid as a React child. This may happen if you
return a Component instead of from render. Or maybe you
meant to call this function rather than return it.
What is the error and is there something fundamental Im not doing correctly.
Br

I have taken you code snippets and run it in codesandbox and it seems ok to me, without seeing the rest of you files its hard to say what the issue. Maybe it to do with how you are importing and using react router. Here is my working snippets
// App
import React from "react";
import ReactDOM from "react-dom";
import { BrowserRouter as Router } from "react-router-dom";
import { Switch, Route } from "react-router";
import Page1 from "./Page1";
import Nav from "./Nav";
import UserProvider, { UserConsumer } from "./Provider";
import "./styles.css";
function App() {
return (
<UserProvider>
<Router>
<Nav />
<UserConsumer>
{state => <p>Username: {state.username}</p>}
</UserConsumer>
<Switch>
<Route exact path="/" component={Page1} />
</Switch>
</Router>
</UserProvider>
);
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
// Provider
import React from "react";
const UserContext = React.createContext();
// Create an exportable consumer that can be injected into components
export const UserConsumer = UserContext.Consumer;
// Create the provider using a traditional React.Component class
export default class UserProvider extends React.Component {
render() {
return (
// value prop is where we define what values
// that are accessible to consumer components
<UserContext.Provider
value={{
username: "Crunchy Crunch",
dateJoined: "9/1/18",
membershipLevel: "Silver"
}}
>
{this.props.children}
</UserContext.Provider>
);
}
}
Here is a link to the the working codesandbox
https://codesandbox.io/s/sweet-smoke-y3t9j

Related

Redirect from React's Context API component

I am trying to redirect from my context following a failed update of the state from the a cookie.
import React, { createContext, Component } from 'react';
import { withRouter } from "react-router-dom";
import Cookies from 'universal-cookie';
export const MyContext = createContext();
const cookies = new Cookies();
class MyProvider extends Component {
componentDidMount() {
this.setStateFromCookie();
}
setStateFromCookie = () => {
try {
this.setState({ data: cookies.get('my-cookie')['data'] });
} catch(error) {
console.log(error);
this.props.history.push('/');
}
return
};
render() {
return (
<MyContext.Provider value={{...this.state}}>
{this.props.children}
</MyContext.Provider>
);
}
}
export default withRouter(MyProvider);
I am using a withRouter hook to this.props.history.push('/'), becuase the context is wrapping the router
class MyApp extends Component {
render() {
return (
<BrowserRouter>
<MyProvider>
<div className="MyApp">
<Router>
<Route exact path='/' component={Index} />
<Route exact path='/dashboard' component={Dashboard} />
</Router>
</div>
</MyProvider>
</BrowserRouter>
);
}
}
export default MyApp;
The problem is that the redirect to the home page following the error, but the home page isn't rendering.. I still see the dashboard page.
Any idea what is going on and how to fix this
The issue is that you have a nested Router wrapping your Routes. You need to remove that and then everything will work fine
<BrowserRouter>
<MyProvider>
<div className="MyApp">
<Route exact path='/' component={Index} />
<Route exact path='/dashboard' component={Dashboard} />
</div>
</MyProvider>
</BrowserRouter>
When you use a nested Router, and try to navigate from Provider, the history used by Provider is being provided by BrowserRouter and hence it isn't able to communicate to the Routes whcih are dependent on the inner <Router> component for history.
Using a single router wrapping your components solves this issue

React: Trouble implementing Redux with React-router

today i'm trying to implement Redux for the first time on a react-app because it has been a mess to manage state/props, it's pretty good on the redux side so far but when i try to link my store with my app + router i run into errors.
Depending on how i place my router tags 2 things appens:
-not compiling (most of the time because i have outside of the router)
-compiling, rendering but when i try to navigate url changes but not the components that should render.
I did many tries (a lot) so i reverted to when i just linked the store.
Below is my index.js:
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
import 'bootstrap/dist/css/bootstrap.css';
import { BrowserRouter } from 'react-router-dom';
import { Provider } from 'react-redux'
import store from './store'
ReactDOM.render(
<Provider store={store}>
<BrowserRouter>
<App />
</BrowserRouter>
</Provider>,
document.getElementById('root')
);
And my App.js (shorthen because it's long and messy):
import React, { Component } from 'react';
import { Route, Switch, Link } from 'react-router-dom';
import { connect } from 'react-redux'
// Components imports
import { Provider } from 'react-redux'
import store from './store'
import { ensureAuth, login, register, updateInputAuth, logout } from './actions/authActions'
class App extends Component {
//states
//methods
render() {
const { pathname } = window.location
const { logged, user, loginError, registerError, inputLogin, inputRegister, successMessage } = this.props
return (
<>
<nav className="navbar navbar-expand-lg navbar-light bg-light" id="navbar">
// My app navbar basically, usses <Link> tags
</nav>
{
!logged ?
<>
<ModalConnect />
<ModalRegister />
</>
: null
}
<>
<Switch>
<Route exact path='/' component={Root}/>
<Route path='/ground' render={(props) => <GroundAnalizer {...props} logged={this.state.logged} />} />
<Route path='/air' component={AirAnalizer} />
<Route path='/simulateur' render={(props) => <Simulateur {...props} logged={logged} log={this.connect} reg={this.register} onInputChange={this.onInputChange} register={this.state.register} login= {this.state.login} errors={this.state.errors} errorsLog={this.state.errorsLog} confirmMsg={this.state.confirmMsg} />} />
<Route path='/calculateur-route' component={CalculateurRoute} />
<Route path='/triangulateur' component={Triangulateur} />
</Switch>
</>
</>
)
}
}
export default connect((store) => {
return{
logged: store.auth.logged,
user: store.auth.user,
loginError: store.auth.loginError,
registerError: store.auth.registerError,
inputLogin: store.auth.inputLogin,
inputRegister: store.auth.inputRegister,
successMessage: store.auth.successMessage,
}
})(App)
So there it is, what am i doing wrong and how i should add my store/routing so it does work ?
Take a look at this document.
You need to import withRouter from react-router-dom and wrap the connect export you have there with a call to withRouter in the components that use React Router navigation.
Thus, your code should be something like:
// Before
export default connect((store) => {
return{
logged: store.auth.logged,
user: store.auth.user,
loginError: store.auth.loginError,
registerError: store.auth.registerError,
inputLogin: store.auth.inputLogin,
inputRegister: store.auth.inputRegister,
successMessage: store.auth.successMessage,
}
})(App)
// After
import { withRouter } from 'react-router-dom';
export default withRouter(connect((store) => {
return{
logged: store.auth.logged,
user: store.auth.user,
loginError: store.auth.loginError,
registerError: store.auth.registerError,
inputLogin: store.auth.inputLogin,
inputRegister: store.auth.inputRegister,
successMessage: store.auth.successMessage,
}
})(App))
This link also has some more information on how this works.

Send props to react router component from parent layout component

I have created a layout component called Main which sends user prop to its children component using React.cloneElement(children, { user: 'First Name'}), but unable to pass that user prop to Route component.
index.js
import React from "react";
import ReactDOM from "react-dom";
import { BrowserRouter as Router, Route } from "react-router-dom";
import Main from "./Main.jsx";
import Home from "./Home.jsx";
const App = () => (
<Router>
<Main>
<Route
path="/"
render={props => {
return <Home {...props} />;
}}
/>
</Main>
</Router>
);
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
Main.jsx
import React from "react";
export default props => {
return <div>{React.cloneElement(props.children, { user: "Username" })}</div>;
};
Home.jsx
import React from "react";
export default props => {
const { user } = props;
return <div>User - {user}</div>;
};
But unable to get user prop in Home component. If I do not use Route then it gets passed to Home.
When I do this, then Home receives user prop.
<Main>
<Home />
</Main>
How can I get user prop and send it to component rendered by Route?
Codesandox link of the scenario - https://codesandbox.io/s/kmyrj0zr8o
Well, you have to wrap Route into own component. The idea is to catch props and pass it manually to Route's render.
Here is modified source: https://codesandbox.io/s/zx508v60yl
Wrapping Route (MyRoute.jsx)
import React from "react";
import { Route } from "react-router-dom";
export default class MyRoute extends React.Component {
render() {
var { Component, path, exact, passedProps } = this.props;
return (
<Route
path={path}
exact={exact}
render={props => <Component {...props} {...passedProps} />}
/>
);
}
}
Modifying Main.jsx
import React from "react";
export default props => {
return (
<div>
{React.cloneElement(props.children, {
passedProps: {
user: "Username"
}
})}
</div>
);
};
Then changing Route to MyRoute in index.js
import React from "react";
import ReactDOM from "react-dom";
import { BrowserRouter as Router, Route } from "react-router-dom";
import Main from "./Main.jsx";
import Home from "./Home.jsx";
import MyRoute from "./MyRoute.jsx";
const App = () => (
<Router>
<Main>
<MyRoute path="/" Component={Home} />
</Main>
</Router>
);
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
When you need to pass props to the router component, you need to use the render prop to do that, but since React.cloneElement will actually just pass the props to the Route component, you can write a wrapper around Route, something like
const RouteWrapper = ({render, exact, strict, path, ...rest}) => {
return (
<Route
exact={exact}
strict={strict}
path={path}
render={(function(routerProps) => {
return render(...routerProps, ...rest)
})()}
/>
)
}
Which you can now use like
const App = () => (
<Router>
<Main>
<RouteWrapper
path="/"
render={props => {
return <Home {...props} />;
}}
/>
</Main>
</Router>
);

Couldn't pass root component props to children components

I'm fairly beginner in react/redux, and I'm creating a simple project using React, Redux and React Router v4. I use Redux to handle the state and it sucessfully passes states to root component Wrapper, but it seems it doesn't pass it to Home or other components.
When I console log this.props.Gallery in Wrapper, the data displays but when I do it on Gallery, it shows 'undefined'. I've passed children in Wrapper using React.cloneElement but it didn't work. Is there an explanation to this and some workaround?
My app project structure is
--wrapper
-home
-gallery
-about
-links
Here are the components
For Routing
App.js
import React from 'react'
import ReactDOM from 'react-dom'
import { BrowserRouter, Route, Switch } from 'react-router-dom'
import Wrapper from './components/Wrapper';
...
//import store
import {Provider} from 'react-redux'
import store , {history} from './store'
//router
const mainRouter=(
<Provider store={store} history={history}>
<Wrapper>
<BrowserRouter>
<Switch>
<Route exact path='/' component={ Home } />
<Route path='/portfolio' component={ Gallery } />
<Route path='/about' component={ About } />
<Route path='/links' component={ Links } />
</Switch>
</BrowserRouter>
</Wrapper>
</Provider>
)
ReactDOM.render(mainRouter, document.getElementById('root'))
Wrapper
import React from 'react'
import {bindActionCreators} from 'redux'
import {connect} from 'react-redux'
import * as actionCreators from '../actions/actionCreators'
function mapStateToProps(state){
return{
gallery:state.gallery
}
}
function mapDispatchToProps(dispatch){
return bindActionCreators(actionCreators,dispatch)
}
class Wrapper extends React.Component{
render(){
return(
<div>
{React.cloneElement(this.props.children, this.props)}
</div>
)
}
}
export default connect(
mapStateToProps,
mapDispatchToProps
)(Wrapper)
Home.js
import React from 'react'
import { NavLink } from 'react-router-dom'
//import component
...
class Home extends React.Component {
render(){
console.log(this.props.gallery)
return(
<div>
<p>this is home page</p>
<Header/>
<Nav/>
<Footer/>
</div>
)
}
}
export default Home;
Update 9/19
I've managed to created nested components in react router v4, but still unable to pass props to its direct children. It throws an error of children being undefined
And i've also updated my component structure to
--Home ~ this is more like a welcome containing app's menu
--Wrapper
-gallery
-about
-links
Here is what I did: I've moved Wrapper to be wrapper of gallery, about and links
App.js
<Provider store={store} history={history}>
<BrowserRouter>
<div>
<Route exact path="/" component={Home}/>
<Route path="/h" component={Wrapper}/>
</div>
</BrowserRouter>
</Provider>
Wrapper.js
function mapStateToProps(state){
return{
gallery:state.gallery
}
}
function mapDispatchToProps(dispatch){
return bindActionCreators(actionCreators,dispatch)
}
class HomePage extends React.Component {
render() {
return(
<div>
<Route path={`${this.props.match.path}/portfolio`} component={ Gallery } />
<Route path={`${this.props.match.path}/about`} component={ About } />
<Route path={`${this.props.match.path}/links`} component={ Links } />
</div>
)
}
}
export default connect(
mapStateToProps,
mapDispatchToProps
)(Wrapper)
You are correctly passing the props from Wrapper down to it's children. However, Home and Gallery are not it's children. They are great great grand children. The only child of Wrapper is the element(s) directly within it. In this case, that is BrowserRouter.
There are a great number of ways that you could make these props available to Home and Gallery. But passing directly from Wrapper is not one of them.
The simplest way would be to make a GalleryContainer class. This would be identical to the Wrapper class you already created (include it's connections to redux), except that it would explicitly render Gallery, as such;
render() {
return <Gallery {...this.props}/>;
}
Then use this new GalleryContainer as the component for the /portfolio route.
I finally managed to pass the props to child components.
I created two containers for my project, one as main container and other as parent to sub components Gallery, About and Links.
Here is structure in App.js
const mainRouter=(
<Wrapper>
<Provider store={store} history={history}>
<BrowserRouter>
<div>
<Route exact path="/" component={Home}/>
<Route path="/h" component={Container}/>
</div>
</BrowserRouter>
</Provider>
</Wrapper>
)
Then in Container, I connected it to redux, and created route to its sub children components and used render to render the components and pass gallery props to Gallery
<Route path={`${this.props.match.path}/portfolio`} render={()=><Gallery {...this.props.gallery}/>}/>
Here is the Container
import React from 'react'
import { bindActionCreators } from 'redux'
import { connect } from 'react-redux'
import * as actionCreators from '../actions/actionCreators'
import { Route } from 'react-router-dom'
//import components
...
function mapStateToProps(state) {
return {
gallery: state.gallery
}
}
function mapDispatchToProps(dispatch) {
return bindActionCreators(actionCreators, dispatch)
}
class Container extends React.Component {
render() {
return (
<div>
<ResponsiveNav/>
<main>
<Route path={`${this.props.match.path}/portfolio`}
render={ ()=><Gallery{...this.props}/> } />
<Route path={`${this.props.match.path}/links`}
render={ ()=><Link{...this.props}/> } />
<Route path={`${this.props.match.path}/about`}
render={ ()=><About{...this.props}/> } />
</main>
<Footer/>
</div>
)
}
}
export default connect(
mapStateToProps,
mapDispatchToProps
)(Container)

React Router will not render Route component unless page is refreshed

It seems my application will not render the component passed to <Route /> unless I refresh the page. What could I be doing wrong?
components/App/index.jsx
// dependencies
import React from 'react';
import PropTypes from 'prop-types';
import { Provider } from 'react-redux';
import { BrowserRouter as Router } from 'react-router-dom'
// components
import Header from '../Header';
// containers
import SidebarContainer from '../../containers/SidebarContainer';
import MainContainer from '../../containers/MainContainer';
const App = ({store}) => (
<Provider store={store}>
<Router>
<div className="wrapper">
<Header />
<div className="container-fluid container-fluid--fullscreen">
<div className="row row--fullscreen">
<SidebarContainer />
<MainContainer />
</div>
</div>
</div>
</Router>
</Provider>
);
App.propTypes = {
store: PropTypes.object.isRequired,
};
export default App;
containers/MainContainer.jsx
// dependencies
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { Route } from 'react-router-dom'
// components
import Dashboard from '../components/Dashboard';
import List from '../components/List';
// containers
import LoginContainer from './LoginContainer.jsx'
class Main extends Component {
render() {
console.log(this.props)
return(
<div className="wrapper">
<Route exact path="/" component={Dashboard} />
<Route path="/login" component={LoginContainer} />
<Route path="/users" component={List} />
</div>
);
}
}
const mapStateToProps = (state) => {
return {
token: state.authentication.token,
};
};
const MainContainer = connect(mapStateToProps, null)(Main);
export default MainContainer;
So it seems when I click on a <Link to="/users" /> component my path changes to http://localhost:3000/users but the component does not change from Dashboard to List
I'm also noticing that when I console.log this.props from MainContainer I do not see anything related to router such as this.props.location.pathname --perhaps I'm not structuring my application correctly?
After poking around the react-router issues page on github I found this thread: https://github.com/ReactTraining/react-router/issues/4671
It appears as though the redux connect method blocks context which is required by react-router package.
That being said, the fix for this is to wrap all redux connected components that have router components inside with withRouter() like so:
containers/MainContainer.jsx
// dependencies
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { Route, withRouter } from 'react-router-dom' // IMPORT withRouter
// components
import Dashboard from '../components/Dashboard';
import List from '../components/List';
// containers
import LoginContainer from './LoginContainer.jsx'
class Main extends Component {
render() {
console.log(this.props)
console.log(this.context)
return(
<div className="wrapper">
<Route exact path="/" component={Dashboard} />
<Route path="/login" component={LoginContainer} />
<Route path="/users" component={List} />
</div>
);
}
}
const mapStateToProps = (state) => {
return {
token: state.authentication.token,
};
};
// WRAP CONNECT METHOD
const MainContainer = withRouter(connect(mapStateToProps, null)(Main));
export default MainContainer;
I think you have to do little more tweak in your code to make it work. Assuming you use react-router v4, the following should solve your problem.
import { BrowserRouter, Route, Switch } from 'react-router-dom';
<Provider store={store}>
<BrowserRouter>
<div>
<Switch>
<SidebarContainer />
<MainContainer />
</Switch>
</div>
</BrowserRouter>
</Provider>

Resources