React to url change within component with React Router 4? - react-router-v4

I have a component available at different urls. This is a simplified version of my app.js
<Router>
<Switch>
<Route path="/login"
render={() => {
return <SignUpAndRegister status="login" />;
}}
</Route>
<Route path="/register"
render={() => {
return <SignUpAndRegister status="register" />;
}}
</Route>
</Switch>
<Router>
I pass the status prop and then in the component setState based on the prop value. This works so far but I also need Links within the component that link to other other state. When your on the register page/state I have a link to login. When you're on the login page/state I have a link to register.
This means I have to have a link to change the url and also call a function to setState:
<Link
to="/login"
onClick={() => this.registerOrLogin('login')}
>
Log in
</Link>
How can I have the components state change when a url change is detected? It seems like browserHistory did this prior to v4 but I cant find an up up date solution.

I can set the state within the SignUpAndRegister component when it first mounts and then every time new props are received eg when the url changes.
componentDidMount() {
this.setState({ status: this.props.status });
}
componentWillReceiveProps(props) {
this.setState({ status: props.status });
}

Related

React-Admin: How to redirect to <Dashboard> when authenticated through custom <Login> page

react-admin loads the <Dashboard> at the base "/". And for that reason, the custom page never opens by default because the <Admin> always routes to the dashboard.
I've implemented <PrivateRoutes> to handle authentication and it's a success.The <Login> page is loaded by default, and upon authentication, it routes to dashboard.
Challenge: This process breaks the functionality of Sidebar <links>, like "Users".
// CustomRoutes.js
export default [
<Route exact path="/login" component={LoginPage} noLayout />,
<Route exact path="/forgot-password" component={ForgotPassword} noLayout />,
<Route exact path="/confirm-password" component={ConfirmForgotPassword} noLayout />,
<PrivateRoute path="/" loggedIn={localStorage.getItem("access_token")} component={dashboard} />
];
And then...
// PrivateRoute.js
import React from "react";
import { Redirect, Route } from "react-router-dom";
const PrivateRoute = ({ path, component: Component, ...rest }) => {
const isLoggedIn = localStorage.getItem("access_token");
return (
<Route
path={path}
{...rest}
render={props => isLoggedIn
? <Component {...props} />
: <Redirect to={{ pathname: "/login", state: { from: props.location } }} />
}
/>
);
};
export default PrivateRoute;
Here's how it looks within the main <App>
// App.js
const App = () => (
<Admin
theme={myTheme}
dashboard={dashboard}
authProvider={authProvider}
dataProvider={dataProvider}
customRoutes={customRoutes}
loginPage={LoginPage}
logoutButton={LogoutButton}
forgotPassword={ForgotPassword}
>
<Resource name="users" list={UserList} create={UserCreate} show={UserShow} icon={UserIcon} />
</Admin>
);
export default App;
Please note that the <resource> "users" is not working.
Do I need to add custom route for that too?
Alright, let's debug this step-by-step:
We can take advantage of react-admin directly to handle the authentication via the authProvider.
Once you include the authProvider, react-adminenables a new page on the /login
route, which displays a <Login> form asking for username and password.
You can definitely customize that login page (as I assume you did),
and I advise that you avoid using a <PrivateRoute> since the authProvider redirects all users who aren't yet authenticated to /login.
Kindly note that <Admin> creates an application with its own state, routing, and controller logic.In your code, you added forgotPassword which is not a prop accepted by <Admin.
<admin
// remove this...
- forgotPassword={ForgotPassword}
>
I wonder exactly what error you get in this case, but this is the main bug.

How to change react-router-native location on native-module callback

