Trigger a rerender of parent component when a child component is rendered - javascript

I am using the following material-ui theme Paperbase and within the Header.js component, I have the following useEffect hook:
const [temperature, setTemperature] = useState([]);
const getTemperature= async () => {
try {
const response = await fetch('/get-temperature')
const tempData = await response.json();
setTemperature(tempData);
} catch (err) {
console.error(err.message);
}
};
useEffect(() => {
getTemperature();
}, []);
The main purpose of this, is to display the current temperature as info, within the header component, which gets displayed at first page load/render.
Now within my App.js below, I have the following return setup where the above Header component is called.
return (
<Router>
<UserProvider myinfo={myinfo}>
<Switch>
<Route path="/">
<ThemeProvider theme={theme}>
<div className={classes.root}>
<CssBaseline />
<nav className={classes.drawer}>
<Hidden xsDown implementation="css">
<Navigator />
</Hidden>
</nav>
<div className={classes.app}>
<Header
onDrawerToggle={handleDrawerToggle}
/>
<main className={classes.main}>
<Switch>
<Route exact path="/new-user"
render={(props) => <Content key={props.location.key} />}
/>
<Route exact path="/view-results"
render={(props) => <ViewResults key={props.location.key} />}
/>
</Switch>
</main>
</div>
</div>
</ThemeProvider>
</Route>
</Switch>
</UserProvider>
</Router>
);
My question is, how can I trigger a rerender of Header (parent) whenever the user routes to either /new-user or /view-results which in turn calls either Content.js or ViewResults.js, inorder to make the useEffect in Header.js refresh the data, from the REST api fetch and display the latest temperature in the header again?
Ideally anytime Content.js or ViewResults.js is rendered, ensure that Header.js getTemperature() is called.
Any help would be much appreciated.

Your current code is pretty close to a multi layout system. As being a component child of Route, you can access the current location via useLocation() or even the native window.location.pathname.
This is my example of multi layout React app. You can try to use it to adapt to your code.
The MainLayout use a fallback route when no path is specified. It also contains a Header and include a page
const Dispatcher = () => {
const history = useHistory();
history.push('/home');
return null;
};
const App = () => (
<BrowserRouter>
<Switch>
<Route
component={Dispatcher}
exact
path="/"
/>
<Route
exact
path="/login/:path?"
>
<LoginLayout>
<Switch>
<Route
component={LoginPage}
path="/login"
/>
</Switch>
</LoginLayout>
</Route>
<Route>
<MainLayout>
<Switch>
<Route
component={HomePage}
path="/home"
/>
</Switch>
</MainLayout>
</Route>
</Switch>
</BrowserRouter>
);
And here is the code for MainLayout
const MainLayout = ({ children }) => (
<Container
disableGutters
maxWidth={false}
>
<Header location={props.location} />
<Container
component="main"
maxWidth={false}
sx={styles.main}
>
{children}
</Container>
<Footer />
</Container>
);
Now that Header can be anything. You need to put a capture in this component
import { useLocation } from 'react-router-dom'
cont Header = (props) => {
const { pathname } = useLocation();
//alternatively you can access props.location
useEffect(() => {
if (pathname === '/new-user') {
getTemperature();
}
}, [pathname]);
};
Note that Header is not a direct descendant of Route therefore it cannot access the location directly via props. You need to transfer in chain
Route -> MainLayout -> Header
Or better use useLocation

Related

Refresh component on route only in React-Router V5

Is there a way to refresh a route when using react router v5.
I have a onClick function which navigates to a route and refetches the data, however that is only if I use history.push('/') I want to refresh the component even if I am just on the same route '/' for example I am currently doing:
is there a better way to navigate programatically in react-router?
currently this handleChange method is in the header component.
const handleChange = (event: any) => {
setValue(event.target.value);
if (history.location.pathname === "/") {
alert('Changing');
// need to refresh component here?
}
history.push('/');
};
routing is:
<QueryClientProvider client={queryClient}>
<BrowserRouter>
<Header links={items} locations={Locations} />
<div className="App">
<Switch>
<Route exact path="/" component={home} />
<Route path="/details/:id" render={(props: RouteComponentProps<any>) => <Detail {...props} />} />
<Route component={NotFound} />
</Switch>
</div>
</BrowserRouter>
<ReactQueryDevtools />
</QueryClientProvider>

Invalid Hook Call useSelector() with react-router

I have my state in Redux working for a shopping cart. Let's say I have two pages and want to pass the state from the shopping page to the cart/checkout page.
I am calling useSelector from this page but I get an error about an invalid Hook Call.
const CartPage = () => {
const selectedProducts = useSelector(
(state) => (state && state.products) || []
);
return (
<PageContainer>
This is the Cart Page
</PageContainer>);
};
export default CartPage;
And this component is rendered by react-dom-router like this
<AppContainer>
<Navigation />
<Switch>
<Route exact path="/" render={WelcomePage} />
<Route path="/cart" render={CartPage} />
<Route path="/shopping" render={ShoppingPage} />
</Switch>
</AppContainer>
I am storing the state from a component inside the page component ShoppingPage.
The useSelector hook is working only inside components outside the react-router-dom. Is there a way to get the state in one of these components?
EDIT: Somehow it worked using a callback inside the render attribute of Route
Here the fixed code
<AppContainer>
<Navigation />
<Switch>
<Route exact path="/" render={() => <WelcomePage /> } />
<Route path="/cart" render={() => <CartPage /> } />
<Route path="/shopping" render={() => <ShoppingPage /> } />
</Switch>
</AppContainer>

