Controlling redux properties in navigation bar in reactjs - javascript

I have a component (DatasetPage) which renders some images of different datasets for an image selected. This depends on the tab clicked on the navigation top bar. The thing is that one of the dataset in my case is very big and so it takes more time to load the page. If I wait until the page is loaded everything works well but, if I click into another tab (another dataset) before the reducer delivers the properties to my component (ImageWithRelateds), the new page is loaded with the information of the other(last) dataset, which was not loaded yet.
So, I have thought about a solution which could be block the navigation through the navigation bar while I have the Loading running. But the thing is that this loading thing is controlled in the ImageWithRelateds.js component and the navigation bar is controlled from App.js. So I would need to access from App.js to the isLoading attribute of ImageWithRelateds.js (which I already have) but I don't know how to do it. I just found ways to access from children to parent attributes but not backwards. If you could help me with that or just proposing another solution I would be very grateful.
App.js
import React, { Component } from 'react';
import { IndexLink } from 'react-router';
import '../styles/app.scss';
class App extends Component {
constructor(props){
super(props);
this.options = this.props.route.data;
}
renderContent(){
if(this.props.route.options) {
return(<div className="navbar-header nav">
<a className="navbar-brand" id="title" href="/" >
IMAGES TEST WEB
</a>
<li className="nav-item" key={`key-9999`}>
<IndexLink to='/home' className="nav-link active" href="#">HOME</IndexLink>
</li>
{this.props.route.options.map((opt,i)=>{
return this.returnOptions(opt,i);
})}
</div>
);
}
}
returnOptions(opt,i){
return(<li className="nav-item" key={`key-${i}`}>
<IndexLink to={opt.link} className="nav-link active"
href="#">{opt.name}</IndexLink>
</li>);
}
render() {
return (
<div className="main-app-page">
<nav className="navbar navbar-default color-navbar fixed">
<div className="container-fluid">
{this.renderContent()}
</div>
</nav>
<div className="content">
{this.props.children}
</div>
</div>
);
}
}
export default App;
Routes.js
import React from 'react';
import ReactDOM from 'react-dom';
import { Route, IndexRoute, browserHistory, Link } from 'react-router';
import App from './common/App';
import NotFound from './common/NotFound';
import Store from './store';
import Home from './components/Home';
import DatasetPage from './components/Images/DatasetPage';
import ImageWithRelateds from './components/Images/ImageWithRelateds';
import { options_NavBar } from './customize.js';
import { getQimList, resetQimList} from './actions/index';
const Test = ()=>{
return(<h2 style={{"paddingLeft":"35%"}} >W E L C O M E !</h2>)
};
export default (
<Route path="/" components={App} options={options_NavBar} history={browserHistory}>
<IndexRoute components={Test}/>
<Route path="/home" component={Home} />
<Route path="images" >
<Route path="oxford" component={DatasetPage} onEnter={()=>{
Store.dispatch(resetQimList());
Store.dispatch(getQimList('oxford'));
}} />
<Route path="paris" component={DatasetPage} onEnter={()=>{
Store.dispatch(resetQimList());
Store.dispatch(getQimList('paris'));
}} />
<Route path="instre" component={DatasetPage} onEnter={(e)=>{
Store.dispatch(resetQimList());
Store.dispatch(getQimList('instre'));
}} />
<Route path=":id" component={ImageWithRelateds} />
</Route>
<Route path="*" component={NotFound} />
</Route>
);
Thank you so much!

