Component render() triggered after history.push but page is blank - reactjs

I want the user to be redirected to the resources list after he deleted an item on its show page. I 've read a lot of SO Q&A on the topic, but I think I have a different issue as my routes and component got hit the right way after history.push
I tracked code execution through debugger till component render and still don't understand why nothing is returned
Here are my routes in my App component (wrapped this way<Router><App /></Router>) component :
<Route component={AppHeader} />
{["/articles/:id/edit", "/articles/new"].map((path, index) =>
<Route key={index} exact path={path} component{ArticleForm}/>
)}
<Route exact path="/articles/:id" component={Article}/>
{["/", "/articles"].map((path, index) =>
<Route key={index} exact path={path} component{ArticlesList}/>
)}
I use redux so Router and ArticleList are exported this way :
export default withRouter(connect(mapStateToProps, mapDispatchToProps)(Component))
In AppHeader component, a delete button is provided, if user is on show or edit page. When clicking on the link, following method is triggered :
class AppHeader extends Component {
...
deleteArticle = async () => {
await ajaxHelpers.ajaxCall('DELETE',`/articles/${this.state.currentArticleID}`, {}, this.state.token)
this.props.history.push("/")
}
...
}
Then Route with ArticlesList is triggered and should render this component. Here is what happens (breakpoints all the way in render methods):
URL is updated
Router is rendered
App header is rendered
Article list is rendered
Article list re-rendered with fecth from API (state populated with list)
Article list re-rendered with componentDidUpdate
BUT page stays blank ... I am using this.props.history.push("/") in other components and it works fine (list get re-rendered and displayed). See blank page and console.logs :
If I manually reload the page, it renders normally.
What is preventing any component to be displayed (DOM is nearly empty, I only get my empty <div id="root"></div>) in this case ?

Do one thing ,to displaying Article list use filter to update list state
deleteArticle = async () => {
await ajaxHelpers.ajaxCall('DELETE',`/articles/${this.state.currentArticleID}`, {}, this.state.token)
this.setState({articleList:articleList.filter((list)=> list.id!==this.state.currentArticleID)})
}
Change :
this.props.history.push("/")
To :
this.setState({articleList:articleList.filter((list)=> list.id!==this.state.currentArticleID)})

Related

react router, how to make page refresh on url change?

I am making a movie app, where the each movie has its own dedicated page. On this page I have provided similar movies that can be clicked on and ideally would take you to its own movie page. I have a route that looks like this
<Route name="movie" path="/movie/:movieID" exact ><MoviePage /></Route>
the link to this route is in each and every movie component that I have created. The issue is that when I click on a similar movie the url changes to the url of the similar movie and I am able to access the similar movie's properties in the developer console, however the page itself does not change. But once refreshed the page's contents change to those that correspond to the url.
I cannot find a way to force refresh the page in order to get the new movie's information to be displayed.
The issue is likely that the MoviePage component needs to react to the movieID route param updating.
Given Route:
<Route name="movie" path="/movie/:movieID" exact>
<MoviePage />
</Route>
MoviePage:
Use an useEffect hook to handle any logic/data fetching/etc based on the movieID param updating.
const { movieID } = useParams();
React.useEffect(() => {
// movieID on initial render or subsequent render when updated
// logic to use movieID and resynchronize any data.
}, [movieID]);
If still using class components, then use the componentDidUpdate lifecycle method. (Assumes MoviePage wrapped in withRouter HOC to receive route props, specifically the match prop)
componentDidUpdate(prevProps, prevState) {
if (prevProps.match.params.movieID !== this.props.match.params.movieID) {
// logic to use movieID and resynchronize any data.
}
}

React app, API call that fetches routes for app

Hello I am using react and redux, i have my action creator that fetches the routes of the page and i creat the routing with them this way:
First in app.js im calling the action creator (using mapDispatchToProps) in UseEffect and passing the result (mapStateToProps) to the Routes component:
useEffect(() => {
fetchMenu();
}, []);
<Routes menu={menu} />
Then in Routes.js:
{menu.map((item) => (
<PrivateRoute
key={item.id}
exact
path={item.link}
parentClass="theme-1"
component={(props) => selectPage(item.linkText, props)}
/>
))}
The problem is that if I refresh the page, there is a little delay between the api call and the render of the page, so for one second the browser shows "NOT FOUND PAGE" and then instantly redirect to the route. How can I make it work properly? Thank you !
Basically what you want is to be able to know that the data hasn't been loaded yet, and render differently based on that. A simple check would be see if the menu is empty. Something like this:
export const Menu = ({ menu, fetchMenu }) => {
useEffect(() => {
fetchMenu();
}, []);
if ( menu.length > 0 ) {
return <Routes menu={menu} />
} else {
return <MenuLoading />
}
}
A more advanced setup would be able to tell the difference between an empty menu due to an API error and a menu that's empty because it's still loading, but to do that you would need to store information about the status of the API call in the state.

Fetching data from API and react-router-dom on refresh / direct link

