Single <Provider> for many components - javascript

I have the following structure in my root component
<div className={styles.main_container}>
<Provider store={store}>
<Topbar></Topbar>
</Provider>
<div className={styles.scene_and_tools}>
<Provider store={store}>
<Sidebar></Sidebar>
</Provider>
<Provider store={store}>
<Scene></Scene>
</Provider>
</div>
<Provider store={store}>
<Timeline></Timeline>
</Provider>
</div>
This works fine but I find it a bit not so "DRY" to repeat <Provider> for every single component that I want to pass context. So I tried
<div className={styles.main_container}>
<Provider store={store}>
<Topbar></Topbar>
<div className={styles.scene_and_tools}>
<Sidebar></Sidebar>
<Scene></Scene>
</div>
<Timeline></Timeline>
</Provider>
</div>
But I am getting the following error:
Failed propType: Invalid prop `children` supplied to `Provider`,
expected a single ReactElement. Check the render method of `App`.
Is there a way to make my code more DRY or I have to live with this redundancy?

The first variation is completely redundant. The only reason Provider exists in the first place is so that you can avoid passing the store down and explicitly. Otherwise you could just as well remove it and pass store as a prop to container components themselves.
The intended use case for Provider is to wrap the rootmost component. All children and grandchildren will then receive the store implicitly. You should only do it once, and put a single React element inside. Your second example does not work because you put three elements inside it: Topbar, div and Timeline. Wrap the outer div instead and it will work. Better yet, move Provider to the place where you call ReactDOM.render() and wrap your rootmost App component in it. Then you don't need it anywhere else.

Related

Fragments should contain more than one child - otherwise, there‘s no need for a Fragment at all react/jsx-no-useless-fragment

I am getting this error in the app.js file in my react project:-
Fragments should contain more than one child - otherwise, there‘s no need for a Fragment at all react/jsx-no-useless-fragment
import React from 'react';
import { BrowserRouter as Router, Routes, Route } from 'react-router-dom';
import NavMenu from './components/NavMenu';
export default function App() {
return (
<>
<Router>
<NavMenu />
</Router>
</>
);
}
I have been trying to find a solution but couldn't get one so can someone pls tell me how to fix this
Any help would be appreciated
I got the same error today due to the fact that I had ternary operator in the following manner:
<>
{isCheck ? (
<button
....
>
</button>
) : (
<div className={class}>
....
</div>
)}
</>
Whan I realized is that without the wrapping element it was not running as it is supposed to be.
My solution was just to replace the Fragment with div.
That made lint happy and the red flag disappeared.
React is not supposed to return adjacent elements, so if you just return one component or element there is no need for using <></> or <React.Fragment></React.Fragment> (these are equivalent).
You will probably encounter situations when you need to return 2 div elements one next to another (like siblings). In that case <></> will come in handy.
Your component is returning the equivalent to this:
<React.Fragment>
<Router>
<NavMenu />
</Router>
</React.Fragment>
<></> is shorthand syntax.
The fragment is for cases when you want to return more than one component or element as siblings. But because a component must only return one thing, we can use fragments to put a sort of "invisible wrapper" around two components to make it appear to be one.
Here is an example of when a component might like to use a fragment:
return (
<>
<Navbar />
<Router>
<Routes>
<Route index element={<Home />} />
</Routes>
</Router>
</>
);
Because you are only returning one element inside that fragment, it is not really doing anything, so just remove the fragment or include another component inside of it.
React is not supposed to return adjacent elements, so if you just return one component or element there is no need for using <></>.
Remove <> </> as it might hinder your program for now
But in future for more than 1 components you might need to use these.

Where is the root of my component hierarchy?