One of the most basic principle in react that the parent give props to children, and the children emit events to the father (to avoid 2 way binding)
so, your App.js should have state, with isLoading variable, and to the ImageWithRelateds component you should pass an event (function) something like this:
<Route path=":id" render={(props) => <ImageWithRelateds {...props} onFinishLoading={loadingFinished} />}>
and inside your component (that should be with state) should have function like this:
function loadingFinished() {
this.setState(prev => ({ ...prev, isLoading: false }))
}
and then, you would know inside you App.js if the loading inside the ImageWithRelateds component finished, and then you would able to do any validation you would like
I suggest to you to read this article about passing events (functions) to components, why it's needed and how to do it effectively
Hope that helped!
Edit:
your final Routes.js code should look something like that:
export default class Routes extends React.Component {
constructor() {
super();
this.state = { isLoading: false };
this.onLoadingFinishded = this.onLoadingFinishded.bind(this);
}
onLoadingFinishded() {
this.setState(state => {
...state,
isLoading: false
});
}
render() {
return <Route path="/" components={App} options={options_NavBar} history={browserHistory}>
<IndexRoute components={Test}/>
<Route path="/home" component={Home} />
<Route path="images" >
<Route path="oxford" component={DatasetPage} onEnter={()=>{
Store.dispatch(resetQimList());
Store.dispatch(getQimList('oxford'));
}} />
<Route path="paris" component={DatasetPage} onEnter={()=>{
Store.dispatch(resetQimList());
Store.dispatch(getQimList('paris'));
}} />
<Route path="instre" component={DatasetPage} onEnter={(e)=>{
Store.dispatch(resetQimList());
Store.dispatch(getQimList('instre'));
}} />
<Route path=":id" render={(props) => <ImageWithRelateds
{...props}
onLoadingFinished={this.onLoadingFinishded} />} />
</Route>
<Route path="*" component={NotFound} />
</Route>
}
}
(i can't ensure that code exactly running because i don't have all of your project, but that most likely it)

Related

Building a Higher Order Component Error Boundary

