React-router does not render anything if routes are changed in render() - reactjs

For some reason, my routes only render half the time - seems like a race condition of some sort. It'll print out the "OK" but nothing from the routes, not even the 404. Pretty clear cut.
If I remove the loading bit it'll always render the switch block as intended.
Is there a better / different way to do this?
v4.2.0
render() {
const { hasInitialized } = this.props;
if (!hasInitialized) {
return (
<div>Loading...</div>
);
}
return (
<div style={{ height: '100%', width: '100%' }}>
<Helmet titleTemplate="%s - Resilient" defaultTitle="Resilient" />
<div>OK</div>
<Switch>
<Redirect from="/" to="/auth/check" exact={true} />
<Route path="/auth" component={AuthLayout} />
<AuthenticatedRoute path="/x" component={AdminLayout} />
<Route component={Miss404} />
</Switch>
</div>
);
}
https://github.com/ReactTraining/react-router/issues/5621

I read the react-router docs many times, and the part about Blocked Updates seems relevant. But, when I put a debugger line in <Layout />, location and history always have the right info, and still, none of the routes would render.
I still don't understand what the issue was, but here's the workaround I came up with. The code below wraps my <Layout /> component, which contains all the routes.
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { withRouter } from 'react-router-dom';
import LocalStorageManager from 'utils/LocalStorageManager';
import { selectCurrentUser, selectHasInitialized } from 'client/selectors/authSelectors';
import { setAccessToken, getProfile } from 'shared/api';
import { setHasInitialized, signIn } from 'modules/auth/actions.js';
import SinglePageCard from 'components/layout/SinglePageCard';
const mapStateToProps = (state) => {
return {
currentUser: selectCurrentUser(state),
hasInitialized: selectHasInitialized(state),
};
};
export default (WrappedComponent) => {
class Layout extends Component {
componentWillMount() {
const accessToken = LocalStorageManager.getAccessToken();
if (!accessToken) {
this.props.setHasInitialized();
return;
}
setAccessToken(accessToken);
getProfile().then((response) => {
console.log(response);
const { user } = response.data.data;
this.props.signIn(user);
}).catch((error) => {
console.log(error);
this.props.setHasInitialized();
});
}
render() {
const { currentUser, hasInitialized, ...rest } = this.props;
if (!hasInitialized) {
return (
<SinglePageCard>
<div>Initializing...</div>
</SinglePageCard>
);
}
return (
<WrappedComponent {...rest} />
);
}
}
return withRouter(connect(mapStateToProps, { setHasInitialized, signIn })(Layout));
};

Related

useParams returns ':1', ':2', ':3' instead of '1' , '2', '3'

So I'm trying to use useParams reactrouterhook to get id of superhero, to further make request to my server with such id. When in RQsuperherous.js I click on Link and get transported on adress http://localhost:3000/SuperHeroes/:2 as intended, yet in console.log my id equils = ':1', ':2', ':3', and so on, and I cant make request to server.
I'm using react router 6.x
I'm not sure about adress '/SuperHeroes/:id' in App.js. Maybe it should be nested.
my App.js
import { Route, Routes } from 'react-router-dom';
import { BasicLayout } from './pages/BasicLayout';
import { SuperHerous } from './pages/SuperHerous.page';
import { RQSuperHeroes } from './pages/RQSuperHeroes.page';
import { Home } from './pages/Home.page';
import { SuperHero } from './pages/SuperHero.page';
function App() {
return (
<Routes>
<Route path="/" element={<BasicLayout />}>
<Route index element={<Home/>} />
<Route path="/SuperHerous" element={< SuperHerous />} />
<Route path="/SuperHeroes/:id" element={< SuperHero />} />
<Route path="/RQSuperHeros" element={< RQSuperHeroes />} />
</Route>
</Routes>
);
}
export default App;
my superHero page
import { useParams } from 'react-router-dom';
import { useSuperHero } from '../hooks/useSuperHero';
export const SuperHero = () => {
const id = useParams();
const { isLoading, data, isError, error } = useSuperHero({id});
console.log(id)
if (isLoading) {
return <h2>Loading...</h2>
}
if (isError) {
return <h2>{error.message}</h2>
}
return (
<div>
<h1>{data.name}</h1>
<h1>{data.alterago}</h1>
xfcghjk
</div>
)
}
my RQsuperherous.js
import { useSuperHero } from "../hooks/useSuperHero"
import { Link } from "react-router-dom";
export const RQSuperHeroes = () => {
const onSuccess = (data) => {
console.log('Perform side effect after data fetching', data)
}
const onError = (error) => {
console.log('Perform side effect after encountering error', error.message)
}
const { data, isLoading, isError, error, isFetching, refetch } = useSuperHero({onSuccess, onError});
if (isLoading) {
return <h2>Loading...</h2>
}
if (isError) {
return <h2>{error.message}</h2>
}
console.log(isLoading, isFetching)
return (
<>
<div>RQ SuperHeroes Page</div>
<button onClick={refetch}>Fetch heroes</button>
{
data && data.length && (
data.map(el => {
return (
<Link to={`SuperHeroes/:${el.id}`} key={el.id}>
<div className='super' >
<h2>{el.name}</h2>
<p>{el.alterego}</p>
</div>
</Link>
)
})
)
}
</>
)
}
Edited:
if in RQsuperherous component in Link path change to SuperHeroes/${el.id}
I have an error in console : No routes matched location
"/RQSuperHeros/SuperHeroes/1"
I think the problem is here:
http://localhost:3000/SuperHeroes/:2
The : in the route definition is not meant to be in the real route
Use this address and check the id variable
http://localhost:3000/SuperHeroes/2