I'm building a react-native application that displays Service overlay (like those facebook messenger bubble heads), implement in Android only, and on the overlay click it should go back to the app in a specific screen.
I'm using react-router-native and I have my routes structured like this:
App.js
<NativeRouter>
<ApolloProvider client={client}>
<Main>
<Switch>
<Route exact path="/home" component={Home} />
<Route exact path="/progress" component={RouteProgress} />
<Route exact path="/search" component={Search} />
</Switch>
</Main>
</ApolloProvider>
</NativeRouter>
The Main component has these:
Main.js
componentDidMount() {
console.log(this.props.location.pathname);
if(this.props.location.pathname === '/') {
this.props.history.push("/home");
}
}
The callback from my Native module is being called like this:
FloatingView.java
case MotionEvent.ACTION_UP:
if (lastAction == MotionEvent.ACTION_DOWN || delta < 3) {
Intent intent = new Intent(FloatingWindow.this, MainActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(intent);
FloatingViewPackage.callback.invoke();
stopSelf();
}
The callback is defined in the component Search, which also executes the native module:
Search.js
<Button onPress={() => FloatingView.init(() => {
console.log('go to progress 1');
this.props.history.push("/progress");
setTimeout(() => {
console.log('go to progress 2');
this.props.history.push("/progress");
}, 1000);
})}
The problem is that this.props.history.push("/progress"); doesn't work neither outside the timeout nor inside.
Outside the timeout, the function is called before Main componentDidMount but location.pathname is not updated. Inside it the function is called after, but it doesn't navigate to the right screen. It always fall into /home.
I thought this my be a life cycle issue, since the Search component is not mounted. I've been trying to figure a way out to make this work. I tried using the Redirect component:
<Button onPress={() => FloatingView.init(() => <Redirect to="/progress" />)}
Does anyway can think of a way around this? Thanks
Found a solution, don't know if the best one, but it works.
Created a singleton navigation.js
navigation.js
export default {
entryPoint: '/home'
};
And changed the following files:
Search.js
<Button onPress={() => FloatingView.init(() => {
navigation.entryPoint = "/progress";
})}
Main.js
componentDidMount() {
if(this.props.location.pathname === '/') {
this.props.history.push(navigation.entryPoint);
}
}

Using React BrowserRouter (v4) how do I recognize when the location has changed?

Have the following JSX
// Root.jsx
<BrowserRouter getUserConfirmation={this.handleLocationChange}>
<Switch>
<Route path='/' exact component={Search} />
<Route path='/queue/' component={Queue} />
<Route path='/healthCheck/' component={HealthCheck} />
<Route path='/transcript' component={Transcript} />
</Switch>
</BrowserRouter>
// Root.js
export class Root extends Component{
constructor(props){
super(props);
}
handleLocationChange(message, callback) {
// your staff here
console.log(`- - - location:`);
callback(true);
}
render(){
return RootTemplate.call(this);
}
}
But when I run this I get...
Root.jsx:25 Uncaught TypeError: Cannot read property 'handleLocationChange' of undefined
at RootTemplate (Root.jsx:25)
at Root.render (Root.js:13)
If I try this...
getUserConfirmation={()=>this.handleLocationChange()}
I don't get an error but I also don't get my console like I would expect.
How do I tell when the location has changed?
Update
I also tried this just for testing...
const getConfirmation = (message, callback) => {
console.log("Welp this is working");
callback(true)
};
...
<BrowserRouter getUserConfirmation={getConfirmation}>
But still not seeing anything in the log.
If your trying to check when a user has navigated from one location in your app to another you should use the history library. You can also use it for getUserConfirmation, which is when a user navigates away from your application.
import createHistory from "history/createBrowserHistory";
import { Router } from "react-router";
// Fires when user navigates away
const getUserConfirmation = () => console.log("User navigated away");
// Create your browser history
const history = createHistory({getUserConfirmation});
// Fires every time a location is updated
history.listen((location) => console.log("Navigated to page", location));
// Attach your browser history to your router
const MyRouter = () => <Router history={history> {your children} </Router>
Use Prompt component from react-router. Include in one of you components.
Try doing this without using getUserConfirmation.
Prompt- https://reacttraining.com/react-router/core/api/Prompt

I am not able to set a specefic path in test

Problem: My App component is responsible to render the other components based on the path. So inorder to test it, I need to be able to "set" the path. e.g I'd like to see the SignUp component is rendered when the path is /sign-up.
What I've done: I thought I could use initialEntries to give the MemoryRouter an initial path (unsuccessful) then I thought I might be able to use Route/Redirect directly in my test inside MemoryRouter to set the path, but that did not work either.
Specification:
React 15
React Router 4 (react-router-dom)
Redux 3.6
Jest (the one comes with create-react-app)
Yo can find the code on the branch with the issue
I spent a while trying to figure this out myself.
Simply put you cannot nest a <Router> within the <MemoryRouter> as they are both Routers.
Solution Example:
Routes.js - Remove <BrowserRouter> from this so you can test your code.
import React from 'react';
import { Route, Switch } from 'react-router-dom';
import SignUp from '../SignUp/SignUp'; //your signup component
import Home from '../Home/Home'; //some other component for an example
const MyRoutes = () => (
<Switch>
<Route path="/" component={Home} />
<Route path="/signup" component={SignUp} />
</Switch>
);
export default MyRoutes;
index.js - Add <BrowserRouter> here as you will need to wrap a <Switch> in a <Router>.
import React from 'react';
import ReactDOM from 'react-dom';
import { BrowserRouter } from 'react-router-dom';
import Routes from './Components/Routes/Routes';
ReactDOM.render(
(
<BrowserRouter>
<Routes />
</BrowserRouter>
), document.getElementById('root')
);
tests/Routes.test.js - Now add the <MemoryRouter> in here for testing.
import React from 'react';
import ReactDOM from 'react-dom';
import { MemoryRouter } from 'react-router-dom';
import Routes from '../Components/Routes/Routes';
const TestRoute = props => (
<MemoryRouter initialEntries={props.initialEntries}>
<Routes {...props} />
</MemoryRouter>
);
it('at /signup it displays the signup page', () => {
const div = document.createElement('div');
ReactDOM.render(<TestRoute initialEntries={['/signup']} />, div); // render on the route "/signup"
const elem = div.getElementsByClassName('signup')[0];
expect(elem).not.toBe(undefined); // check an element exists with that class
if(elem !== undefined) {
expect(elem.innerHTML).toEqual('signup'); // check it has worked
}
});
In other words you are separating the Routes/Switch from the Router to enable you to be able to test it, as you cannot nest two Routers.
I'm having a similar issue, I have found that the following works for testing components whose behaviour changes based upon the route:
<MemoryRouter initialEntries={['/about']}>
<Navigation {...props} />
</MemoryRouter>
In this instance, the snapshot shows that an .active className is added to the 'about' link in Navigation.
What does not seem to work is adding memory router around the component/container holding the initial routes. To keep things simple I have reduced my basic routing page to this:
<Router>
<div className="App">
<Navigation app={this.props.app} />
<Switch>
<Route exact path="/" component={Home} />
<Route exact path="/about" component={About} />
<Route exact path="/login" render={() => <Login app={this.props.app} />} />
<Route exact path="/signup" render={() => <Signup app={this.props.app} />} />
<Route exact path="/account" render={() => <Account app={this.props.app} />} />
<Route component={ErrorPage} />
</Switch>
</div>
</Router>
The react-router team states in current testing docs:
We have a lot of tests that the routes work when the location changes,
so you probably don’t need to test this stuff.

Detect what specific page is open?

Pretty simple question.. does anyone have a solution for detecting what component is open in React Native? Treating a page like a component?
My solution right now is to use a global state manager like Redux or Mobx and just constantly update it with whatever component you have open.
In you're renderScene property of your Navigator, you have access to the route object. So you can pass that down into whatever component you need.
<Navigator
configureScene={() => Navigator.SceneConfigs.FadeAndroid}
style={styles.navigator}
initialRoute={{ title: 'Welcome' }}
renderScene={(route, navigator) => {
if (route.title === Welcome) {
return <Welcome navigator={navigator} route={route} />
// Now inside Welcome, if you do this.props.route.title
// you can access the current route name 'Welcome'
}
}}
/>