The project I am working on needs better error handling and to begin I've decided to implement reacts ErrorBoundary hook componentDidCatch which I was able to implement simply in a single component. However a senior developer has recommended I make my error boundary a Higher Order Component so that I can wrap the entire application in it. This is where I am running into trouble because despite reading the documentation higher order components make little sense to me.
This is what I have implemented so far:
my HOC
import React from "react";
import ErrorScreen from "./ErrorScreen"
export default function NewErrorHandler(WrappedComponent) {
class ErrorHandler extends React.Component {
constructor(props) {
this.state = {hasError: false}
};
componentDidCatch(error, info) {
this.setState({ hasError: true })
}
render() {
if(this.state.hasError) {
return (
<ErrorScreen/>
)
} else {
return this.props.children
}
}
}
}
My issue so far is Im not exactly sure how to wrap the application in the error boundary. In all the examples Ive seen the HOC's are wrapping around functional components easily through exporting however the way this application is set up there is no explicit exported function that I can see:
import ReactDOM from "react-dom";
import React from "react";
import { store, history } from "./store";
import { Provider } from "react-redux";
import { Route, Switch } from "react-router"; // react-router v4
import { ConnectedRouter } from "connected-react-router";
import DetectAdblock from "./components/DetectAdblock";
import ErrorBound from "./components/ErrorHOC";
const Encounter = Loadable({
loader: () => import("./components/Encounter/Encounter"),
loading: Loading,
delay: 300
});
const VerifyInsuranceList = Loadable({
loader: () => import("./components/VerifyInsurance/InsuranceList"),
loading: Loading,
delay: 300
});
const VideoChat = Loadable({
loader: () => import("./components/VideoChat"),
loading: Loading,
delay: 300
});
document.addEventListener("DOMContentLoaded", () => {
ReactDOM.render(
<Provider store={store}>
<ConnectedRouter history={history}>
<TokenLoader>
<DetectAdblock />
<BrowserBanner />
<EnvBanner />
<IdleMonitor />
<TokenRefresher />
<MonotonicClock frequency={15} />
<RtcMonitor />
<PatientPoller pollInterval={30000} />
<Redirect />
<Switch>
<Route exact={true} path="/" component={ProviderDashboard} />
<Route
exact={true}
path="/accept-invitation/:inviteID"
component={AcceptInvite}
/>
<Route exact={true} path="/login" component={Login} />
<Route
exact={true}
path="/reset-password/:resetID"
component={ResetPassword}
/>
<Route
exact={true}
path="/request-password-reset"
component={ForgotPassword}
/>
<Route
exact={true}
path="/waiting-room"
component={ProviderAvailablePatients}
/>
<Route exact={true} path="/encounter" component={Encounter} />
<Route exact={true} path="/video" component={VideoChat} />
<Route
exact={true}
path="/providers"
component={ManagerProviders}
/>
<Route exact={true} path="/providers/new" component={Invite} />
<Route
exact={true}
path="/providers/edit/:providerID"
component={ProviderEdit}
/>
<Route
exact={true}
path="/providers/audit/:providerID"
component={ProviderAudit}
/>
<Route
exact={true}
path="/activity-monitor"
component={ActivitySummary}
/>
<Route
exact={true}
path="/encounter-monitor"
component={EncounterMonitor}
/>
<Route
exact={true}
path="/encounter-monitor/:encounterID"
component={EncounterMonitorDetails}
/>
<Route exact={true} path="/billing" component={BillingTab} />
<Route exact={true} path="/patients" component={PatientTab} />
<Route exact={true} path="/rounding" component={RoundingTab} />
<Route
exact={true}
path="/patients/:patientID"
component={PatientChart}
/>
<Route
exact={true}
path="/active-patient-chart/:patientID"
component={ActivePatientChart}
/>
<Route
exact={true}
path="/insurnace-history/:patientID"
component={InsuranceHistory}
/>
<Route
exact={true}
path="/verify-insurance"
component={VerifyInsuranceList}
/>
<Route render={() => <div>Not Found</div>} />
</Switch>
</TokenLoader>
</ConnectedRouter>
</Provider>
document.getElementById("app")
);
});
Here I have left out some of the import statements but have shown how I am importing my ErrorHOC.js. Any insight into how to wrap the whole application would be super helpful. If I am missing information here needed for understanding please let me know.
As discussed in the comments, error boundary is NOT a use case for HOC, any way here is a possible example of how the logic should work:
// withErrorBoundary.js
class ErrorHandler extends React.Component {}
// HOC wrapping the passed component
export default function withErrorBoundary(WrappedComponent) {
const Component = (
<ErrorHandler>
<WrappedComponent />
</ErrorHandler>
);
return Component;
}
// index.js
import withErrorBoundary from "./withErrorBoundary.js";
const App = <Provider store={store}>...</Provider>;
// Usage
const AppWithErrorBoundary = withErrorBoundary(App);
ReactDOM.render(<AppWithErrorBoundary />, document.getElementById("app"));
Error boundary should be a wrapper component so you can pass helpful props to it, easier reuse case on multiple usages, and more:
class ErrorBoundaryWrapper extends React.Component {
render() {
return (
<>
{/** Use other props passed to wrapper **/}
<div>...</div>
{this.props.children}
</>
);
}
}
// Usage, see the advantage over HOC
<>
<ErrorBoundaryWrapper specialProps={props1}>
<Component1 />
</ErrorBoundaryWrapper>
<ErrorBoundaryWrapper specialProps={props2}>
<Component2 />
</ErrorBoundaryWrapper>
</>
See similar question: What ErrorBoundary actually does.

React Router hierarchy with rendering components