I am creating a modal for my React / Redux application and found some great answers in this post.
One thing I cannot figure out though is where "the root of [my] component hierarchy" is.
My application is structured as below:
app.js
const jsx = (
<React.StrictMode>
<Provider store={store}>
<AppRouter />
</Provider>
</React.StrictMode>
)
AppRouter.js
...
return (
<React.Fragment>
<LoadingSpinnerFullPage />
<Router history={history}>
<Suspense fallback={<LoadingSpinnerFullPage />}>
<div className="appContainer">
<Route exact path={SidebarLocation.path} render={(props) => privatePath({component: Sidebar})} />
<div className="app-MainContent content-container-parent ">
<Route exact path={HeaderLocation.path} render ={(props) => privatePath({component: Header, ...props})} />
<Switch>
Where would my component "hierarchy root" be considered to be located?
Is it
directly under <React.Fragment> ?
alongside all routes directly under < Router > ?
at the same level as < AppRouter /> in app.js inside < Provider > ?
If my modal is connected to the redux store listening to modal.isOpen - would placing it in the Router cause re-rendering of my entire app each time the modal is opened or closed?
Many thanks! /K
The element that you pass to ReactDOM.render is technically the "root" of your component hierarchy, which I'm guessing would be <React.StrictMode> here (assuming you call it with the jsx variable).
If your modal is connected to the Redux store, then it must be rendered somewhere inside the react-redux <Provider> element (even though that's not technically your root here).
I would suggest rendering it as a child of <Provider>, alongside the <AppRouter> component. That's the closest it can be to the root element without having to couple it to one of your other components.
You may need to wrap them both in a fragment to keep the Provider component happy.
const jsx = (
<React.StrictMode>
<Provider store={store}>
<React.Fragment>
<AppRouter />
<ModalRoot />
</React.Fragment>
</Provider>
</React.StrictMode>
)

Not able to use WithRouter of react-router-dom

I am new to the react js. Here I am trying to use the withrouter to get the info of my location.
SO, I have following structure.
index.js
ReactDOM.render(
<App />
, document.getElementById('root'));
App.js
<Provider store={store}>
<div>
<Header />
<Main />
</div>
</Provider>
Main.js
return (
<Router history={history}>
<div>
{this.props.isFetching && <Loading />}
<Switch>
<PrivateRoute exact path="/" component={LandingPage} />
<PrivateRoute exact path="/create-job" component={NewJob} />
<PrivateRoute exact path="/user-jobs" component={JobList} />
<Route exact path="/login" component={Login} />
</Switch>
</div>
</Router>
Now, I am trying to use the withRouter in the Header.js. which is not a part of the Router. SO,
import { withRouter } from "react-router-dom";
export default withRouter(connect(mapStateToProps, { logout })(Header));
I tried using this way. So, it is giving me the following error.
You should not use <Route> or withRouter() outside a <Router>
What is it that I am doing wrong over here ?
You're rendering the Header component (which uses withRouter) outside the Router component. You need to make sure that all the Routes and the components using withRouter need to be a child of Router, at some level in the hierarchy.
In your case, maybe wrap the div in the App with the Router?
Details from source
The error is generated here when Route doesn't get passed a context from its provider. And, withRouter is just a wrapper over Route. The only time the context is not available is when the Route is not nested inside a Router somewhere.
Your issue is that withRouter props get blocked by PureComponent check, put it after connect:
export default connect(mapStateToProps, { logout })(withRouter(Header));
See: https://github.com/ReactTraining/react-router/blob/master/packages/react-router/docs/api/withRouter.md#important-note
Personally I prefer to have my providers in one place in index.js not App.js, your whole app should be wrapped in Router, not a part of App.js:
const WrappedApp = (
<Provider store={store}>
<BrowserRouter>
<App />
</BrowserRouter>
</Provider>
);
ReactDOM.render(WrappedApp, document.getElementById('root'));
You need to move <Header> component inside <Router> component, otherwise it doesn't work as the error says.
So you want to do 2 things, inject the state in the Header, and inject routing information in Header.
If you are outside of the <Router/> component you will not be able to use withRouter(, simply because you will not have any match props to link to your compoonent.
What you need to do is to create something called layouts.... and use the layouts in the page definition like this.
Lets say you have this Route <PrivateRoute exact path="/" component={LandingPageContainer} />
Now inside the LandingPage you need to do something like
landingPageContainer.js
render() {
return (
<SimpleLayout header={<Header ...this.props/> footer={<Footer ...this.props/>}>
<LandingPageDummyPage ...this.props/>
<SimpleLayout/>
);
}
export default connect(withRouter(LandingPageContainer);
this will copy all the props passed to the Simple Layout to your LandingPageLayout, header and footer
simpleLayout.js
render() {
return (
<div>{this.props.header} </div>
<div>{this.props.children} </div>
<div{this.props.footer} </div>
);
}
export withRouter(SimpleLayout);
advice . read the router documentation and try to understand whats the purpose if it... you didn`t quite get it :) ...

React Router V4 is updating URL, but not refreshing (React, Redux)

I am trying to use React-Router V4 to add routes to my app, but it is not working at all. Basically, I'm trying to programatically change the route with history.push, which is updating the browser URL, but not changing anything inside the actual app.
NOTE: I am using redux.
The only answered question on this issue is:
React history.push() is updating url but not navigating to it in browser
However, I've tried the answer to the above question, and it doesn't work for me.
Here are the important snippets:
Topmost file (index.js)
...
ReactDOM.render(
<BrowserRouter>
<Provider store={store}>
<App/>
</Provider>
</BrowserRouter>
, document.getElementById('root'));
...
Component containing routes
...
export default function ContentRouter() {
return <div className="content">
<Route exact path="/dashboard" component={TmpDashboard}/>
<Route exact path="/" component={() => {
return <h1>Home</h1>
}}/>
</div>
}
Component pushing routes
...
this.handleGroupClick = (group) => {
this.props.history.push(`/groups/${group}`);
this.props.onOpenChange(false);
};
...
export default withRouter(connect(mapStateToProps, mapDispatchToProps(DrawerConnector))
After a lot of searching in the completely wrong place, I figured out what was wrong. The lack of updating was being caused by Redux
Whenever a component is wrapped in connect it causes updates to be blocked, and the view doesn't update.
The solution is mentioned here:
https://github.com/ReactTraining/react-router/issues/4671#issuecomment-285320076
Basically, every component with a Route or a React-Router thing inside it must be wrapped with withRouter
EDIT: Only the top level component that uses connect should be wrapped in withRouter. Note that this may cause performance issues
EDIT: The way I got around the increased coupling was to have a component just to deal with routes. That way, I only need to wrap that component, and the component with a Router.
Here's a setup that works:
The main App:
class App extends React.Component {
render() {
return (
<BrowserRouter>
<div>
/* this is a common component and happens to need to redirect */
<Route component={CommonComponentThatPushesToHistory} />
<div id="body">
/* I usually place this switch in a separate Routes file */
<Switch>
<Route exact path="FooRoute" component={FooPage}/>
<Route exact path="BarRoute" component={BarPage}/>
</Switch>
</div>
/* another common component which does not push to history */
<Footer />
</div>
</BrowserRouter>
);
}
}
export default withRouter(App);
CommonComponentThatPushesToHistory
class CommonComponentThatPushesToHistory extends React.Component{
render(){
return(
<button type="button"
onClick={() => {this.props.history.push('some-page');}}>
Click to get redirected</button>
);
}
}
FooPage may have a child component that pushes to history:
class FooPage extends React.Component{
render(){
return(
<MyChild history={this.props.history} />
);
}
}
Then MyChild component can push to history the same way CommonComponentThatPushesToHistory does.

Reactjs: Changing props outside the main scope

I made a test where I do React.render() in a child component and pass a prop to it. The app structure looks like:
<App>
<Child />
</App>
Then in <Child />, I render <Outside /> component with another React.render(). When I checked in Chrome React firebug, the App structure is:
<App>
<Child />
</App>
<Outside />
instead of:
<App>
<Child />
<Outside />
</App>
However, when passing a {selected : true } state from <App />, it displays well as a prop in <Outside />, but when I make a state change to { selected : false } in <App />, both <Child /> and <Outside /> don't receive the updated prop. I think it happens because <Outside /> is out of the <App /> scope so the data flow doesn't flow well. (Sorry for my English)
You can check the test here: http://jsbin.com/yazaqo/1/edit?js,console,output
What I'm trying to ask is: Is there any other way to update a component which is outside the App scope?
The Child component is receiving the updated prop, its just not being logged to the console.
Only render and componentWillReceiveProps are invoked when a component receives new props, so if you move the console.log to either one of those methods, you'll see the updated value.
This is also why Outside is not receiving the updated prop. You're rendering it in componentDidMount, which is only invoked once, when the component is mounted.
It should be rendered in Child's render method the same way Child is rendered in the App component. The other option would be to render it in componentWillReceiveProps, although you may run into some problems doing it that way.
Hope this helps.

Categories