How to re-render the same component being used in different routes? - reactjs

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);
}

Related

How hide component from another component start render through route on react

I would like to hide a component when another component is routing
for more specific, i have a fixed bottom nav that i want to hide when a component is been route by user, in this case its a comment box
My first option was trying on the parent component with browserhistory and history.listen, and componning a string with math.params for get a match between, and this change the state of parent, witch will hide the bottomnav, and the code wrote was trying through a cyclelife passing props, but nothing, anyone can help me please?
class App extends React.Component {
constructor(props){
super(props);
this.state = {
showBottomNav: true
}
this.hideBottomNav = this.hideBottomNav.bind(this)
}
hideBottomNav= () => {
this.setState({
showBottomNav: false
})
}
render() {
return (
<Router>
<NavBar />
<Switch>
<Route path='/' exact component={Home} />
<Route path='/wars' exact component={Tournament} />
<Route path='/shop' exact component={Shop} />
<Route path='/library' exact component={Library} />
<Route
path='/:id'
exact
render={ props => <ExpandedPost {...props} parentMethod={() => this.hideBottomNav()} />}
/>
</Switch>
<BottomNav />
</Router>
);
}
}
export default App;
And the children Component
export default function PostReview(props) {
const classes = useStyles();
useEffect(() => {
props.parentMethod()
},[props])
return (
<div>....

React router receives match params but does not update the component

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.

react.js redirect to view

i want redirect to "/user". i write but this not work.
how to correctly redirect to the right page
onClick = (e) => {
this.setState({ errorLoad: false});
getPlayerInfo(this.state.id).then(data => {
if(data.success == false) {
this.setState({ errorLoad: true});
return;
}
this.setState({ user: data.player});
console.log(data);
<Redirect to="/user"/>
});
}
My router list. Among them there is a router with the path "/ user"
<Route path="/user" render={(props) => <User {...props} user={this.state.user} />} />
UPADATE
App.js
The button I click on is in the component <SearchForm/>
render() {
let style = {marginLeft: '20px'};
return (
<div>
<Header source='https://www.shareicon.net/data/2017/02/15/878753_media_512x512.png'/>
<SearchForm onClick={this.onClick} style={style} onChange={this.onHandle} placeholder="search"/>
<Centered style={ {marginTop: '50px'} }>
<BrowserRouter>
<Switch>
<Route exact path='/' component={Startup} />
<Route path="/user" render={(props) => <User {...props} user={this.state.user} />} />
</Switch>
</BrowserRouter>
</Centered>
</div>
);
}
There are two ways to programmatically navigate with React Router - <Redirect /> and history.push. Which you use is mostly up to you and your specific use case.
<Redirect /> should be used in user event -> state change -> re-render order.
The downsides to this approach is that you need to create a new property on the component’s state in order to know when to render the Redirect. That’s valid, but again, that’s pretty much the whole point of React - state changes update the UI.
The real work horse of React Router is the History library. Under the hood it’s what’s keeping track of session history for React Router. When a component is rendered by React Router, that component is passed three different props: location, match, and history. This history prop comes from the History library and has a ton of fancy properties on it related to routing. In this case, the one we’re interested is history.push. What it does is it pushes a new entry onto the history stack - aka redirecting the user to another route.
You need to use this.props.history to manually redirect:
onClick = (e) => {
this.setState({ errorLoad: false});
getPlayerInfo(this.state.id).then(data => {
if(data.success == false) {
this.setState({ errorLoad: true});
return;
}
this.setState({ user: data.player});
console.log(data);
this.props.history.push('/user');
});
}
You should be getting history as a prop from your <Router> component.
EDIT:
Okay thank you for the code update. The SearchForm component is not nested under your BrowserRouter, so it is not getting the history prop. Either move that component inside the BrowserRouter or use the withRouter HOC in SearchForm reacttraining.com/react-router/web/api/withRouter
Option 1: Move SearchForm inside the BrowserRouter
render() {
let style = {marginLeft: '20px'};
return (
<div>
<Header source='https://www.shareicon.net/data/2017/02/15/878753_media_512x512.png'/>
<Centered style={ {marginTop: '50px'} }>
<BrowserRouter>
<SearchForm onClick={this.onClick} style={style} onChange={this.onHandle} placeholder="search"/>
<Switch>
<Route exact path='/' component={Startup} />
<Route path="/user" render={(props) => <User {...props} user={this.state.user} />} />
</Switch>
</BrowserRouter>
</Centered>
</div>
);
}
Option 2: use the withRouter HOC to inject the history prop into SearchForm manually:
import { withRouter } from 'react-router-dom';
class SearchForm extends React.Component { ... }
export default withRouter(SearchForm)

react-router : share state between Routes without Redux

I would like to have a shared state (list of clients fetched remotely) between 2 sibling Routes : Timesheets and Clients.
I want to try how far i can go with 'pure' React (No Flux architecture).
This example works, but I have an error : browser.js:49 Warning: [react-router] You cannot change <Router routes>; it will be ignored
So, it doesn't seem to like async props.
constructor(props) {
super(props);
this.state = {
clients : []
}
}
componentDidMount() {
fetch("clients.json")
.then(response => response.json())
.then(clients => this.setState({ clients }));
}
render() {
return (
<Router history={browserHistory}>
<Route path="/" component={Header} >
<Route path="timesheet" component={() => (<Timesheets {...this.state} />) }/>
<Route path="clients" component={() => (<Clients {...this.state} />) }/>
</Route>
</Router>
);
}
Is it possible to send async props down to each route?
Or is it possible to set the whole state in the parent route (Header component) and then access this state from each child route (Timesheets and Clients components)?
You can use an high-order component to fetch and inject data to your top level component. Then you can pass props to sub routes via React.cloneElement.
HOC
const FetchHOC = url => Component => class extends React.Component() {
state = { data: null };
componentDidMount() {
fetch(url).then(data => this.setState({ data }));
}
render() {
return <Component {...this.props} {...this.state.data} />;
}
}
Route configuration
<Route path="/" component={FetchHOC('http://some-url')(App)}>
<Route path="subroute1" component={SubRoute1} />
<Route path="subroute2" component={SubRoute2} />
</Route>
Parent route render()
<div className="App">
{this.props.children && React.cloneElement(this.props.children, {
data: this.props.data,
// ...
})}
</div>
You can take a look at reacts Context. Redux also makes use of Context. It allows you to pass some data down to all children. But you should write code to use this data, for instance you have determine contextTypes etc.
You can see details on docs about how to use it.

How to update state after React Router changes automatically?

I have a file with router and a component. Shortly, the code is like this:
// define the routes for each language ..
const InnerRoutes = (
<Route>
<IndexRoute page="home" component={StaticPage}></IndexRoute>
<Route path="contacts" component={ContactsPage}></Route>
<Route path="(:page)" component={StaticPage}></Route>
</Route>
);
// define the routes for all the languages, using InnerRoutes ..
const AllRoutes = (
<Router history={browserHistory}>
<Route path='/' component={App} language="bg">
{InnerRoutes}
<Route path="en" language="en">
{InnerRoutes}
</Route>
</Route>
</Router>
);
// and render our app ..
ReactDOM.render(
AllRoutes,
document.getElementById('app')
);
My question is: how can I have the App component state changed when router change is triggered?
And of course - have the router params in the app state.
(Because currently I can take the router stuff from the App component's method componentDidUpdate and then trigger setState to change the App state. Unfortunately - then I have the componentDidUpdate triggered twice.)
I've added this to my App and it seems to receive changes when routes change. More reference here
class App extends React.Component {
...
getInitialState() {
return {
lang: 'en' // default
};
}
componentWillReceiveProps(props) {
console.log('location: ', props.location.pathname);
var newLang = props.location.pathname.split('/').shift();
if(this.state.lang !== newLang) {
this.setState({lang: newLang});
}
}
render() {
const lang = this.state.lang;
return (
<AboutPage language={lang} />
<Support language={lang} />
);
}
}
If this doesn't work, you can also look into how two components talk to each other

Resources