I'm passing an object to a class component and want to have that component open in a different route. The routing works, but all the time the props are undefined in the child component unless I move the <Route path=''...> line before every other component. The props work, but the page display is not correct.
PARENT
import React, { Component } from "react";
import { BrowserRouter as Router, Switch, Route } from "react-router-dom";
import Header from "./Header";
import DarbaiLT from "./DarbaiLT";
import AnObject from "./AnObject";
let clickeddiv = ''
class App extends Component {
onObjectClick = (clickeddivffromdarbai) => {
clickeddiv = clickeddivffromdarbai;
console.log("clickeddiv: ", clickeddiv);
};
*//clickeddiv is data coming from DarbaiLT component*
};
render() {
return (
<Router>
<div>
<Switch>
<Route path="/object/:id" exact component={AnObject} />
<Route path="/about" exact component={About} />
<Route path="/contacts" exact component={Contacts} />
<Route path="/partners" exact component={Partneriai} />
<DarbaiLT onObjectClick={this.onObjectClick} />
<AnObject dataforComponent={clickeddiv}/> //when this line is the last, it's not working
</Switch>
<Footer />
</div>
</Router>
);
}
}
export default App;
CHILD
import React, { Component } from "react";
class AnObject extends Component {
constructor(props) {
super(props);
}
render() {
return (
<>
<div onClick={() => console.log(this.props.dataforComponent)}>
<img src='../smth/pic.jpg' width="100%" />
</div>
</>
);
}
}
export default AnObject;
if I move the line to the top, passing of props works, but then all pages show only the AnObject, and doesn't render the About, Contacts and so on...
<Router>
<div>
<Header />
<Slides />
<Switch>
<AnObject stateforyou={clickeddiv}/> //if the line is here, routing doesn't work
<Route path="/object/:id" exact component={AnObject} />
<Route path="/about" exact component={About} />
<Route path="/contacts" exact component={Contacts} />
<Route path="/partners" exact component={Partneriai} />
<DarbaiLT onObjectClick={this.onObjectClick} />
</Switch>
<Footer />
</div>
</Router>
the documentation of React Router states that: "All children of a < Switch > should be < Route >".
Using the react-router Switch is like using switch case statement of javascript, whenever the link is matched to the route, the passed component gets rendered. Your problem here, however, is how to pass props to the rendered component which is done this way:
<Switch>
<Route path="/object/:id" exact component={() => <AnObject stateforyou={clickeddiv}/> } />
<Route path="/about" exact component={About} />
<Route path="/contacts" exact component={Contacts} />
<Route path="/partners" exact component={Partneriai} />
</Switch>
I'm not sure I totally understand the issue. But when you use <Switch> in the react-router, it will render only the first match.
You have a switch set up like this:
<Switch>
<Route path="/about" exact component={About} />
<DarbaiLT onObjectClick={this.onObjectClick} />
<AnObject dataforComponent={clickeddiv}/>
</Switch>
This means that if the visitor is at the url /about, it will render only the About component and nothing else. If you want to be able to render multiple components simultaneously as siblings, remove the <Switch>...</Switch>.

How to redirect to another Page using a button in Reactjs

I am trying to learn how to redirect through pages using React.
I have tried to write some code on my own but i keep getting problems. I Created a route class for the class path, and 2 classes to move through. And the route class is imported to the app class. I am not pasting any data from the second class because its a written paragraph to be displayed.
This is what i have done:
import React from 'react'
import {BrowserRouter as Router, Route, Switch} from 'react-router-dom';
import Firsttry from './firsttry'
import Comp2 from "./comp2";
const Routes = () => (
<Router>
<Switch>
<Route path="/" component={Firsttry} />
<Route path="/comp2" component={Comp2} />
</Switch>
</Router>
);
export default Routes;
Second class:
import React, { Component } from "react";
import { Redirect } from "react-router-dom";
class Firsttry extends Component {
constructor(props) {
super(props);
this.state = {
redirect: false
};
}
onclick = () => {
this.setState({
redirect: true
});
};
render() {
if (this.state.redirect) {
return <Redirect to="/comp2" />;
}
return (
<div>
<p> hello</p>
<button onClick={this.onclick}>click me</button>
</div>
);
}
}
export default Firsttry;
Switch the routes. May be always your first route is getting hit and Comp2 is never rendered.
<Switch>
<Route path='/comp2' component={Comp2} />
<Route path='/' component={Firsttry}/>
</Switch>
Or you have another option: adding exact prop to your route.
<Switch>
<Route exact path='/' component={Firsttry}/>
<Route exact path='/comp2' component={Comp2} />
</Switch>
Only one Route inside a Switch can be active at a time, and / will match every route. You can add the exact prop to the / route to make sure it will only match on the root path.
const Routes = () => (
<Router>
<Switch>
<Route exact path="/" component={Firsttry} />
<Route path="/comp2" component={Comp2} />
</Switch>
</Router>
);