I'm learning redux and routing and history with react-router-dom and react-connected-router for an app. I have an api and I do my fetchData in componentDidMount():
componentDidMount() {
const { onFetchData, prodPerPage, currentPage, filters } = this.props;
onFetchBooks(prodPerPage, currentPage, filters);
}
In my pagination component I use a link that routes to the page which works fine onClick because I re-fetch data on click where currentPage becomes the pageIndex so data is fetched for that page.
Here is my link:
<Link to={`/products/page/${item.page}`}>
<PaginationItem {...item} />
</Link>
Here is my route:
<Route exact path="/products/page/:page" component={ProductsPage} />
If I reload my page my currentPage is set to 1 from initialState.
On Back/Forward my page again loads data for page 1.
How can I set my currentPage to be whatever I navigate to? So if my link is https://localhost:3000/products/page/3 then set currentPage to 3 and fetch the correct data?
Note: If I try to load https://localhost:3000/products/page/3sddada it doesn't redirect to 404, how do I fix that too?
Thanks in advance.
We need to get access to the dynamic pageid being passed into our route and use it to query the correct page from the API. This is easy to do using react-router-dom. The library passes in a prop called match into every route that is rendered. Inside this match object is another object called params. This holds all matching params where the key is the name we specified when creating the route and the value is the actual value in the URL.
componentDidMount() {
const { page} = this.props.match.params;
this.setState({currentPage:page});
const { onFetchData, prodPerPage, filters } = this.props;
onFetchBooks(prodPerPage, currentPage, filters);
}
React-router v4 now allows you to use regexes to match params -- https://reacttraining.com/react-router/web/api/Route/path-string
<Switch>
<Route exact path="/products/page/:page(\\d+)" component={ProductsPage}/>
<Route exact path="/products/page/:page(\\w+)" component={ErrorRedirectPage}/>
</Switch>

Refreshing Component after Route Change

I have a row of buttons that all links to a chart being rendered, then the button pressed, it decides which data will be shown on the chart below.
<div>
<Route path="/" component={Main} />
<Route path="/chart/:topic" component={Chart} />
</div>
Button element:
<Link to={"/chart/" + collection.name}>
<Button key={index} data-key={index} style={this.btnStyle}>
{this.store.capitalizeFirstLetter(collection.name)}
</Button>
</Link>
This works fine when the button is pressed for the first time. However if the user tries to change the data by pressing a different button the chart component does not refresh at all, browser shows that the URL has changed however the component does not refresh at all.
I know this is because of, I've put a console.log in the chart component and it does not come up the second time a button is pressed.
componentDidMount = () => {
const { match: { params } } = this.props;
this.topic = params.topic;
console.log("chart topic", this.topic);
this.refreshData(true);
this.forceUpdate();
this.store.nytTest(this.topic, this.startDate, this.endDate);
};
As you can see I tried to put a forceUpdate() call but that did nothing. Any help is appreciated!
It's because your component is already rendered and didn't see any change so it don't rerender.
You have to use the componentWillReceiveProps method to force the refresh of your component
Example
componentWillReceiveProps(nextProps){
if(nextProps.match.params.topic){
//Changing the state will trigger the rendering
//Or call your service who's refreshing your data
this.setState({
topic:nextProps.match.params.topic
})
}
}
EDIT
The componentWillReceiveProps method is deprecated.
Now the static getDerivedStateFromProps is prefered when you're source data are coming from a props params
Documentation
This method shoud return the new state for trigger the remounting, or null for no refresh.
Example
static getDerivedStateFromProps(props, state) {
if (props.match.params.topic && props.match.params.topic !== state.topic) {
//Resetting the state
//Clear your old data based on the old topic, update the current topic
return {
data: null,
topic: props.match.params.topic
};
}
return null;
}
componentDidMount is only called once while mounting(rendering) the component.
You should use getDerivedStateFromProps to update your component

Why am I losing my ReactJS state?

I'm new to ReactJS, but I have a simple use case: I have a login form that sets the user's state (full name, etc.) and then I use the React Router to browserHistory.push('/') back to the home page. I can confirm that if I stay on the login page that my states actually get saved, however when I go back to the homepage, and into my "parent" component (App.js) and run this before the render method:
console.log(this.state) // this returns null
It always returns true. My constructor is not even doing anything with the state. If I put a log in the constructor on my App.js parent component I can verify that the page is not actually being reloaded, that the component is only mounted once (at least the constructor on App.js is only called once during the whole homepage > login > homepage lifecycle). Yet again, the state seems to be removed after changing pages.
What simple thing am I missing?
Edit: some code, trying to simplify it:
// LoginForm.js
//Relevant method
handleSubmit() {
this.login(this.state.username, this.state.password, (success) => {
if (!success)
{
this.setState({ isLoggedIn: false })
this.setState({ loginError: true })
return
}
this.setState({ isLoggedIn: true })
browserHistory.push('/') // I can verify it gets here and if at this point I console.log(this.isLoggedIn) I return true
})
}
// App.js
class App extends React.Component {
constructor(props) {
super(props);
console.log('hello')
}
render() {
const { props } = this
console.log(this.state) // returns null
return (
<div>
<AppBar style={{backgroundColor: '#455a64'}}
title="ADA Aware"
showMenuIconButton={false}>
<LoginBar/>
</AppBar>
<div>
{props.children}
</div>
</div>
)}
//Part of my routes.js
export default (
<Route path="/" component={App}>
<IndexRoute component={HomePage}/>
<Route path="/login" component={LoginForm}/>
<Route path="*" component={NotFoundPage}/>
</Route>
);
Where you call handleSubmit(), what component calls it?
If it is <LoginForm> or <LoginBar> or something like this, your this. means that component, non <App>.
To set parent's state (<App> in your case) you should pass to child a property with a handler function (for example onLogin={this.handleAppLogin.bind(this)}) so your child must call this prop (this.props.onLogin(true)) and handler in the <App> will set App's state: handleAppLogin(isLoggedIn) { this.setState({isLoggedIn}); }
But for the "global" state values such as login state, access tokens, usernames etc, you better shoud use Redux or some other Flux library.
This was a closed issue router project and discussed in many SO articles:
https://github.com/ReactTraining/react-router/issues/1094
https://github.com/reactjs/react-router-redux/issues/358
Redux router - how to replay state after refresh?
However, it is persistent real world need. I guess the pattern of how to handle this is based on Redux and/or browser storage.

Resources