Props not rendering

having some trouble rendering props in a component for a project using Reactjs. The information is showing in props in the react dev tools, yet I am unable to render them on the browser. When console logging, there is no value showing...
I'm wondering if I need to dig deeper into the api in order to grab what I need?
CocktailRecipe.js
````import React, { Component } from 'react'
// import Spinner from '../layout/Spinner'
class CocktailRecipe extends Component {
componentDidMount(){
this.props.getCocktail(this.props.match.params.idDrink);
// console.log(this.props.match.params.idDrink)
}
render() {
const {strDrink} = this.props.cocktailrecipe;
console.log(strDrink);
// console.log(this.props.cocktailrecipe.strDrink);
// const {loading} = this.props.loading;
// if (loading) {
// <Spinner />
// }else{
return (
<div>
<h3>{strDrink}</h3>
<h3>This is the title</h3>
</div>
)
// }
}
}
export default CocktailRecipe````
app.js
````import { Component, Fragment } from 'react';
import { BrowserRouter as Router, Switch, Route} from 'react-router-dom';
import './App.css';
import Navbar from './layout/Navbar';
import CocktailList from './cocktail/CocktailList';
import axios from 'axios';
import Search from './cocktail/Search';
import Alert from './layout/Alert';
import About from './pages/About';
import CocktailRecipe from './cocktail/CocktailRecipe';
class App extends Component {
state={
cocktails: [],
cocktailrecipe:{},
loading: false,
msg:'',
type:''
}
async componentDidMount() {
try {
this.setState({loading: true})
const res = await axios.get('https://www.thecocktaildb.com/api/json/v1/1/search.php?s=');
// console.log(res.data);
this.setState({cocktails: res.data.drinks, loading: false})
} catch(error) {
console.log(error)
}
}
handleSearchCocktails= async (text) => {
try{
const res = await axios.get(`https://www.thecocktaildb.com/api/json/v1/1/search.php?s=${text}`);
// console.log(res.data);
this.setState({cocktails: res.data.drinks, loading: false})
} catch(error) {
console.log(error)
}
}
// Get cocktail recipe
getCocktail = async (idDrink) => {
try {
const res = await axios.get(`https://www.thecocktaildb.com/api/json/v1/1/lookup.php?i=${idDrink}`);
// console.log(res.data.drinks);
this.setState({cocktailrecipe: res.data.drinks.id, loading: false})
} catch(error) {
console.log(error)
}
}
handleClearCocktails= () => {
this.setState({cocktails:[], loading: false})
}
handleSetAlert=(msgfromSearch, typefromSearch)=>{
this.setState({ msg:msgfromSearch, type:typefromSearch })
setTimeout(()=>this.setState({msg:'', type:''}), 5000)
}
render() {
const {cocktails, loading, cocktailrecipe} = this.state;
return (
<Router>
<div className="App">
<Navbar title="COCKTAIL LIBRARY" />
<div className="container">
<Alert msg={this.state.msg} type={this.state.type} />
<Switch>
<Route exact path='/' render={props=>(
<Fragment>
<Search searchCocktails={this.handleSearchCocktails} clearCocktails={this.handleClearCocktails} showClear={this.state.cocktails.length>0?true:false} setAlert={this.handleSetAlert} />
<CocktailList loading={loading} cocktails={cocktails} />
</Fragment>
)} />
<Route exact path='/about' component={About} />
<Route exact path='/cocktailRecipe/:idDrink' render={props => (
<CocktailRecipe {...props} getCocktail={this.getCocktail} cocktailrecipe={cocktailrecipe} loading={loading}/>
)} />
</Switch>
</div>
</div>
</Router>
);
}
}
export default App;````
In your screenshot, props cocktailrecipe is an array of object.
Use array desctructuring instead of object on CocktailRecipe.js
- const {strDrink} = this.props.cocktailrecipe;
+ const [strDrink] = this.props.cocktailrecipe;
So turns out that my wifi connection is part of the problem. And grabbing the wrong object.
In CocktailRecipe.js I added in:
line 22:
const { drinks } = this.props.cocktailrecipe;
and then put into the render():
{drinks && drinks[0].strDrink }
I'm told that this may not be the most elegant or efficient solution, so if anybody has a better way, please let me know.