React router dom and layouts

I want to use layouts with my react-router-dom, at this moment i am doing that like this
const DefaultLayout = ({children, ...rest}) => {
return (
<div className={styles.wrapper}>
<Header/>
{children}
<Footer/>
</div>
)
};
const DefaultRoute = ({component: Component, ...rest}) => {
return (
<Route {...rest} render={matchProps => (
<DefaultLayout>
<Component {...matchProps} />
</DefaultLayout>
)}/>
)
};
render(
<Provider store={store}>
<HashRouter>
<Switch>
<DefaultRoute exact path="/" component={AdvertList}/>
<DefaultRoute exact path="/user" component={UserOptions}/>
<Route path="/login" children={Login}/>
<Route render={
() => (
<div>
Not found
</div>
)
}/>
</Switch>
</HashRouter>
</Provider>,
document.querySelector('#app')
);
it works okay, both UserOptions and AdvertList components are rendered inside DefaultLayout, and Login component does not, but in official documentation i didn't find solution like that, instead there is "nested routing" where you adding new nested routes in subclasses, like
if you need default layout u make it on route /, then if you need advert list with that layout, in layout component you defined route /adverts and adding link to it, and so on, each sub component uses layout of parent one.
But in my case there is already product list on route /, and i need to change that content to other products list regarding link pressed, not to add to parent layout, but to change it part. Here is my code,
const { BrowserRouter, Route, Switch, Link } = window.ReactRouterDOM;
const { Component } = window.React;
const About = () => ('About');
const MiscProducts = () => ('Misc products');
class AdvertsList extends Component {
render() {
return (
<div className="container">
<header>Header</header>
<main>
<nav>
<Link to="/miscProducts">Misc Products</Link> #
<Link to="/about">About</Link>
</nav>
<div className="content">
Main Products
</div>
</main>
<footer>Footer</footer>
<Route path="/miscProducts" component={MiscProducts} />
</div>
)
};
};
class App extends Component {
render() {
return (
<BrowserRouter>
<Switch>
<Route path="/" component={AdvertsList} />
<Route path="/about" component={About} />
<Route path="*" render={
() => (
<div>
Not found
</div>
)
}/>
</Switch>
</BrowserRouter>
);
}
}
ReactDOM.render(<App />, document.querySelector("#app"));
http://jsfiddle.net/gmcke2a4/6/ here main products loaded by default, and when i press misc products, misc products must be loaded instead of main one.
p.s. And why about doesn't work?
Login Fix
<Route path="/login" children={Login}/> this seems wrong because children component expects function which return nodes i think.Try <Route path="/login" children={() => (</Login />)}
Layout
But in my case there is already product list on route /, and i need to
change that content to other products list regarding link pressed, not
to add to parent layout
You can create component which renders specific products like this.
const MainProducts = () => 'Main Products'
const GummyBearsProducts = () => 'GummyBears'
const Products = props => (
<div className="products-container">
<Switch>
<Route path={`${props.location.pathname}`} component={MainProducts}/>
<Route path={`${props.location.pathname}/gummy-bears`} components={GummyBearProducts}/>
</Switch>
</div>
)
And then use it as follows.
class AdvertsList extends Component {
render() {
return (
<div className="container">
<header>Header</header>
<main>
<nav>
<Link to="/products">Products</Link> #
<Link to="/about">About</Link>
</nav>
<div className="content">
<Route path="/products" component={Products} />
</div>
</main>
<footer>Footer</footer>
</div>
)
};
};
React router is great in rendering specific components.I hope it answers your question.Cheers!
If you are using react-router-dom v6. Then follow the below procedure to configure react-router-dom,
App.jsx:
import './App.css';
import { BrowserRouter, Routes, Route } from 'react-router-dom'
import Home from './Components/Home'
import About from './Components/About'
import Layout from './Components/Layout'
function App() {
return (
<div className="App">
<BrowserRouter>
<Routes>
<Route path="/" element={<Layout />}>
<Route path="/" element={<Home />} />
<Route path="about" element={<About />} />
</Route>
</Routes>
</BrowserRouter>
</div>
);
}
export default App;
After configuring the react router in App.jsx. I am creating 3 components Home, About and Layout. Home and About are regular components and Layout component is to handle the Layout part in react-router-dom using Outlet.
Layout.jsx
import { Outlet, Link } from 'react-router-dom'
export default function Layout() {
return (
<>
<Link to="/">Home</Link>
<Link to="/about">About</Link>
<Outlet />
</>
)
}
Home.jsx
export default function Home() {
return (
<>
<p>This is Home</p>
</>
)
}
About.jsx
export default function About() {
return (
<>
<p>This is About Us</p>
</>
)
}

