I am trying to create a Navbar (component), which changes slightly when a user logs in in a SignIn component. So, my app looks like this:
I have a state in App, with authenticated set to false as default. I then have a function, updatedAuthenticationEvent, which sets the value of the app state.
Ihave a NavBar component, which I pass in the value of Authenticated. The idea is that I change something on the navbar, if authenticated. That would be, dont show "Signin" and rather show "Sign Out". So when I sign in on my sign in component, the navbar changes.
I then have a few routes in my app, one of which is my Signin, which I attempt to pass my function to, as a callback.
export default class App extends React.Component {
constructor(props)
{
super(props);
this.state = {
authenticated: false
}
}
updatedAuthenticationEvent(isAthenticated)
{
console.log('Called!');
this.setState({authenticated: isAthenticated});
}
render() {
return (
<div>
<Router>
<div>
<Navbar authenticated={this.state.authenticated} />
<Route exact path='/' component={Home} />
<Route path="/login" component={Login} changeState={this.updatedAuthenticationEvent} />
</div>
</Router>
<Footer />
</div>
)
}
}
My SignIn component, actually wraps my sign in content.
But for now, I'm just trying to make something work.
So for now, all I am doing is that when the sign in component loads, set the Authenticated to true.
My goal though is to pass that prop to my LoginBox, which has all the logic, and my actual sign in method. But for now, I want this to work.
export default class SignIn extends Component {
constructor(props)
{
super(props);
}
componentWillMount()
{
this.props.changeState(true);
}
render() {
return (
<div className="row justify-content-md-center">
<div className="col-lg-4">
<LoginBox changeState={this.props.changeState} />
</div>
</div>
)
}
}
I get the error:
TypeError: this.props.changeState is not a function
What do I need to change to achieve this? Signin box event, should effect the Navbar.
well, I will not recommend this, but here it is,
use the render prop from Router, so you can pass the callback for changeState directly
<Route path="/login" render={(props) => <Login {...props} changeState={this.updatedAuthenticationEvent} />
then I would recommend change componentWillMount for componentDidMount in your Login Component
and finally
Use an arrow function for your updatedAuthenticationEvent callback like
updatedAuthenticationEvent = (isAthenticated) => {
....
}
So you do not have context problems when executing changeState
working demo
Related
I have used react-router to switch between 2 pages from the navbar. One of the pages is Home component. Now I have 3 columns as components in the homepage. Left column, middle column, and right column. By a button in the left column, I want to switch between two components in the mid column of the homepage.
function App() {
return (
<div className="App">
<Router>
<Navbar/>
<Switch>
<Route exact path="/">
<Home/>
</Route>
<Route path="/FAQ">
<FAQ/>
</Route>
</Switch>
</Router>
</div>
);
}
At home components ->
class Home extends Component {
render() {
return (
<div className={style.homeFlex}>
<LeftContainer/>
**<MidContainer/>**
**<AnotherMid/>**
<RightContainer/>
</div>
</div>
)
}
}
export default Home;
From a button inside LeftContainer, I want to switch between MidContainer and AnotherMid.
How can I use the router inside of an already routed component Linked from a sibling component? If there is a better way other than using the router to achieve the same, please share that also.
You don't need a route in this situation. You can use a state to render different components in your Home component.
Suppose your LeftContainer component looks like this:
class LeftContainer extends React.Component {
render() {
return (
<div>
<button onclick={() => this.props.handleSwitchComponent()}>
click here to change component
</button>
</div>
);
}
}
You'll need to pass a function through props, then you can use this component in Home component like this:
class Home extends Component {
state = { IsMidContainerVisible: true }
handleSwitchComponent = () => {
this.setState(({ IsMidContainerVisible }) => ({
IsMidContainerVisible: !IsMidContainerVisible;
}));
};
render() {
return (
<div className={style.homeFlex}>
<LeftContainer handleSwitchComponent={this.handleSwitchComponent} />
{/* Now here we need to specify when we want to show components */}
{this.state.IsMidContainerVisible ? <MidContainer /> : <AnotherMid />}
<RightContainer />
</div>
);
}
}
export default Home;
In my main class I have a nav bar with the options below:
<NavDropdown title="Search" id="collasible-nav-dropdown">
<NavDropdown.Item href="#/searchpage/p" onClick={this.dontEdit}>Find People</NavDropdown.Item>
<NavDropdown.Item href="#/searchpage/s" onClick={this.searchSchool}>Find Schools</NavDropdown.Item>
<NavDropdown.Item href="#/searchpage/w" onClick={this.dontEdit}>Find Work Places</NavDropdown.Item>
</NavDropdown>
These have a route which routes to the same component which then reads the parameter at the end of the URL and runs a different search depending on the value. For example 's' is search schools and 'p' is search people. If I navigate between the different search functions from the nav bar then it doesn't refresh to the new search. For example if I go from 'Find Schools' to 'Find Work' it stays on schools, but if I were to go direct to 'Find Work Places' then it goes there direct. Also if I navigate to the home page and back to another search then it works.
The route looks like:
<Route path="/searchpage/:type" render={props => (<SearchPage {...props} findPerson={this.findPerson} routeReset={this.routeReset} getPersonsByName={this.getPersonsByName} />)}/>
Is anyone able to advise how to get this to route as I want it to? The search component is like:
import Row from 'react-bootstrap/Row';
import Col from 'react-bootstrap/Col';
import Container from 'react-bootstrap/Container';
import Button from 'react-bootstrap/Button';
import Flash from './components/flash';
import Search from "./components/search";
const searchtypes = {"p":"People","w":"Work Places","s":"Schools"};
class SearchPage extends Component {
constructor(props) {
super(props);
this.state = {
type:this.props.match.params.type
}
}
componentDidMount(){
}
render() {
return (
<Container>
<Row>
<Col>
<h4>Search {searchtypes[this.state.type]}</h4>
<br/>
</Col>
</Row>
<Row><Col><Search {...this.props} type={this.state.type}/></Col></Row>
</Container>
);
}
}
export default SearchPage;
The Route's render prop doesn't remount when the matched route doesn't change, i.e. even when the route matches but the route param is different it won't re-render. Instead use the component prop.
react-router-dom component
When you use component (instead of render or children, below) the
router uses React.createElement to create a new React element from the
given component. That means if you provide an inline function to the
component prop, you would create a new component every render. This
results in the existing component unmounting and the new component
mounting instead of just updating the existing component. When using
an inline function for inline rendering, use the render or the
children prop (below).
<Route
path="/searchpage/:type"
component={props => (
<SearchPage
{...props}
findPerson={this.findPerson}
routeReset={this.routeReset}
getPersonsByName={this.getPersonsByName}
/>
)}
/>
An alternative to this is to implement the componentDidUpdate lifecycle function in SearchPage to detect when the route param prop updates and update the type stored in state. This way the component won't continually unmount/mount each time.
componentDidUpdate(prevProps) {
if (prevProps.match.params.type !== this.props.match.params.type) {
setState({
type: this.props.match.params.type,
});
}
}
Try this:
class SearchPage extends Component {
render() {
return (
<Container>
<Row>
<Col>
<h4>Search {searchtypes[this.props.match.params.type]}</h4>
<br/>
</Col>
</Row>
<Row><Col><Search {...this.props} type={this.props.match.params.type}/></Col></Row>
</Container>
);
}
}
The type data comes from props and therefore you should not persist it on the component state.
Note: Make sure you use react-router Link component it seems you use native a tags
I'm coding an authentication with react-router v4 and I'm using the PrivateRoute with render props, like the documentation: Redirects (Auth)
What I'm trying to do is: Whenever the user navigates to a route, I want to dispatch an action to make a request to the backend to verify if he's logged in.
Like this:
// App.js
class App extends Component {
checkAuth = () => {
const { dispatch, } = this.props;
// callback to dispatch
}
render() {
const props = this.props;
return (
<Router>
<div className="App">
<Switch>
<Route exact path="/" component={Login} />
<PrivateRoute
exact
path="/dashboard"
component={Dashboard}
checkIsLoggedIn={this.checkAuth}
/>
{/* ... other private routes here */}
</Switch>
</div>
</Router>
);
}
In PrivateRoute.js I'm listening the route to check if it changes, but when a route changes, this function is called too many times, and that's a problem to dispatch an action to make a request.
// PrivateRoute.js
const PrivateRoute = ({ component: Component, auth, checkIsLoggedIn, ...rest }) => (
<Route
{...rest}
render={props => {
props.history.listen((location, action) => {
if (checkIsLoggedIn) {
// Here I check if the route changed, but it render too many times to make a request
checkIsLoggedIn(); // here is the callback props
}
});
if (auth.login.isLoggedIn) {
return <Component {...props} />;
} else {
return <Redirect to={{ pathname: "/login", state: { from: props.location } }} />
}
}
}
/>
);
I need a help to figure it out a good way to call the backend whenever the route changes.
Creating a Higher Order Component (HOC) is a very clean way to do this. This way, you won't need to create a separate PrivateRoute component, and it would take only one line of change to convert any Component from public to protected, or vice versa.
Something like this should work:
import React from 'react';
import { Redirect } from "react-router-dom";
export function withAuth(WrappedComponent) {
return class extends React.Component {
constructor(props) {
super(props);
this.state = {
isUserLoggedIn: false,
isLoading: true
};
}
componentDidMount() {
// Check for authentication when the component is mounted
this.checkAuthentication();
}
checkAuthentication() {
// Put some logic here to check authentication
// You can make a server call if you wish,
// but it will be faster if you read the logged-in state
// from cookies or something.
// Making a server call before every protected component,
// will be very expensive, and will be a poor user experience.
this.setState({
isUserLoggedIn: true, // Set to true or false depending upon the result of your auth check logic
isLoading: false
});
}
render() {
// Optionally, you can add logic here to show a common loading animation,
// or anything really, while the component checks for auth status.
// You can also return null, if you don't want any special handling here.
if (this.state.isLoading) return (<LoadingAnimation />);
// This part will load your component if user is logged in,
// else it will redirect to the login route
if (this.state.isUserLoggedIn) {
return <WrappedComponent authData={this.state} {...this.props} />;
} else {
return <Redirect to={{ pathname: "/login", state: { from: props.location } }} />;
}
}
}
}
Once you have that component in place, all you need to do is use the HOC in any component that you wish to have protected. For example, in your case, the export line in your Dashboard file would be something like this:
/* Dashboard.js */
class Dashboard extends React.Component { ... }
export default withAuth(Dashboard);
and in your App, you can use a simple Route component:
<Route exact path='/dashboard' component={Dashboard} />
Your App does not need to care about which routes are protected, and which ones aren't. In fact, only the actual components need to know that they are protected.
Hope this helps. Cheers! :)
So I have a global nav bar component that sits at the home screen and app screen and a music playing component. On click of one of the items in the nav bar I want to mute something on the music component.
Currently, to mute the music etc I'm using state.
So the way I've got this setup is to pass through an object as props and set that as state like so:
const obj = {
playing: false,
toggleButtonText: 'Play',
muteActive: false,
};
And I pass this as props into my components:
<Router>
<div>
<Nav stateVal={obj} />
<Route exact path="/" render={() => <Start />} />
<Route path="/app" render={() => <App stateVal={obj} />} />
<Modal />
</div>
</Router>
Then in each of my components, I do:
constructor(props) {
super(props);
this.state = this.props.stateVal;
}
So the props are set as the state of the component.
My problem is that I want one component to update the props and the update the state of the other component but I have no idea how I'm going to do that?
Could anyone give me a bit of help or pointers?
Assigning props to state in constructor is an anti-pattern because if the props change later on then the state isn't going to change.
Have the component update the props of the parent and then pass the props down the other child.
If you can't do this for some reason then you should look into Redux, Flux or MobX to handle the state.
Example
class Parent extends React.Component {
setMusicActive = (muteActive) => {
this.setState({ muteActive });
}
<ChildOne muteActive={this.state.muteActive} setMusicActive={this.setMuteActive} />
<ChildTwo muteActive={this.state.muteActive} setMusicActive={this.setMuteActive} />
}
class ChildOne extends React.Component {
someOtherFunction = () => {
this.props.setMuteActive(!this.props.muteActive);
}
}
Updates the value in one place and you can use it in the children.
I'm using Redux with redux-simple-router.
Here's what I'm trying to do. A user hits a URL like so:
http://localhost:3000/#/profile/kSzHKGX
Where kSzHKGX is the ID of the profile. This should route to Profile container filled out with the details of the profile with id kSzHKGX.
My routes look like this:
export default (
<Route path="/" component={App}>
...
<Route path="profile" component={Profile} />
...
</Route>
)
So hitting the above link would give me Warning: [react-router] Location "undefined" did not match any routes
My container looks like this:
#connect(
state => state.profile,
dispatch => bindActionCreators(actionCreators, dispatch)
)
export class Profile extends Component {
constructor(props) {
super(props)
}
componentDidMount() {
const { getProfileIfNeeded, dispatch } = this.props
getProfileIfNeeded()
}
render() {
return (
<section>
...
</section>
)
}
}
So normally my container would just be populated from the state as usual in Redux.
Basically I need to have a way of doing some wildcard in the route. Than I need to pass the URL to the action that would pull up the right profile from an API. The question is, is it doable with react-simple-router? Can I do that somehow using UPDATE_PATH? Would it be the proper Redux way? Or should I use something else?
Following Josh David Miller's advice, I made my route look like so:
<Route path="admin/profile/:id" component={Profile} />
Than my container got this method to get the profile from API:
componentWillMount() {
const { getProfile, dispatch } = this.props
getProfile(this.props.params.id)
}
And this to cleanup (without it I would have the previous profile display for split second on component load - before I hit API in componentWillMount)
componentWillUnmount() {
this.props.unmountProfile()
}
Update:
As an alternative to the cleanup, I'm considering using the Container Component Pattern. Basically have the outer component do the data fetching and passing the data to the inner component as a prop.