I have the following problem: I have a general component that contains some data from the redux store and I want to clear this data once the user visits another route.
<Route path="/create/gallery" element={<CreatePage type={ContentType.gallery}/>} />
also I have some more code that saves my entered data to the store
saveGeneralInfo = (field: string, value: string) => {
const data = {};
data[field] = value;
this.props.dispatch(saveGeneralInfo(data));
}
How I can clear the state if the user leave the page or visit any other link? (For example from header)
if(this.state.keycloak) {
if(this.state.authenticated) return (
<div className="App">
<Header/>
<Routes>
<Route path="/" element={<Content />} />
<Route path="/sites"/>
<Route path="/users"/>
<Route path="/create/gallery" element={<CreatePage type={ContentType.gallery}/>}/>
<Route path="/create/article" element={<CreatePage type={ContentType.article} />} />
<Route path="/create/quiz" element={<CreatePage type={ContentType.quiz} />} />
</Routes>
</div>
);
else return (
<div>Can't authorize</div>
)
}
You will have to provide functionality for store clearing that fires on unMount lifecycle event in every route root component.
If you are using functional components:
export const Component = () => {
const dispatch = useDispatch();
useEffect(() => {
return () => {
dispatch(yourActionThatCleansReduxStore())
}
}, [])
//rest of your code
}
In my case I reset parts of my store for every page URL like /info or /user where store looks like
{
user: {
id: ...
},
info: ...
}
You can create a route controller using children component
import { useDispatch } from "react-redux";
import { useLocation } from "react-router-dom";
import { cleanState } from "Your-reducer.js";
function CleanState({ children }) {
const location = useLocation();
const dispatch = useDispatch();
useEffect(() => {
dispatch(cleanState()); // every time the route changes clean the state
// if you don't use redux-toolkit you can use action.payload etc....
},[location.pathname])
return <>{children}</>;
}
export default CleanState;
then you have to wrap the main component
import { BrowserRouter as Router, Routes, Route } from "react-router-dom";
import YourComponents from "./YourComponents"; // Your components
import CleanState from "./CleanState"; // where did you save it
function App() {
return (
<Router>
<CleanState> // This is the previous component, this will listen the movements of the routes
<Routes>
<Route path="/main" element={<YourComponents />} />
<Route path="*" element={<YourComponents />} />
</Routes>
</CleanState>
</Router>
);
}
export default App;
Related
I'm trying to use useParams() in my App component because I need it in two different child components.
But useParams() returns *: "tenants/rdyTupPulEab6mztoLvnQ/projects/0/workspace" not actually able to destructure the tenantId.
I assume this is because App isn't rerendering when the url changes, but I don't want to put two useParams() in both children and send the data back up to app. This is the best place for it to go, but not sure how to get useParams() to destructure the data correctly.
How can I do this, or what alternatives are there?
MRE:
function App() {
console.log(useParams())
useEffect(() => {
(api call that needs the tenantId from useParams())
})
return (
<Routes>
<Route path="/tenants/:tenantId/workspace"
element={<Workspace/>}/>
<Route path="/tenants/:tenantId/setup" element=.
{<Setup/>}/>
</Routes>
)
}
The console.log returns {*: 'tenants/rdyTupPulEab6mztoLvnQ/projects/0/workspace'}.
I need it to return {*: 'tenants/rdyTupPulEab6mztoLvnQ/projects/0/workspace', tenantId: 'rdyTupPulEab6mztoLvnQ'}
The App component can't access the route path params of any of the routes the Routes component is managing. The options you have are to use the matchPath utility to find a "match" to extract the tenentId parameter value.
Something like:
function App() {
const match = useMatch("/tenants/:tenantId/*");
useEffect(() => {
if (match) {
const { tenantId } = match.params;
// ... business logic using tenentId
}
}, [match]);
return (
<Routes>
<Route path="/tenants/:tenantId/workspace" element={<Workspace />} />
<Route path="/tenants/:tenantId/setup" element={<Setup />} />
</Routes>
);
}
An alternative is to create an intermediate layout component that can use the useParams hook.
Example:
import { Outlet, useParams } from 'react-router-dom';
export const TenantIdLayout = () => {
const { tenantId } = useParams();
useEffect(() => {
if (tenantId) {
// ... business logic using tenentId
}
}, [tenantId]);
return <Outlet />;
};
function App() {
return (
<Routes>
<Route element={<TenantIdLayout />}>
{/* "tenantId" routes */}
<Route path="/tenants/:tenantId/workspace" element={<Workspace />} />
<Route path="/tenants/:tenantId/setup" element={<Setup />} />
</Route>
{/* non-"tenantId" routes */}
</Routes>
);
}
I have a login form which I need to change my current router to the register form if the user does not have an account. I set up that functionality with useHistory and not useNavigate because Im required so by the project.
Here is my app.js with the routes :
const appHistory = createBrowserHistory();
function App() {
const [oldData, setOldData] = useState([]);
const datas = useMemo(() => ({ oldData, setOldData }), []);
return (
<div className="App">
<BrowserRouter basename={`sampleapp.html#`} history={appHistory}>
<OldUserCotnext.Provider value={datas}>
<Switch>
<Route path="/home" component={Home} />
<Route path="/sample1" component={Sample1} />
<Route path="/sample2" component={Sample2} />
<Route path="/sample3" component={Sample3} />
</Switch>
</OldUserCotnext.Provider>
</BrowserRouter>
</div>
);
}
Also here is the button func that takes me to the next route :
const updateFunc = () => {
if (user.length > 0 && urpassword.length > 0) {
updateUser(userId, password, username);
navigate.push('/sample3');
} else {
Swal.fire(`${t('errpop')}`, `${t('subwarning')}`, 'error').then((result) => {
if (result.isConfirmed) {
resetField();
}
});
}
};
The final output is that the register form component is rendered in the page, but the thing is that the url stays the same and the useEffect that is set up in the register form does not get triggered because an rerender is also not triggered.
Here you can see Im still on the login form url but with the register form component.
I am new to reactjs and have simple question. I want to pass auth props to Login component and want to update its value from within Login component. How can I pass auth to Login component and update its value from Login component.
Secondly, I have one protected route and only logged-in users can open that component. I added Private method to implement this. Is this right approach of making some components protected ?
import "./App.css";
import {
BrowserRouter,
Routes,
Route,
Switch,
Navigate,
NavLink,
} from "react-router-dom";
import Home from "./pages/Home";
import Login from "./pages/Login";
import RequestDemo from "./pages/RequestDemo";
import ProtectedRoute from "./Protected.Route";
const auth = true; //your logic
const Private = ({ Component }) => {
return auth ? <Component /> : <Navigate to="/login" />;
};
function App() {
return (
<BrowserRouter>
<Routes>
<Route exact path="/" element={<Home />} />
<Route exact path="/Login" element={<Login />} />
<Route
path="/RequestDemo"
element={<Private Component={RequestDemo} />}
/>
</Routes>
</BrowserRouter>
);
}
export default App;
For auth variable I would suggest using as a State or context, below I have provided an example using context, you can do it with State also
import './App.css';
import { BrowserRouter,Routes, Route, Switch, Navigate, NavLink } from 'react-router-dom';
import Home from './pages/Home';
import Login from './pages/Login';
import RequestDemo from './pages/RequestDemo';
import ProtectedRoute from './Protected.Route';
import { useContext } from 'react';
// In the login Component
const LoginComponent = () => {
const authContext = useContext(MyContext);
const handleLogin = () => {
authContext.onAuthChange(true); // this will make the user login that change the value of auth to true
}
return (
<div>Login JSX</div>
)
}
const MyContext = React.createContext(null);
const Private = ({Component}) => {
const authContext = useContext(MyContext);
return authContext.auth ? <Component /> : <Navigate to="/login" />
}
function App() {
const [auth, setAuth] = React.useState(true);
const handleAuthChange = (newAuthState) => {
setAuth(newAuthState);
}
return (
<MyContext.Provider value={{
auth,
onAuthChange: handleAuthChange
}}>
<BrowserRouter>
<Routes>
<Route exact path='/' element={<Home />} />
<Route exact path='/Login' element={<Login />} />
<Route path='/RequestDemo' element={<Private Component={RequestDemo} />} />
</Routes>
</BrowserRouter>
</MyContext.Provider>
);
}
export default App;
1. In your case first you have to change auth from const to let as you want to change auth. Then you have to pass auth and an function which can update auth in the same file(where auth is declared) hence you will pass 2 props auth and function which updates auth.
From your code syntax I'm assuming you are using v6.
import "./App.css";
import {
BrowserRouter,
Routes,
Route,
Switch,
Navigate,
NavLink,
} from "react-router-dom";
import Home from "./pages/Home";
import Login from "./pages/Login";
import RequestDemo from "./pages/RequestDemo";
import ProtectedRoute from "./Protected.Route";
let auth = true; // you can mutate let and not constant
const authUpdate = (argument) => {
// update auth in this function and pass this function to the component
console.log(auth + 1);
// if you want some argument or parameter you can do so by declaring argument
console.log(argument); // only if you want to send some argument or else no need
};
const Private = ({ Component }) => {
return auth ? <Component /> : <Navigate to="/login" />;
};
function App() {
return (
<BrowserRouter>
<Routes>
<Route exact path="/" element={<Home />} />
<Route
exact
path="/Login"
element={<Login auth={auth} authUpdate={authUpdate} />} // passed auth and authUpdate as props
/>
<Route
path="/RequestDemo"
element={<Private Component={RequestDemo} />}
/>
</Routes>
</BrowserRouter>
);
}
export default App;
2. There's more than one way for protected route. Yes, you can do it like that as mentioned in code snippet.
PS: I suggest you to use state for auth if you want to trigger re-rendering and want to update auth value at parent and child component level both. If component doesn't re-render's then prop/variable won't be updated
import { useState } from "react";
const [auth,setauth]=useState(true)
const authUpdate=()=>{
// pre-steps
setauth(auth=>!auth)
// you should avoid passing setauth directly as a prop
// down the line in child component you can forget what setauth does and you can set something different
}
If you want to use certain variable/state in all child component you can look into Store or Global State concept. You can use react-redux or useContext which does not need to pass props from parent component to child component (prop drilling)
First page:
const firstpage= () => {
//some code above here..
//then
history.push({
pathname: '/cars',
state: {
cars: [cars.data],
}
//Redirect to cars page with the state
})
}
Then I redirect it to /cars with the state.
Cars page:
import { Link , Redirect, useLocation} from "react-router-dom";
const Cars = () => {
const { state } = useLocation();
console.log(state.cars);
// Works fine and able to retrieve the data
}
However, I don't want the users to go to /cars without any state from being set in the first page.
How can I make sure so a user with empty state from cars, will not go to url/cars
Maybe some function similar to "if logged in"?
const App = () => {
// Can I retrieve something from useLocation like the cars page?
// Problem may be that the state has never been pushed
const CarsNotSet= () => {
return <Redirect to="/" />
};
return (
<Router>
<div>
<Switch>
<Route exact path="/">
<Firstpage/>
</Route>
<Route path="/cars">
<Cars/>
</Route>
<Route path="" component={CarsNotSet} />
</Switch>
</div>
</Router>
);
}
You already solved the issue. with useLocation
Just check the state.cars using useEffect
Cars.js:
const { state } = useLocation();
useEffect(() => {
if (!state.cars) {
return <Redirect to="/" />
}
}, [])
Dont't forget to import useEffect from react:
import React, { useEffect } from 'react'
My problem is when I change a state inside a redux store and based on this state I mount or unmount a component. The Code looks like this:
class Main extends Component {
render() {
const { dropdownState } = this.props;
return (
<div>
<SecondHeadBar />
<div className="main">
<Switch>
<Route exact path='/' component={withRouter(WebsiteIndex)}/>
<Route path='/track/:trackid' component={withRouter(MssTrack)}/>
<Route path='/album/:albumid' component={withRouter(Container.AlbumContainer)}/>
<Route path='/profile/:userName' component={withRouter(MssUser)}/>
<Route path='/upload/:albumid' component={withRouter(MssUploadTemplate)}/>
<Route path='/upload' component={withRouter(MssUploadTemplate)}/>
<Route path='/admin' component={withRouter(ControlCenter)}/>
<Route path='/kategorie' component={withRouter(Category)} exact/>
<Route path='/kategorie/:catName' component={withRouter(Folder)}/>
<Route path='/notFound' component={withRouter(NotFound)}/>
<Route path='/meine-eintraege' component={withRouter(Container.MyEntriesContainer)}/>
</Switch>
</div>
{dropdownState ? <DownloadDropdown /> : ''}
</div>
);
}
}
function mapStateToProps(state) {
return {
dropdownState: state.collection.dropdownState
};
}
function mapDispatchToProps(dispatch) {
return {
dispatch
};
}
export default connect(mapStateToProps, mapDispatchToProps)(Main);
Whenever the prop dropdownState changes. And the Component DownloadDropdown gets mounted then everything in the Main Component gets rerendered. So the content flashes.
Simplest solution would be to have <DownloadDropdown /> be a container component that is connected to Redux and will always stay mounted although not visible. Then you can utilize a HOC or something that's always mounted and visible (like <SecondHeadBar />) and have it connected to a Redux action creator that toggles DownloadDropdown's visiblity. In other words, isolate Redux to two components, instead of over your entire route tree.
Working example: https://codesandbox.io/s/yw4m7yz8r1 (navigate around the routes and click the 'Download Schedule' link at the top!)
I'm not sure how you are triggering the mount/unmount, but let's stay it's being toggled by a button:
SecondHeadBar.js
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { handleDropdown } from '../actions';
class SecondHeadBar extends Component {
state = {...}
componentDidMount = () => { ... }
render = () => (
<div>
...
<button onClick={this.props.handleDropdown}>Toggle Dropdown</button>
...
</div>
)
}
export default connect(null, { handleDropdown })(SecondHeadBar)
DownloadDropdown.js
import React, { Component } from 'react';
import { connect } from 'react-redux';
class DownloadDropdown extends Component {
state = { ... }
componentDidMount = () => { ... }
render = () => (
this.props.isVisible
? <div>I'm visible!</div>
: null
)
}
export default connect(state => ({ isVisible: state.dropdown }))(DownloadDropdown)
actions.js
import { TOGGLE_DROPDOWN } from '../types'
export const handleDropdown = () => ({
type: TOGGLE_DROPDOWN
})
reducers.js
import { TOGGLE_DOWN } from '../types';
...
const dropdownReducer = (state=false, { type, payload }) => {
switch(type) {
case TOGGLE_DROPDOWN: return !state
default: return state
}
}
export default = combineReducer({
...
dropdown: dropdownReducer
...
})
routes.js
const Main = () => (
<div>
<SecondHeadBar />
<div className="main">
<Switch>
<Route exact path='/' component={withRouter(WebsiteIndex)}/>
<Route path='/track/:trackid' component={withRouter(MssTrack)}/>
<Route path='/album/:albumid' component={withRouter(Container.AlbumContainer)}/>
<Route path='/profile/:userName' component={withRouter(MssUser)}/>
<Route path='/upload/:albumid' component={withRouter(MssUploadTemplate)}/>
<Route path='/upload' component={withRouter(MssUploadTemplate)}/>
<Route path='/admin' component={withRouter(ControlCenter)}/>
<Route path='/kategorie' component={withRouter(Category)} exact/>
<Route path='/kategorie/:catName' component={withRouter(Folder)}/>
<Route path='/notFound' component={withRouter(NotFound)}/>
<Route path='/meine-eintraege' component={withRouter(Container.MyEntriesContainer)}/>
</Switch>
</div>
<DownloadDropdown/>
</div>
);
export default Main;
Now, when the user clicks the "Toggle Dropdown" button in <SecondHeadBar/>, it'll update <DownloadDropdown/>'s visibility without affecting your route tree.
I think you can use this lifecycle methods to check.
static getDerivedStateFromProps(nextProps, prevState) {
if (nextProps.name !== prevState.name) {
return { name: nextProps.name};
}
}
or for older version check in componentwillreceiveProps and stops re render.