I am using React (16.3.2) with TypeScript (2.8.3), Keycloak-js (3.4.3) and React Router 4 (4.2.2) together. Here is the Keycloak init:
const auth = Keycloak('./../keycloak.json');
const init = () => {
return auth.init({ onLoad: 'login-required', checkLoginIframe: false });
};
The keycloak.json file is stored in public folder
I do Keycloak initialization before ReactDOM.render method:
import { init } from './auth';
init()
.success((authenticated: boolean) => {
if (authenticated) {
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root') as HTMLElement
);
} else {
console.log('not authenticated');
}
})
.error(() => {
console.log('failed to initialize');
});
Then the App (ThemeProvider comes from styled-components):
const App: React.SFC<Props> = ({ theme }) => {
return (
<BrowserRouter>
<ThemeProvider theme={theme}>
<Switch>
<Redirect from="/" exact={true} to="/books" />
<Route path="/books" component={BooksList} />
<Route component={Error404} />
</Switch>
</ThemeProvider>
</BrowserRouter>
);
};
Then the BooksList:
const BooksList: React.SFC<RouteComponentProps<void>> = ({ match }) => {
return (
<ColumnView>
<Switch>
<Route path={match.url} component={List} />
</Switch>
<Switch>
<Route path={match.url} exact component={EmptyView} />
<Route path={match.url + '/details/:id'} component={BookDetails} />
<Route component={Error404} />
</Switch>
</ColumnView>
);
};
When I open my website on URL localhost:3000 everything works as it should. Keycloak renders a login page and I can navigate through the whole website. The problem appears when I want to enter a different URL by typing it to the browser, for example localhost:3000/books/details/11. Suddenly Keycloak starts to search for the keycloak.json file in a very different directory - not localhost:3000/keycloak.json but localhost:3000/books/details/keycloak.json.
The problem seems to be non existent when I write the localization of the configuration file as:
const auth = Keycloak('./../../../keycloak.json');
Where the number of '../' depends on how much nested my router is. This fixes everything.
So the solution is quite easy - I had to delete the single dot in front of the initialization URL to make the path direct not relative:
const auth = Keycloak('/../keycloak.json');
Related
I have a React project that has a HeaderComponent that exists for all routes in project like this:
function App() {
return (
<Fragment>
<Router>
<HeaderComponent />
<Routes>
<Route path="/login" element={<Login />}></Route>
<Route path="/register" element={<Register />}></Route>
<Route path="/" element={<LandingPage />}></Route>
</Routes>
<FooterComponent />
</Router>
</Fragment>
);
}
And my problem is that the <HeaderComponent> is rendered when the website first loads but when the user logs in, the <HeaderComponent> is not aware of the changes because the component has already mounted.
So in my <HeaderComponent>, the componentDidMount function looks like this:
componentDidMount() {
AuthService.authorizeUser()
.then((r) => {
this.setState({ loggedIn: true });
})
.catch((error) => {
this.setState({ loggedIn: false });
});
}
This only works if I refresh the page.
Basically, if a user successfully logs in (from the <Login> component), what is the proper way of making my HeaderComponent aware of this?
You can use Context API to make AuthContext to share global state within your app:
// AuthContext.js
export const AuthContext = React.createContext({});
export const AuthProvider = ({
children,
}) => {
// your context logic
return (
<AuthContext.Provider value={yourAuthValue}>
{children}
</AuthContext.Provider>
);
}
export const useAuth = () => React.useContext(AuthContext);
// Layout.js
import { Outlet } from 'react-router-dom'
// Using `Outlet` to render the view within layout
export const Layout = () => {
return (
<>
<HeaderComponent />
<Outlet />
<FooterComponent />
</>
)
}
// HeaderComponent.js
import { useAuth } from './AuthContext'
export const HeaderComponent = () => {
// get state from auth context
const { isLoggedIn } = useAuth()
return // rest of your code
}
// App.js
function App() {
return (
<Fragment>
<-- Wrap your app with AuthContext let other components within your app can access auth state !-->
<AuthProvider>
<BrowserRouter>
<Routes>
<Route path="/" element={<Layout />}>
<Route index element={<LandingPage />} />
<Route path="/login" element={<Login />} />
<Route path="/register" element={<Register />} />
</Route>
</Routes>
</BrowserRouter>
</AuthProvider>
</Fragment>
);
}
There are a couple of ways to do so.
When you're facing a situation where you need to share the same state between multiple components, lifting the state up should be the first thing to try Check this codesandbox.
And some great blogposts to read, KCD - Prop Drilling, KCD - State Management with React
Such approach may cause "prop drilling" when you need the same state in deeply nested components and that's where the context API comes in handy.
codesandbox
I know this issue has been discussed before. But, somehow I cannot get it work in my application.
Normally, the navigation works fine between components. However, history.push only changes the url but does not update the content. For example, in the Login page, I want to navigate user to Home page if already logged in. But, the code only updates the url. Any ideas?
const Login = () => {
useEffect(() => {
if (authenticationService.currentUserValue != null) {
history.push('/home');
}
}, [])
//other code
}
In index.js, I have the following
<BrowserRouter basename={baseUrl} history={history}>
<Provider store={store}>
<App />
</Provider>
</BrowserRouter >,
In app.js, I have:
<Layout>
<Switch>
<PrivateRoute exact path="/home" component={withRouter(Home)} />
<Route exact path='/home' component={withRouter(Home)} />
<Route exact path='/login' component={Login} />
<Route exact path='/register' component={withRouter(Register)} />
</Switch>
</Layout>
The issue in your case is that you are using a custom history with BrowserRouter which isn't correct. BrowserRouter uses its own history and you must use that to change pages
const Login = ({history}) => {
useEffect(() => {
if (authenticationService.currentUserValue != null) {
history.push('/home');
}
}, [])
//other code
}
If you have used custom history for a reason, then you need to use Router with a custom history prop
<Router basename={baseUrl} history={history}>
<Provider store={store}>
<App />
</Provider>
</Router >
I'm stuck with a problem in my project. I'm trying to show a component and work with the this.props.match.params, but no matter what I do I get undefined.
Routes:
const App = () => (
<BrowserRouter>
<Fragment>
<Header/>
<main>
<Switch>
<Route path="/show/:id" component={Show}/>
<Route path="/" component={Home}/>
</Switch>
</main>
</Fragment>
</BrowserRouter>
);
export default App;
then I have a handler on my home route:
async handleSubmit(searchId) {
const id = await DwellingService.findSiocId(searchId);
if (id) {
this.props.history.push(`/show/${id}`);
}
}
and finally on my show component
componentDidMount() {
console.log(this.props.match.params)
const {id} = this.props.match.params;
if (id) {
this.props.requestFindDwelling(id);
}
}
So I have been researching and I think is not a react router problem, first when I try to access the routes by typing them I was getting unexpected > on my bundle.js which was solved adding <base href="/" /> on the index.html.
Now my component is rendering ok by the console.log of the show component is giving me this:
isExact:false
params:{}
path:"/show"
url:"/show"
When I started the project to be able to use browserhistory and not getting error by refreshing the page I had to add this to my index file:
app.get('/*', function(req, res) {
res.sendFile(path.join(__dirname, './public/index.html'), function(err) {
if (err) {
res.status(500).send(err);
}
});
});
For the kind of error I get I'm supposing the route is not being found and is redirecting me to /show.
<Switch>
<Route path="/show/:id" component={Show}/>
<Route path="/" component={Home}/>
</Switch>
This will never render Home as Switch renders first thing that matches and / will match always the first route. Not sure if this will fix the problem but try and let me know:
<Switch>
<Route exact path="/" component={Home}/> // exact is important
<Route path="/show/:id" component={Show}/>
</Switch>
I have my React Router V4 routes structured this way:
const isAuthenticated = () => {
let hasToken = localStorage.getItem("jwtToken");
if (hasToken) return true;
return false;
};
const AuthenticatedRoute = ({ component: Component, ...rest }) =>
<Route
{...rest}
render={props =>
isAuthenticated()
? <Component {...props} />
: <I NEED TO REDIRECT FROM HERE TO SERVER PAGE />}
/>;
class App extends Component {
render() {
return (
<BrowserRouter basename="/editor">
<Switch>
<AuthenticatedRoute exact path="/" component={AppNav} />
<AuthenticatedRoute
exact
path="/:module"
component={AppNav}
/>
<AuthenticatedRoute
exact
path="/:module/:screen"
component={AppNav}
/>
<AuthenticatedRoute
exact
path="/:module/:screen/:action"
component={AppNav}
/>
<AuthenticatedRoute
exact
path="/:module/:screen/:action/:id"
component={AppNav}
/>
<Route component={PageNotFoundError} />
</Switch>
</BrowserRouter>
);
}
}
export default App;
As you see on code, if not authenticated I want to redirect to server page. The page is another react application to manage user registration and is located in the server but in another route tree: /registration
What I've tried with no success:
<Redirect to="//registration' />
windows.location = "/registration"
windows.location.href = "/registration"
<Redirect to="http//registration' />
windows.location = "http://registration"
windows.location.href = "http://registration"
All of the redirects to page in current application.
What would be the solution for this ?
I had a create-react-app project with react router, and the problem that when entering a path '/path/*' it loaded the as if in the same react router, even though I had configuration for using another react project with an express server.
The problem was that the service worker was running in the background, and for any route inside '/' it was using cache.
The solution for me was to modify the 'registerServiceWorker.js', so that when you are inside the path, you ignore the service worker. I didn't get too deep in learning about service workers, but here is the code I used:
export default function register() {
...
window.addEventListener('load', () => {
const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`;
if(window.location.href.match(/*\/path\/*/)){
// Reload the page and unregister if in path
navigator.serviceWorker.ready.then(registration => {
registration.unregister().then(() => {
window.location.reload();
});
});
} else if (isLocalhost) {
...
When inside the path, it unsubscribes the service worker and reloads the page.
I implemented it like so:
const AuthenticatedRoute = ({ component: Component, ...rest }) =>
(<Route
{...rest}
render={props =>(
isAuthenticated()
? (<Component {...props} />)
: ( window.location = "http://your_full_url" )
)}
/>);
Its been hours I am not finding a way or any good documentation which can show me how to navigate to any page programmatically. Here is my use case:
I have a login form, as soon as user login successfully, they should navigate to the account page, so I am trying to do this redirection from my actions, here is my code (Note: I am using typescript):
/* Router.tsx */
const RouterComponent = () => {
return (
<Router>
<Switch>
<Route exact path="/" component={LoginForm} />
{/* <Route exact path="/" component={EmployeeList} /> */}
<Route path="/account" component={Account} />
</Switch>
</Router>
)
}
/* App.tsx */
render () {
const store = createStore(reducers, {}, applyMiddleware(ReduxThunk));
return (
<Provider
store={store}>
<Router />
</Provider>
)
}
/* Reducer */
import { routerReducer } from 'react-router-redux';
export default combineReducers({
auth: AuthReducer,
account: AccountReducer,
routing: routerReducer
})
/* Login action */
const loginUserSuccess = (dispatch: any, user: any) => {
dispatch({
type: LOGIN_USER_SUCCESS,
payload: user
});
// Here I want to add programmatic navigation
}
I was trying to follow this doc: https://github.com/reactjs/react-router-redux, but its using browserHistory from 'react-router' which is not available in react-router.
Versions:
"react-router-dom": "^4.2.2",
"react-router-redux": "^5.0.0-alpha.8",
One way is to use react-router-redux
import { push } from 'react-router-redux';
...
dispatch(push(`/account/${user.id}`));
Your App.tsx has an empty <Router /> component that is not connected to redux because it is not nested inside a <Provider> tag.
/* App.tsx */
render () {
const store = createStore(reducers, {}, applyMiddleware(ReduxThunk));
return (
<Provider
store={store}>
<Router>
<Switch>
<Route exact path="/" component={LoginForm} />
{/* <Route exact path="/" component={EmployeeList} /> */}
<Route path="/account" component={Account} />
</Switch>
</Router>
</Provider>
)
}