So I need to prevent the user from going back to the profile page (/profile) after he already selected a profile.
I'm storing the profile selected inside the application state.
Scenario wanted: User goes to /profile, select a profile, then goes to '/' (which is my home), and can navigate to /exams if he wants.
BUT, he can't go back to /profile, since he's already inside the application with a profile stored in the state. If he tries to go to /profile, through browser back-arrow or even typing /profile in the url, the current page simply reloads.
What's the best way to achieve this?
OBS: this const { id } = useSelector... is the const that retrieves the profile from the state, so I have to use this as condition, but I don't know how.
Therefore, if the user have an id that's not empty (which means he already selected a profile), he can't go back to profile. Other than that, he can visit /profile.
Below follows my route.tsx :
const Exams = lazy(() => import('../pages/private/Exams'));
const Home = lazy(() => import('../pages/private/Home'));
const ProfileSelector = lazy(() => import('../pages/private/ProfileSelector'));
const { id } = useSelector((state: RootState) => state.profile);
const AppRoutes = () => {
return (
<Router history={history}>
<Suspense fallback={<LoadingPage />}>
<Switch>
<Route exact path={'/'} component={Home} />
<Route exact path={'/exams'} component={Exams} />
<Route exact path={'/profile'} component={ProfileSelector} />
</Switch>
</Suspense>
</Router>
);
};
export default AppRoutes;
My profile store if there's any use:
interface UserProfileModel {
id: string;
council: string;
state: string;
number: string;
description: string;
}
const initialState: UserProfileModel = {
id: '',
council: '',
state: '',
number: '',
description: '',
};
export const userProfileSlice = createSlice({
name: 'profile',
initialState,
reducers: {
selectProfile: (state, action: PayloadAction<UserProfileModel>) => {
return {
...state,
...action.payload,
};
},
clearProfile: () => initialState,
},
});
export const { selectProfile, clearProfile } = userProfileSlice.actions;
export default userProfileSlice.reducer;
Set a state for example profileSelected to true when the user selects a profile then:
put
{profileSelected ? null : <Route exact path={'/profile'} component={ProfileSelector} />}
instead of
<Route exact path={'/profile'} component={ProfileSelector} />
Related
I am trying to share my props (data, saveWorkButtonClicked, updateFBRDB) from <ProjectPage /> component route to <Indent /> component route.
But getting the following error:
Uncaught DOMException: Failed to execute 'pushState' on 'History': async (data, setSpinner, updateFBRDB) => {
setSpinner && setSpinner(true);
let rawRoomData = String.raw`${J...<omitted>...
} could not be cloned.
App.js
<Router>
<Switch>
<Route path="/ProjectPage/:projectId" exact component={ProjectPage} />
<Route path="/Indent/" render={(props) => <Indent {...props} />} />
</Switch>
</Router>
ProjectPage.js
history.push("/Indent/",
{
data: { ...project, rooms: project.rooms, ProjectId: project.ProjectId, ClientName: project.ClientName, Address: project.Address, AmountRecieved: project.AmountReceived, SiteEngineerId: project.SiteEngineersId },
saveWorkButtonClicked,
updateFBRDB,
}
)
// saveWorkButtonClicked & updateFBRDB are API calls which will be called in <Indent />
Indent.js
export default function Indent({ data, saveWorkButtonClicked, updateFBRDB }) {
console.log('data in indent', data)
}
NOTE: Please give solutions where this can be implemented without Context/ Redux/ Mobx. Also, I am using react-router-dom v5.2.0
I would suggest an workaround. Have a state which keeps track of when you want to move to next page, so that we can use Redirect component conditionally with your desired data as props.
App.js
<Router>
<Switch>
<Route path="/ProjectPage/:projectId" exact component={ProjectPage} />
</Switch>
</Router>
ProjectPage.js
const [isDone, setIsDone] = useState(false);
const handleClick = () => {
// Do all your works, when you want to `push` to next page, set the state.
setIsDone(true);
}
if(isDone) {
return (
<>
<Route path="/Indent"
render={ props =>
<Indent
{...props}
data={...}
saveWorkButtonClicked={saveWorkButtonClicked}
updateFBRDB={updateFBRDB}
/>
}
/>
<Redirect to="/Indent" />
</>
);
}
return (
<div>Your Normal Profile Page goes here</div>
)
If you want to "share" props, you need to do one of two things. Either have the receiving component be a child of the propsharing component - in which case you can pass them as props directly. Else, you would need to pass them as state via a common ancestor component, which you would need to update by sending a callback down to the component that will update the state.
You can pass state to location with this format
const location = {
pathname: '/Indent/',
state: {
data: { ...project, rooms: project.rooms, ProjectId: project.ProjectId, ClientName: project.ClientName, Address: project.Address, AmountRecieved: project.AmountReceived, SiteEngineerId: project.SiteEngineersId },
saveWorkButtonClicked,
updateFBRDB,
}
}
history.push(location)
And then using withRouter to receive location values
import { withRouter } from 'react-router'
function Indent({ location }) {
const { state } = location
const { data, saveWorkButtonClicked, updateFBRDB } = state || {}
return <></>
}
export default withRouter(Indent)
I am trying to setup a website with a login screen for unauthorized users and a dashboard for authorized users using react router dom.
Every time there is a route change (dashboard routes) when a user clicks a link in the sidebar, for example. The useEffect inside dashboard component is called which fetches data that I already have.
## ROUTES ##
export const appRoutes = auth => [
{
path: '/',
component: () => auth ? <Redirect to='/dashboard' /> :<Login/>,
exact: true
},
{
path: '/dashboard',
component: Guilds ## REDIRECTS TO THE NEXT ROUTE WITH ID ##,
exact: true,
private: true
},
{
path: '/dashboard/:id',
component: Dashboard,
private: true
},
{
path: '/dashboard/*',
component: Dashboard,
private: true
}
]
export const dashboardRoutes = [
{
path: '/dashboard/:id',
component: Home,
exact: true
}
]
## SIMPLIFIED APP COMPONENT ##
export default function App() {
return (
<ThemeProvider theme={theme}>
<BrowserRouter>
<Switch>
{appRoutes(auth).map(value => {
if(value.private) return <PrivateRoute path={value.path} component={value.component} exact={value.exact} key={value.path} auth={auth} />;
else return <Route path={value.path} component={value.component} exact={value.exact} key={value.path} />;
})}
</Switch>
</BrowserRouter>
</ThemeProvider>
)
}
## SIMPLIFIED DASHBOARD COMPONENT ##
export default function Dashboard({ match }) {
const [guild, setGuild] = useState(null);
const [user, setUser] = useState(null);
useEffect(() => {
getGuild(match.params.id)
.then(res => {
setGuild(res.data);
return getUser();
})
.then(res => {
setUser(res.data);
})
.catch(err => {
console.log(err);
})
}, [match.params.id]);
return (
<div className={classes.root}>
<Header onToggleDrawer={onToggleDrawer} guild={guild} auth />
<SideBar onToggleDrawer={onToggleDrawer} isOpen={drawerOpen} user={user} />
<div className={classes.content}>
<div className={classes.toolbar} />
<div className={classes.contentContainer}>
{dashboardRoutes.map(value => {
return <Route exact={value.exact} path={value.path} component={value.component} key={value.path}/>
})}
</div>
</div>
</div>
)
}
## PRIVATE ROUTE COMPONENT ##
export const PrivateRoute = ({ component: Component, auth, ...rest }) => {
return (
<Route {...rest} render={(props) => (
auth
? <Component {...props} />
: <Redirect to='/' />
)} />
)
}
I'm not sure if I am approaching the situation correctly but any help would be great. I take it the function is called in-case a user comes to the site from a bookmark for example but if someone can shed some light that would be cool.
Thank you.
The reason behind that why the fetch is happening several times is the dependency array what you have for useEffect. I assume the match.params.id is changing when the user clicks then it changes the route which will trigger the fetch again.
Possible solutions:
1. Empty dependency array:
One possible solution can be if you would like to fetch only once your data is set the dependency array empty for useEffect. From the documentation:
If you want to run an effect and clean it up only once (on mount and unmount), you can pass an empty array ([]) as a second argument. This tells React that your effect doesnβt depend on any values from props or state, so it never needs to re-run.
So if you have the following, it will run only once:
useEffect(() => {
// this part runs only once
}, []); // empty dependency array
2. Checking if the fetch happened already:
The other solution what I was thinking is to check if you have the value already in the guild variable just like below:
useEffect(() => {
// no value presented for guild
if (guild === null) {
// code which is running the fetch part
}
}, [match.params.id]);
I hope this gives you an idea and helps!
I am having an issue with my application. My user component only loads UserCard when I start the application from the homepage then click users link there... if I just refresh the users URL... UserCard doesn't get loaded which means something is wrong with my this.props.users. I do see that in chrome it says: Value below was evaluated just now when I refresh but when I go through the flow it doesn't say that. Any help will be appreciated.
App.js
class App extends Component {
constructor(props) {
super(props);
this.state = {
users: []
};
}
componentDidMount() {
users = []
axios.get('/getall').then((res) => {
for(var d in res.data) {
users.push(new User(res.data[d]));
}
});
this.setState({ users });
}
render() {
const { users } = this.state;
return (
<Router history={history}>
<Switch>
<PrivateRoute exact path="/" component={Home} />
<Route exact path='/users' render={(props) => <Users {...props} users={users} />}/>
</Switch>
</Router>
)
}
}
PrivateRoute:
export const PrivateRoute = ({ component: Component, ...rest }) => (
<Route {...rest} render={props => (
<Component {...props} /> )} />
)
User.js
export default class Users extends Component {
render() {
console.log(this.props.users);
return (
<Row>
{this.props.users.map(u =>
<UserCard key={u.name} user={u}/>
)}
</Row>
);
}
}
export class User {
constructor(obj) {
for (var prop in obj){
this[prop] = obj[prop];
}
}
getURLName() {
return this.name.replace(/\s+/g, '-').toLowerCase();
}
}
class UserCard extends Component {
render() {
return (
<Link to={'/users/' + this.props.user.getURLName()} >
<div>
// Stuff Here
</div>
</Link>
);
}
}
As per the comments:
The issue here is how you're setting state. You should never modify state directly since this will not cause the component to rerender See the react docs
Some additional thoughts unrelated to the question:
As per the comments - use function components whenever possible, especially with hooks on the way
There is probably no need to create a User class, only to new up little user objects. Simply use plain old JS objects and calculate the link url right in the place its used:
render() {
const { user } = this.props
return <Link to={`/users/${user.name.replace(/\s+/g, '-').toLowerCase()}`} />
}
It might be a good idea to start using a linter such as eslint. I see that you're declaring users = [] without using let or const (don't use var). This is bad practice since creating variables in this way pollutes the global name space. Linters like eslint will help you catch issues like this while you're coding.
Hello and thank you for your time.
I am following this course: https://app.pluralsight.com/library/courses/react-flux-building-applications/table-of-contents and I have discovered that the React Router API has changed.
I am facing difficulties when trying to pass form's data to view, through Route, and Link components.
I will write the important code:
Main.js holds the Route, the important Route is the one which has /author/:id
const Main = () => (
<Switch>
<Route exact path="/Home" component={Home}/>
<Route path="/authors" component={AuthorPage}/>
<Route path="/about" component={About}/>
<Route path="/author" component={ManageAuthorPage}/>
<Route path="/author/:id" component={ManageAuthorPage}/>
<Redirect from="*" to="/Home"/>
</Switch>
);
We put the author.id from the authorList.js, the important part is: <td><Link to="/author/"render={(props) => <ManageAuthorPage id={author.id}/>}>{author.id}</Link></td>
const AuthorList = (props) => {
const createAuthorRow = function (author) {
return (
<tr key={author.id}>
<td><Link to="/author/"
render={(props) => <ManageAuthorPage id={author.id}/>}>{author.id}</Link>
</td>
<td>{author.firstName} {author.lastName}</td>
</tr>
)
;
};
return (
<div>
<table className="table">
<thead>
<tr>
<th>ID</th>
<th>Name</th>
</tr>
</thead>
<tbody>
{props.authors.map(createAuthorRow, this)}
</tbody>
</table>
</div>
);
};
AuthorList.propTypes = {
authors: PropTypes.array.isRequired
};
And we use it, on the manageAuthorPage.js, the important code is in ComponentWillMount:
class ManageAuthorPage extends React.Component {
state = {
author: {
id: '',
firstName: '',
lastName: '',
},
};
setAuthorState = (event) => {
let field = event.target.name;
let value = event.target.value;
this.state.author[field] = value;
return this.setState({author: this.state.author});
};
saveAuthor = (event) => {
event.preventDefault();
AuthorApi.saveAuthor(this.state.author);
toastr.success('Author saved ;=)');
};
componentWillMount = () => {
const authorId = this.props.id;
console.log(authorId);
if (authorId) {
this.setState({author: AuthorApi.getAuthorById(authorId)});
}
};
render() {
return (
<AuthorForm author={this.state.author}
onChange={this.setAuthorState}
onSave={this.saveAuthor}/>
);
};
}
Also the console outputs:
Warning: Invalid value for prop render on tag. Either remove it from the element, or pass a string or number value to keep it in the DOM. For details.
Could you help me please πππ?
I have also read: React Router Pass Param to Component
https://medium.com/#pshrmn/a-simple-react-router-v4-tutorial-7f23ff27adf
EDIT:
I have also tried #Ramana Venkata suggestion, using:
<Route path="/author?id={id}" component={ManageAuthorPage}/>
And in manageAuthorPage I do:
componentDidMount = () => {
const authorId = this.props.id;
console.log(this.props);
if (authorId) {
this.setState({author: AuthorApi.getAuthorById(authorId)});
}
};
And I do see the author.id in the url, but it does not spawn when we write: console.log(this.props);
So I mean, if the URL is:
http://localhost:3001/author/cory-house
The console output for match.location.search is "". Why?
Thank you for your help.
EDIT2: I have also tried updated course's version:
https://github.com/coryhouse/react-flux-building-applications/pull/1/files
With:
The same Main.js route:
<Route path="/author/:id" component={ManageAuthorPage}/>
Passing the id into the link using it like a string:
<td><Link to={"author/" + author.id}>{author.id}</Link></td>
Also in manageAuthorPage we now Have:
componentDidMount = () => {
var authorId = this.props.match.params.id; //from the path '/author:id'
console.log(this.props);
};
And authorId is undefined:
Also in the console's output I only see the author.id in the: match.location.pathname, for URL: http://localhost:3001/author/cory-house we see:
"/author/cory-house"
Could you help me please?
Thank you.
I am using react-router, and I am trying to combine some routes/sub-routes.
I have a left menu bar with 2 options: main and secondary, if you click on main, then a menu with tabs should be open with 4 options, if you click on secondary, the same should happen but with 3 different options.
The left menu bar component
export default class LeftNavMenu extends React.Component {
static propTypes = {
getActivePage : React.PropTypes.func,
leftMenuItems : React.PropTypes.arrayOf(React.PropTypes.object),
}
static contextTypes = {
router : React.PropTypes.func,
}
render () {
return (
<LeftNav
ref="appNavbar"
docked={false}
menuItems={this.props.menuItems}
selectedIndex={this.props.getActivePage()}
onChange={this.props._onLeftNavChange} />
);
}
// Navigate to route when clicking on a Side Bar element.
_onLeftNavChange = (e, key, payload) => {
if (payload.route === 'main') {
console.log(payload.route);
}
this.context.router.transitionTo(payload.route);
}
}
here is the Tabs Component
export default class TabsMainMenu extends React.Component {
static propTypes = {
getActivePage : React.PropTypes.func,
menuItems : React.PropTypes.arrayOf(React.PropTypes.object),
}
static contextTypes = {
router : React.PropTypes.func,
}
render () {
const tabs = this.props.menuItems.map((item) => {
return (
<Tab
key={item.route}
label={item.text}
route={item.route}
onActive={this._onActive} />
);
});
return <Tabs initialSelectedIndex={this.props.getActivePage()}>{tabs}</Tabs>;
}
_onActive = tab => {
this.context.router.transitionTo(tab.props.route);
}
**and here the main component where I am calling those 2 components above
const menuItems = [
{ route : 'universal-search', text : 'Universal Search' },
{ route : 'game-info', text : 'Game Info' },
{ route : 'player-info', text : 'Players Info' },
{ route : 'money', text : 'Money' },
{ route : 'refunds', text : 'Refunds' },
{ route : 'videos', text : 'Videos' },
{ route : 'tips', text : 'Tips' },
], leftMenuItems = [
{ route : 'main', text : 'Main - Management' },
{ route : 'secondary', text : 'Secondary - Operations' },
];
export default class App extends React.Component {
static contextTypes = {
router : React.PropTypes.func,
}
render () {
return (
<LeftNavMenu ref="appNavbar" menuItems={leftMenuItems} getActivePage={this._getActivePage} />
<TabsMainMenu menuItems={menuItems} getActivePage={this._getActivePage} />
<RouteHandler />
);
}
// Toggle Side Bar.
_onLeftIconButtonTouchTap = () => {
this.refs.appNavbar.refs.appNavbar.toggle();
}
// Get the active page.
_getActivePage = () => {
for (const i in menuItems) {
if (this.context.router.isActive(menuItems[i].route)) return parseInt(i, 10);
}
}
_onChange = (event) => {
this.setState({value: event.target.value});
}
}
here are the routes
const Routes = (
<Route handler={Root}>
<Route name="app" path="/" handler={App}>
<Route name="main">
<Route name="game-info" path="game-info" handler={GameInfo} />
<Route name="player-info" path="player-info" handler={PlayerInfo} />
<Route name="money" path="money" handler={Money} />
<Route name="refunds" path="refunds" handler={Refunds} />
</Route>
<Route name="secondary">
<Route name="videos2" path="videos" handler={Videos} />
<Route name="tips2" path="tips" handler={Tips} />
<Route name="universal-search2" handler={UniversalSearch} />
</Route>
<DefaultRoute handler={UniversalSearch} />
</Route>
<Route name="login" handler={Login} />
</Route>
);
look at this in the first component I wrote above
// Navigate to route when clicking on a Side Bar element.
_onLeftNavChange = (e, key, payload) => {
if (payload.route === 'main') {
console.log(payload.route);
}
this.context.router.transitionTo(payload.route);
}
there is where I need to tell the app if payload.route === 'main' then display the tabs I need, but, in that component, what should I do to get the tabs from the tabs component?
so, what should I do in order to call the routes I need depending whether the user choose main or secondary on the left menu bar component?
It's hard for me to follow, so I can't write some sample code, but it sounds to me like you need a store to sit behind the tab components.
The store would definitely hold data useful to determine "current tab" (like tab index or route name).
It may also hold the full list of tabs. This is where I'm not sure, because the sample code seems complex, while obviously also just snippets of a bigger application...