Render nested route component on new page

I want to be able to have a Portfolio page (example.com/portfolio), and a dynamic route for individual case studies (example.com/portfolio/case-study/dynamic-url-slug). Currently, the new component that should render in its own page is still rendering within the page (understandable, as the markup declares the route within the containing div). But how do I get it to render on its own page?
App.js (where all routes are declared)
import React, { Component } from 'react';
import { Switch, Route } from 'react-router-dom';
import Home from './components/Pages/Home/Home';
import About from './components/Pages/About/About';
import Portfolio from './components/Pages/Portfolio/Potfolio';
import CaseStudy from './components/Pages/Portfolio/CaseStudyPage';
export default class App extends Component {
render() {
return (
<div className="icon-container" id="outer-container">
<div className="pages">
<Switch>
<Route exact path='/' component={ Home } />
<Route path='/about' component={ About } />
<Route path='/portfolio' component={ Portfolio } />
<Route exact path={`/portfolio/case-study/:caseSlug`}
render={(props) => <CaseStudy />} />
</Switch>
</div>
</div>
)
}
}
Portfolio.js
import React, { Component } from 'react';
import '../styles/vendor/swiper/swiper.min.css';
import Swiper from 'react-id-swiper';
import { Link, Route } from 'react-router-dom';
import CaseStudyPage from './Pages/Work/CaseStudyPage';
const case_studiesURL = "http://myprivateblogapi.com/wp-json/wp/v2/case_studies?_embed";
const case_URL = '/portfolio/case-study/';
export default class Portfolio extends Component {
constructor(props) {
super(props);
this.state = {
case_studies: [],
isLoading: true,
requestFailed: false
}
}
componentDidMount() {
fetch(case_studiesURL)
{/* fetching all the appropriate data */}
}
renderPortfolioItem(data) {
return props => <CaseStudyPage data={data} {...props} />
}
render() {
if(this.state.isLoading) return <span>Loading...</span>
const params = {
{/* swiper parameters */}
}
let case_studies_items = this.state.case_studies.map((case_studies_item, index) => {
return (
<div className="portfolio-slide" id={`swiper-slide-${index}`}
key={index}
>
<Link className="portfolio-link"
to={`${case_URL}${case_studies_item.slug}`}>
<h3 className="portfolio-swiper--slide-title"> {case_studies_item.title.rendered}</h3>
</Link>
<Route exact path={`${case_URL}:caseSlug`}
render={this.renderPortfolioItem(case_studies_item)} />
</div>
)
});
return(
<div className="portfolio-swiper--container">
<Swiper {...params}>
{case_studies_items}
</Swiper>
</div>
)
}
}
You should define a route for each different views in react router,
<Switch>
<Route exact path='/' component={ Home } />
<Route exact path='/about' component={ About } />
<Route exact path='/portfolio' component={ Portfolio } />
<Route exact path='/portfolio/case-study' component={ CaseStudy } />
<Route exact path='/portfolio/case-study/:caseSlug' component {CaseStudyDetails} />
</Switch>
and you don't need to create a render method to pass props to your view components. You can easily reach router props inside of a react component if it is already rendered into Router,
this.props.match
this.props.location
this.props.history
as an example you can get your dynamic parameter inside of CaseStudy component like,
this.props.match.caseSlug

Categories

Resources