react router use params returns empty object

I have a web app which is under development which is just like google drive using firebase. I have this useParams() in Dashboard Screen which is the main page of the App with All the different Folder Routes. So for this screen i have used useParams and now when i console.log(params) it shows an empty object {} and also when i click the button it does not navigate only the URL changes
Github Code :- https://github.com/KUSHAD/RDX-Drive/
In App.js
import { BrowserRouter, Switch, Route } from 'react-router-dom';
import PrivateRoute from './Components/Route/PrivateRoute';
import Dashboard from './Screens/Main/Dashboard';
import ViewProfile from './Screens/Profile/ViewProfile';
import Signup from './Screens/Auth/Signup';
import Login from './Screens/Auth/Login';
import ForgotPassword from './Screens/Auth/ForgotPassword';
function App() {
return (
<>
<div className='App'>
<div className='main'>
<BrowserRouter>
<Switch>
{/* Drive */}
<PrivateRoute exact path='/' component={Dashboard} />
<PrivateRoute
exact
path='/folder/:folderId'
component={Dashboard}
/>
{/* Profile */}
<PrivateRoute path='/profile' component={ViewProfile} />
{/* Auth */}
<Route path='/signup' component={Signup} />
<Route path='/login' component={Login} />
<Route path='/forgot-password' component={ForgotPassword} />
</Switch>
</BrowserRouter>
</div>
</div>
</>
);
}
export default App;
In Dashboard.js
import NavBar from '../../Components/Shared/NavBar';
import Container from 'react-bootstrap/Container';
import AddFolderButton from '../../Components/Main/AddFolderButton';
import { useDrive } from '../../services/hooks/useDrive';
import Folder from '../../Components/Main/Folder';
import { useParams } from 'react-router-dom';
export default function Dashboard() {
const params = useParams();
console.log(params);
const { folder, childFolders } = useDrive();
return (
<div>
<NavBar />
<Container fluid>
<AddFolderButton currentFolder={folder} />
{childFolders.length > 0 && (
<div className='d-flex flex-wrap'>
{childFolders.map(childFolder => (
<div
key={childFolder.id}
className='p-2'
style={{ maxWidth: '250px' }}>
<Folder folder={childFolder} />
</div>
))}
</div>
)}
</Container>
</div>
);
}
Issue
After scouring your repo looking for the usual suspect causes for "it does not navigate only the URL changes" I didn't find anything odd like multiple Router components, etc. I think the issue is your PrivateRoute component isn't passing the props to the Route correctly. You're destructuring a prop called rest and then spread that into the Route, but you don't pass a rest prop to the PrivateRoute
export default function PrivateRoute({ component: Component, rest }) { // <-- rest prop
const { currentUser } = useAuth();
return (
<Route
{...rest} // <-- nothing is spread/passed here
render={props => {
return currentUser ? (
<Component {...props} />
) : (
<Redirect to='/login' />
);
}}
/>
);
}
The routes, these are not passed any prop named rest:
<PrivateRoute exact path='/' component={Dashboard} />
<PrivateRoute
exact
path='/folder/:folderId'
component={Dashboard}
/>
What I believe to be occurring here is the exact and path props aren't passed to the underlying Route component and so the first nested component of the Switch is matched and rendered, the "/" one that doesn't have any route params.
Solution
The fix is to spread the rest of the passed props into rest instead of destructuring a named rest prop.
export default function PrivateRoute({ component: Component, ...rest }) {
const { currentUser } = useAuth();
return (
<Route
{...rest}
render={props => {
return currentUser ? (
<Component {...props} />
) : (
<Redirect to='/login' />
);
}}
/>
);
}
An improvement of your private route may be as follows:
export default function PrivateRoute(props) {
const { currentUser } = useAuth();
return currentUser ? (
<Route {...props} />
) : (
<Redirect to='/login' />
);
}
This checks your user authentication and renders either a Route or Redirect. This pattern allows you to use all the regular Route props so you aren't locked into using the render prop to render the component.

React - can a component be styled depending on what other component is rendered?

I have a Search component, when the homepage component is rendered I'd like the Search component to be rendered at the bottom of the page. When any other page component is rendered I'd like the Search component to be at the top of the page.
Currently what I have my app.js as:
const App = () => {
return (
<BrowserRouter>
<Switch>
<Route path='/' component={Home} />
<Route path='/about' component={About} />
<Route path='/work' component={Work} />
<Route path='/contact' component={Contact} />
</Switch>
</BrowserRouter>
)
}
and inside a page component:
const Contact = () => {
return (
<div>
<Search />
Contact
</div>
)
}
Obviously this way means I have to add the Search component to every component and choose whether I place it at the top or bottom.
My question is this, can I place it on the app.js like so:
const App = () => {
return (
<BrowserRouter>
<Search />
<Switch>
<Route path='/' component={Home} />
<Route path='/about' component={About} />
<Route path='/work' component={Work} />
<Route path='/contact' component={Contact} />
</Switch>
</BrowserRouter>
)
}
And then depending on which page component is being rendered, style the Search component so it either appears at the top or bottom of the page.
Thanks
I would add a className prop to the Search component and add some if statement.
For example:
<Search className={location === '/' ? 'top' : 'bottom'} />
With the useLocation() hook provided by React Router, you can determine what page you're on.
const Contact () => {
const location = useLocation();
const styles = location === "something" ? {...topStyles} : {...downStyles};
return (
<div>
<Search style={styles} />
Contact
</div>
)
}

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>
</>
)
}

Categories

Resources