I'm using react-boilerplate for my project. When changing the router param, the component does not rerender. The router looks like this.
<switch>
<route exact path="/" render={()><homepage {...this.props}/> } />
<route exact path="/practice-areas" component={PracticeAreasLandingPage}/>
<route path="/practice-areas/:practiceAreasItem" component={PracticeAreas}/>} />
<route component={ notfoundpage } />
</switch>
The component looks like this.
class PracticeArea extends PureComponent {
constructor(props) {
super(props);
}
componentWillMount() {
this.props.onInit();
}
render() {
const pa = this.props.practiceArea;
const randPas = this.props.randPracticeAreas;
....
export function mapDispatchToProps(dispatch) {
return {
onInit: () => {
dispatch(loadPracticeArea());
dispatch(loadRandomPracticeAreas());
}
};
}
const mapStateToProps = createStructuredSelector({
practiceArea: makeSelectPracticeArea(),
loading: makeSelectLoading(),
error: makeSelectError(),
randPracticeAreas: makeSelectRandomPracticeAreas(),
randLoading: makeSelectRandomLoading(),
randError: makeSelectRandomError(),
});
const withConnect = connect(
mapStateToProps,
mapDispatchToProps,
);
const withReducer = injectReducer({
key: 'practiceArea',
reducer
});
const withSaga = injectSaga({
key: 'practiceArea',
saga
});
export default compose(
withReducer,
withSaga,
withConnect,
)(PracticeArea);
When clicking on a Link that has a route like '/practice-areas/some-route', the component does not update.
Try moving the PracticeAreas Route before the PracticeAreasLandingPage Route
<Switch>
<Route exact path="/" render={()><homepage {...this.props}/> } />
<Route path="/practice-areas/:practiceAreasItem" component={PracticeArea} />
<Route path="/practice-areas" component={PracticeAreasLandingPage}/>
<Route component={ NotFoundPage } />
</Switch>
Order of Routes is important in a Switch component, since it goes down the list to find a path that is satisfied by the url.
If you navigate to localhost:3000/practice-areas/123 and the PracticeAreasLandingPage Route comes before PracticeArea, you still satisfy the necessary condition for PracticeAreasLandingPage which is just "practice-areas" so Switch ends up rendering that first. And since you are already on that route to begin with, it gives the appearance that nothing was updated.
Swapping the position of the two Routes would resolve this because now you're telling Switch to go down the list and check whether the url, localhost:3000/practice-areas/123 satisfies the path for PracticeArea "/practice-areas/:practiceAreasItem" first.
Related
I am trying to redirect from my context following a failed update of the state from the a cookie.
import React, { createContext, Component } from 'react';
import { withRouter } from "react-router-dom";
import Cookies from 'universal-cookie';
export const MyContext = createContext();
const cookies = new Cookies();
class MyProvider extends Component {
componentDidMount() {
this.setStateFromCookie();
}
setStateFromCookie = () => {
try {
this.setState({ data: cookies.get('my-cookie')['data'] });
} catch(error) {
console.log(error);
this.props.history.push('/');
}
return
};
render() {
return (
<MyContext.Provider value={{...this.state}}>
{this.props.children}
</MyContext.Provider>
);
}
}
export default withRouter(MyProvider);
I am using a withRouter hook to this.props.history.push('/'), becuase the context is wrapping the router
class MyApp extends Component {
render() {
return (
<BrowserRouter>
<MyProvider>
<div className="MyApp">
<Router>
<Route exact path='/' component={Index} />
<Route exact path='/dashboard' component={Dashboard} />
</Router>
</div>
</MyProvider>
</BrowserRouter>
);
}
}
export default MyApp;
The problem is that the redirect to the home page following the error, but the home page isn't rendering.. I still see the dashboard page.
Any idea what is going on and how to fix this
The issue is that you have a nested Router wrapping your Routes. You need to remove that and then everything will work fine
<BrowserRouter>
<MyProvider>
<div className="MyApp">
<Route exact path='/' component={Index} />
<Route exact path='/dashboard' component={Dashboard} />
</div>
</MyProvider>
</BrowserRouter>
When you use a nested Router, and try to navigate from Provider, the history used by Provider is being provided by BrowserRouter and hence it isn't able to communicate to the Routes whcih are dependent on the inner <Router> component for history.
Using a single router wrapping your components solves this issue
When I link from one page to another, a component from the previous page remains on the page after changing to the new page. I start at the landing page and change to the results page using this.props.history.push('/results'). On the results page, another component named BarChart renders. After it is done rendering, I click a link that takes me back to the landing page using . After returning to the landing page, the landing page component renders but the BarChart also remains on the page
I have tried all the solutions that I could find such as wrapping my App with BrowserRouter, Switch, wrapping my exported components using withRouter and more.
App.js:
import {
BrowserRouter as Router,
...
}
const App = () =>
<Router>
<Switch>
<Route exact path={routes.RESULTS} component={() => <ResultsPage />} />
<Route exact path={routes.LANDING} component={() => <LandingPage />} />
</Switch>
</Router>
export default App;
My 3 components:
landing.js:
class LandingPage extends Component {
...
//how I handle switching to results page/component:
handleSubmit(event){
event.preventDefault();
get_data(this.state.search);
const { history } = this.props;
history.push("/results");
}
...
export default compose(
withRouter,
connect(mapStateToProps),
)(LandingPage);
results.js:
import BarChart from './barchart';
class ResultsPage extends Component {
constructor(props){
super(props);
}
render() {
...
//routes.LANDING = '/'
return (
<div>
<Link to={routes.LANDING}>Back to landing page</Link>
<BarChart width={width} height={height}/>
</div>
);
}
}
export default compose(
withRouter,
connect(mapStateToProps),
)(ResultsPage);
barchart.js:
class BarChart extends Component {
constructor(props){
super(props);
this.drawChart = this.drawChart.bind(this);
}
drawChart() {
...
}
render() {
this.drawChart();
return (
<div>
<svg></svg>
</div>
);
}
}
const mapStateToProps = (state) => ({
data: state.dataState.data,
});
export default compose(
withRouter,
connect(mapStateToProps),
)(BarChart);
You just need to switch the order of your routes :)
<Switch>
<Route exact path="/" component={() => <LandingPage />} />
<Route exact path="/results" component={() => <ResultsPage />} />
</Switch>
"every that matches the location renders inclusively" - https://reacttraining.com/react-router/web/api/Switch
I have several routes rendering the same component. Depending on the route I want the component to fetch different data. However since I keep rendering the same component, React doesn't see any changes to the DOM when I click a Link tag (from my nav bar located in the Layout component) to another route rendering that same component. Meaning the component is not re-rendered with the new data. Here are my routes:
class App extends Component {
render() {
return (
<BrowserRouter>
<Provider store={store}>
<Layout>
<Switch>
<Route exact path="/" component={Home} />
<Route exact path="/fashion" component={PostTypePageContainer} />
<Route exact path="/beauty" component={PostTypePageContainer} />
</Switch>
</Layout>
</Provider>
</BrowserRouter>
);
}
}
export default App;
Here is the PostTypePageContainer component that I want to re-render with the new data each time:
class PostTypePageContainer extends Component {
componentDidMount() {
let route;
switch (this.props.location.pathname) {
case '/fashion':
route = '/fashion';
break;
case '/beauty':
route = '/beauty';
break;
default:
console.log('No data was found');
}
let dataURL = `http://localhost:8888/my-site//wp-json/wp/v2${route}?_embed`;
fetch(dataURL)
.then(res => res.json())
.then(res => {
this.props.dispatch(getData(res));
});
}
render() {
let posts = this.props.postData.map((post, i) => {
return <PostTypePage key={i} props={post} />;
});
return <div>{posts}</div>;
}
}
const mapStateToProps = ({ data }) => ({
postData: data.postData
});
export default connect(mapStateToProps)(PostTypePageContainer);
How do I go about re-rendering that component each time?
This is intended behavior of react-router.
While i suggest you create a HOC to fetch the data from different locations and pass it to the PostTypePageContainer via props, using a key will give you a quick work around that will cause your component to remount.
class App extends Component {
render() {
return (
<BrowserRouter>
<Provider store={store}>
<Layout>
<Switch>
<Route exact path="/" component={Home} />
<Route exact key={uniqueKey} path="/fashion" component={PostTypePageContainer} />
<Route exact key={someOtherUniqueKey} path="/beauty" component={PostTypePageContainer} />
</Switch>
</Layout>
</Provider>
</BrowserRouter>
);
}
}
export default App;
Source: https://github.com/ReactTraining/react-router/issues/1703
I wasn't able to get the <Route key={...} ... /> to work in my case. After trying several different approaches the one that worked for me was using the componentWillReceiveProps function in the reused component. This was getting called each time the component was called from a <Route />
In my code, I did:
componentWillReceiveProps(nextProps, nextContext) {
// When we receive a call with a new tag, update the current
// tag and refresh the content
this.tag = nextProps.match.params.tag;
this.getPostsByTag(this.tag);
}
I am new to React and I am working on the search functionality of a site. I am using a create-react-app base.
Here is the routing:
<BrowserRouter>
<App>
<Switch>
<Route path='/' exact component={Home} />
<Route path='/blah' component={Blah} />
<Route path='/boop' component={Boop} />
<Route path="/search/:searchTerm?" component={Search} />
<Route path="*" component={NotFound} />
</Switch>
</App>
</BrowserRouter>
Note the search one has an optional parameter. I am picking up this parameter fine in Search.js
import React, { Component } from 'react';
import { Link } from 'react-router-dom';
import SearchResults from './SearchResults';
export default class Search extends Component {
constructor(props) {
super(props);
this.state = {
searchTerm: this.props.match.params.searchTerm
}
}
render() {
return (
<div className="Search">
SEARCH: {this.state.searchTerm}
<SearchResults searchTerm={this.state.searchTerm} />
</div>
);
}
}
Over in the actual form that triggers the search, this snippet handles the click:
handleClick(event) {
this.props.history.push('/search/'+this.state.value);
}
This works and displays results for paths like /search/test and /search/woo. The only problem is when I go directly from one search to another.
/search/test -> /search/woo updates the path in the address bar, but does not render the content of the woo page with the new search results.
Going to another path between makes it work. So /search/test -> /boop -> /search/woo all works as expected.
Am I missing something here? Do I need to manually trigger this somehow, or should optional parameter changes trigger the components to update?
Thanks!
You need to sync state to props on every change if you want to store this term in component's state.
export default class Search extends Component {
...
componentWillReceiveProps(nextProps) {
const newSearchTerm = nextProps.match.params.searchTerm
if(this.state.searchTerm !== newSearchTerm) {
this.setState(() => ({ searchTerm: newSearchTerm }))
}
}
...
}
But there is no reason to do it. Use this.props in render. It could be as simple as this
export default ({ match: { params: { searchTerm } } }) => (
<div className="Search">
SEARCH: {searchTerm}
<SearchResults searchTerm={searchTerm} />
</div>
)
First my routes:
const routes = (
<Route path="/" component={authWrapper(App)}>
<IndexRoute getComponent={resolveIndexComponent} />
<Route path="about" getComponent={resolveAboutComponent} />
</Route>
);
Notice how the root route gets wrapped with "authWrapper", there I check if user has JWT and dispatch to redux store the result.
The question/problem: How do I load different "child" component for authenticated and non-authenticated users when they enter IndexRoute ?
The above resolveIndexComponent just loads '/domains/home' which contains:
const mapStateToProps = (state) => ({ authenticated: state.common.currentUser });
const App = (props) => {
if (props.authenticated) {
return (<Authenticated />);
}
return (<Unauthenticated />);
};
export default connect(mapStateToProps)(App);
Could I skip this and load either <Authenticated /> or <Unauthenticated /> directly from react-router ? (based on either props or even what authWrapper returns)