I can't implement Redirect in React

I want to redirect to the home page when some condition returns null or false but the action of Redirect is not triggered.
import { Link, Redirect } from "react-router-dom";
if(localStorage.getItem("example") === null || localStorage.getItem("example") === false){
return <Redirect to="/" />
}
I put this code inside in a simple function triggered in one OnClick and componentDidMount(), but it's not working.
You could use Redirect to home page, based on redirect flag that could be changed by using setState in onClickHandler or handleSubmit.
import { Redirect } from "react-router-dom";
class MyComponent extends React.Component {
state = {
redirect: false
}
handleSubmit () {
if(localStorage.getItem("example") === null || localStorage.getItem("example") === false){
return this.setState({ redirect: true });
}
}
render () {
const { redirect } = this.state;
if (redirect) {
return <Redirect to='/'/>;
}
return <YourForm/>;
}
You need to use the Redirect inside render. It is a React Component which renders and then sends the user to the desired path:
import React, { Component } from "react";
import { Route, Switch, Redirect } from "react-router-dom";
class RootPage extends React.Component {
state = {
isLoggedOut: false
};
onClick = () => {
this.setState({
isLoggedOut: true
});
};
render() {
return (
<div>
{this.state.isLoggedOut && <Redirect to="/logout" />}
<button onClick={this.onClick}>Logout</button>
</div>
);
}
}
const Log = () => <h1>Logout</h1>;
class App extends Component {
render() {
return (
<div>
<nav className="navbar navbar" />
<Switch>
<Route exact path="/" component={RootPage} />
<Route exact path="/logout" component={Log} />
</Switch>
</div>
);
}
}
export default App;
When you click on the logout button it will redirect you to the rootPath.
Here is the Demo: https://codesandbox.io/s/q9v2nrjnx4
Have a look at this example in the official docs.
<Redirect /> should be inside your render method if you use a class component. or if you use a function component it should be in what's returned by it.
Example bellow:
import { Component } from 'react';
const PrivateComponent = (props) => {
return(
localStorage.getItem("example")
? <RandomComponent />
: <Redirect to="/signin" />
)
}

React Links in Sidebar work only on first click

Maybe I am missing something obvious here but I have a Sidebar component that is used on 2 pages of my app; Blog and Post. In my Sidebar component I have a dispatch to fetch some data for "pinned articles" and in my App I have some routes. When I click on the link in the sidebar, the link works but if I click a different link in the sidebar, nothing happens. In my redux store I have enabled logging middleware and I see the first click on the link is SUCCESS but clicking a second link in the sidebar shows nothing but CLEAR. Any suggestions?
App.jsx
[... unrelated code omitted ...]
render() {
const { alert } = this.props;
return (
<div>
{alert.message &&
<div className={`alert ${alert.type}`}>{alert.message}</div>
}
<Router history={history}>
<div>
<PrivateRoute exact path="/" component={HomePage} />
<PrivateRoute exact path="/resource-centre" component={Blog} />
<PrivateRoute path="/resource-centre/:id" component={Post} />
<Route path="/login" component={LoginPage} />
<Route path="/register" component={RegisterPage} />
</div>
</Router>
</div>
);
}
Sidebar.jsx
import React from 'react';
import { connect } from 'react-redux';
import { Link } from 'react-router-dom';
import { articleActions } from '../../actions';
import _ from 'lodash';
class Sidebar extends React.Component {
componentDidMount() {
this.props.dispatch(articleActions.fetchPinnedArticles());
}
render() {
const { pinnedArticles } = this.props;
return (
<div>
<h2 className="widget-title">Pinned</h2>
<ul>
{pinnedArticles.items && pinnedArticles.items.map(article =>
<li className="tag-cloud-link" key={article.id}>
<Link to={{ pathname: `/blog/${article.id}` }}>{article.title}</Link>
</li>
)}
</ul>
</div>
)
}
}
function mapStateToProps(state) {
const { pinnedArticles } = state;
return {
pinnedArticles
}
}
const connectedSidebar = connect(mapStateToProps)(Sidebar);
export { connectedSidebar as Sidebar };
articleAction.js
function fetchPinnedArticles() {
return dispatch => {
dispatch(request());
articleService.fetchPinnedArticles()
.then(
pinnedArticles => dispatch(success(pinnedArticles)),
error => dispatch(failure(error))
);
};
function request() {
return { type: articleConstants.FETCH_PINNED_REQUEST }
}
function success(pinnedArticles) {
return { type: articleConstants.FETCH_PINNED_SUCCESS, pinnedArticles }
}
function failure(error) {
return { type: articleConstants.FETCH_PINNED_FAILURE, error }
}
}
articleService.js
function fetchPinnedArticles() {
const requestOptions = {
method: 'GET',
headers: authHeader()
};
return fetch(`${config.apiUrl}/articles/pinned`, requestOptions).then(handleResponse, handleError);
}
articleReducer.js
export function pinnedArticles(state = {}, action) {
switch (action.type) {
case articleConstants.FETCH_PINNED_REQUEST:
return {
loading: true
}
case articleConstants.FETCH_PINNED_SUCCESS:
return {
items: action.pinnedArticles
};
case articleConstants.FETCH_PINNED_FAILURE:
return {
error: action.error
};
default:
return state;
}
}

Redirect landing page component 5 seconds

I am trying to Redirect or Switch the following Hero React component after a 5 second delay using React-Router.
<Redirect to="/home" /> redirect's the component to http://url.com/home instantly.
Any suggestions on how to handle this? Thx.
class Intro extends Component {
render() {
return (
<div className="d-flex w-100 h-100 mx-auto p-4 flex-column">
<Header />
<div
style={{
paddingTop: '40px'
}}>
</div>
<Hero />
<Footer />
</div>
);
}
}
export default Intro;
I've also tried using setTimeout as well
You can use the history prop injected by react-router and invoke the push method inside a setTimeout. You should do it in componentDidMount:
constructor(props) {
// ...
this.redirectTimeout = null;
}
componentDidMount() {
const { history } = this.props;
this.redirectTimeout = setTimeout(() => {
history.push('/home')
}, 5000);
}
Edit: Make sure to clear the timeout to avoid redirects when you move out of the page before the 5 seconds are over, else you will still get redirected:
componentWillUnmount() {
clearTimeout(this.redirectTimeout);
}
You can create a custom DelayRedirect component.
Gist here: https://gist.github.com/JT501/4b0f60fb57b1410604b754fd9031150a
import * as React from 'react';
import { useEffect, useState } from 'react';
import { Redirect, RedirectProps } from 'react-router';
interface DelayProps {
delay: number;
}
const DelayRedirect = ({ delay, ...rest }: RedirectProps & DelayProps) => {
const [timeToRedirect, setTimeToRedirect] = useState(false);
useEffect(() => {
const timeout = setTimeout(() => {
setTimeToRedirect(true);
}, delay);
return () => clearTimeout(timeout);
}, [delay]);
return timeToRedirect ? <Redirect {...rest} /> : null;
};
export default DelayRedirect;
Usage:
<DelayRedirect to={'/'} delay={1000} />

Resources