I have a page where i add products to a product list. After a successful add, the app needs to go back to the list to see the products again with the new entry added. The redirecting to the product list works fine, and i see the products again, but the new product is not on the list. In order to view the new added products, i have to hard reload the page. For some reason the component is not getting re-rendered thus not requesting the new product list from the server.
Why is this happening? How can I make the ProductPage re-render without forcing a full app refresh.
I have reproduced the problem in this codesandbox.
This was pretty much covered by Jacob Smith in his above comment. However, to expand a little upon his explanation, you have a setup that looks something like this:
// App.jsx
return (
<>
<h1>Some header</h1>
<Switch>
<Route path='productlist' component={ProductList} />
</Switch>
</>
)
// ProductList.jsx
const [state, setState] = usetState();
useEffect(() => {
loadSomeData().then(data => {
setState(data);
})
}, []);
return (
<>
<Switch>
<Route path='productlist' render={() => <ProductPage data={state} />} />
<Route path='productlist/add' component={AddProduct} />
</Switch>
</>
)
Due to this data flow, new data is only loaded when ProductList mounts, unmounts, and mounts again. However, nothing you do in either ProductPage or AddProduct ever unmounts ProductList, since both are rendered in that component.
This can be fixed in two main ways, move the render of AddProduct to the top level in App.jsx or, if Axios.post returns the new data, you can hand down the state setting method to AddProduct and set the state in the .then callback, since the parent component is not unmounted.
Another way is that you should really hard reload after the submitting the form by adding the following code at the end of the form submit handler function of your form.
window.location = '/productList';
Related
I want setup aside element in react-router-dom,
which show on everything page, without 404.
Bellow my code:
<>
<aside>
<*Routes*\>
<*Route* *path*="\*" *element*={<*BookSidebar* />} />
</*Routes*\>
</aside>
<*Routes*\>
<*Route* *element*={<*BooksLayout* />}>
<*Route* *index* *element*={<*BookList* />} />
<*Route* *path*=":id" *element*={<*Book* />} />
<*Route* *path*="new" *element*={<*NewBook* />} />
<*Route* *path*="\*" *element*={<*NotFound* />} />
</*Route*\>
</*Routes*\>
</>
At the moment <*Route* *path*="\*" *element*={<*BookSidebar* />} /> , showed on each pages even NotFound, how I can fix it?
Here's my take on this problem.
If I got it right, you want to render <BookSidebar /> everywhere but on the NotFound page.
What you could do is a logic-oriented rendering based on the useLocation() hook provided by that same React Router!
So, first hand, we'll need to render this on each of our page, and what contains each of our page? App.js here.
So, our code on App.js should look more or less like this:
{aside && (
<aside>
<BookSidebar/>
</aside>
)}
This will allow us to render, or not the BookSidebar component based on the aside state.
Let's add that state, and set it to true, as most of the time, it'll be true.
const [aside, setAside] = useState(true);
We're getting there, all that is left is to detect, at each time the user navigates from a page, if he is on the 404 page or not.
And there's nothing much more convenient for that than useEffect.
But, before that, we need to use the useLocation() hook of React Router, which allows us to know where the user is at any given time, because remember, we need to know if he is or not on the 404 Error page!
const location = useLocation();
This needs to run in the App.js, as it's where we render our Route.
useEffect(() => {
console.log("Location changed! We are now in: ", location.pathname);
if (location.pathname === "/error404path") {
console.log("Detected we were in /error404path, hiding Aside.");
setAside(false);
}
}, [location]);
And that's it! Our logical rendering based on the state that we can, on purpose, turn off based on any conditions, is done and working!
I am seeking to recreate a pattern with React Router. It is best described by the Twitter example: as you hit the Tweet button, the browser navigates to /compose/tweet, mounting the composer component. However, and that's the key, the previous route (/home, /explore, /notifications, /messages) is kept mounted despite the route change. How do you do that?
This could be called bidimensional routing: the /compose/tweet route is kept orthogonal with respect to the other routes that render the main view. The other routes are hidden (i.e., not in the address bar any longer) upon navigating to /compose/tweet, thereby rendering two independent routes (say, /notifications and /compose/tweet) at once.
My actual example: I need to show a user settings menu (/user/menu) as a large sidebar, but I do not want that to change whether the user was navigating / (the homepage), /faq, /contact, etc. Based on my current understanding of React Router, as soon as you hit /user/menu, any other route (take /faq as an example) would be unmounted based on route match.
Caching the previous route (e.g., Redux, which I'm using extensively already) does not seem feasible, since, even though I would be able to redirect the user to the previous route upon exiting /user/menu, React would still be unmounting components, in fact showing the homepage in the background until the user exits /user/menu & gets redirected to where they were at, which is not the intended behaviour. I would want the rest of the page to stay there with the rendered components, just the way Twitter does.
Am I overlooking anything? Is this an easy pattern and I am missing something?
Note: it's a SSR isomorphic app, but I guess/hope that won't change things.
Despite Adam Abramov's suggestion to keep React Router as the source of truth for whatever can be passed as route, and avoid deep integrations between Router and Redux, I found myself having to use Redux as the main source of truth in this (important) use case. I still wanted to have Route components for SSR and SEO purposes.
So, I created my own MultidimensionalSwitch and MultidimensionalRoute components to solve this use case. If a MultidimensionalSwitch is mounted, it will render the components at their subroutes, but if none of them is matched, it will render them based on another dimension, which is provided by Redux at an additional alt property of the corresponding MultidimensionalRoute.
Here below is some code, feel free to answer/ask should you need more info about it.
Main
class _Main extends Component {
render() {
const {menuOpen,selected} = this.props;
return (
<Fragment>
<Route exact path={exactRoutes.ROOT} component={() => <Redirect to={selected?exactRoutes[selected]:exactRoutes.HOME} />} />
<Header />
<Route path={nestedRoutes.AUTH+routeParams.LOGIN_SIGNUP.key} component={ScreenAuth} />
<Route path={exactRoutes.USER_MENU} component={ScreenUser} />
{!menuOpen?"":<ScreenMenu />}
<ScreenMain>
<MultidimensionalSwitch>
<MultidimensionalRoute path={exactRoutes.HOME} alt={selected===guestMenuOption.HOME} component={GuestHome} />
<MultidimensionalRoute path={exactRoutes.USER} alt={selected===guestMenuOption.USER} component={User} />
<MultidimensionalRoute path={exactRoutes.VISION} alt={selected===guestMenuOption.VISION} component={GuestVision} />
<MultidimensionalRoute path={exactRoutes.FAQ} alt={selected===guestMenuOption.FAQ} component={GuestFaq} />
<MultidimensionalRoute path={exactRoutes.INFOGRAPHICS} alt={selected===guestMenuOption.INFOGRAPHICS} component={GuestInfographics} />
<MultidimensionalRoute path={exactRoutes.BLOG} alt={selected===guestMenuOption.BLOG} component={GuestBlog} />
<MultidimensionalRoute path={exactRoutes.CONTACT} alt={selected===guestMenuOption.CONTACT} component={GuestContact} />
</MultidimensionalSwitch>
<Footer />
</ScreenMain>
<Flare />
</Fragment>
);
};
}
const mapStateToProps = state => ({
menuOpen: state.client.guest.menuOpen,
selected: state.client.guest.guestMenuOption,
});
const Main = withRouter(connect(mapStateToProps,{})(_Main));
export default Main;
MultidimensionalSwitch
class _MultidimensionalSwitch extends Component {
render() {
return (
<Fragment>
<Switch>
{this.props.children}
{this.props.children.map(child => !child.props.alt?"":<Route path={nestedRoutes.ROOT} component={child.props.component} />)}
</Switch>
</Fragment>
);
}
}
const mapStateToProps = state => ({
selected: state.client.guest.guestMenuOption,
});
const MultidimensionalSwitch = withRouter(connect(mapStateToProps,{})(_MultidimensionalSwitch));
export default MultidimensionalSwitch;
MultidimensionalRoute
class _MultidimensionalRoute extends Component {
render() {
const {path,component} = this.props;
return (
<Fragment>
<Route exact path={path} component={component} />
</Fragment>
);
}
}
const mapStateToProps = (state,ownProps) => ({
path: ownProps.path,
component: ownProps.component,
});
const MultidimensionalRoute = withRouter(connect(mapStateToProps,{})(_MultidimensionalRoute));
export default MultidimensionalRoute;
I'm trying to display articles filtered by subject. When you access directly path="/subject/:selectedSubject" (subject/TECH) for example it works perfectly. However if you navigate through <Link to={"/subject/TECH"} /> it will change the URL but will not load new articles.
I've tried: connecting everything with "withRouter".
I know that the <Link/> is changing the redux state, however that is not calling componentWillMount() which is where fetchArticles() is.
So, my question is, where should I put fetchArticles in order for it to be called when is triggered. I tried putting it inside render() but it keeps getting called non-stop. Or maybe I'm approaching this the wrong way.
PS: if another path gets called via <Link/>, like for example path="/", it works as intended (loads up new articles).
at App/
<BrowserRouter>
<div>
<Route path="/" exact component={ArticlesList} />
<Route path="/subject/:selectedSubject" component={ArticlesList} />
</div>
</BrowserRouter>
at ArticleList/
componentDidMount() {
if (this.props.match.params.selectedSubject) {
const selectedSubject = this.props.match.params.selectedSubject.toUpperCase();
this.props.fetchArticles(selectedSubject);
} else {
this.props.fetchArticles();
}
}
at Link/
{this.props.subjects.map(subject => {
return (
<Link to={`/subject/${subject.name}`} key={subject.name}>
<li>
<span>{subject.name}</span>
</li>
</Link>
);
})}
Since you use the same Component for / and /subject/:selectedSubject, he's not calling mounting lifecycle methods, including constructor() and componentDidMount().
You have to use updating lifecycle method, especially componentDidUpdate() to handle a update of your component.
I have a container component with a modal in it that is opened and closed based on a state property.
I want to control this via the URL, i.e. I want to have
/projects - the modal is NOT open
/projects/add - the modal IS open
As well as being able to link directly to it, I want the URL to change when I click on links within the main container to open the modal.
Can someone explain how I could do this, or point me in the right direction of a good tutorial?
NOTE: This way is not perfect. Even more it's rather antipattern than pattern. The reason I publish it here is it works fine for me and I like the way I can add modals (I can add modal to any page and in general their components don't depends on the other app in any way. And I keep nice url's instead of ugly ?modal=login-form). But be ready to get problems before you find everything working. Think twice!
Let's consider you want following url's:
/users to show <Users /> component
/users/login to show <Users /> component and <Login /> modal over it
You want Login to not depend on Users in anyway, say adding login modal to other pages without pain.
Let's consider you have kinda root component which stands on top of other components. For example Users render may look something like this:
render() {
return (
<Layout>
<UsersList />
</Layout>
);
}
And Layout's render may look something like this:
render() {
return (
<div className="page-wrapper">
<Header />
<Content>
{this.props.children}
</Content>
<Footer />
</div>
);
}
The trick is to force modal's injection to <Layout /> every time we need it.
The most simple approach is to use flux for it. I'm using redux and have ui reducer for such page meta-information, you can create ui store if you use other flux implementation. Anyway, final goal is to render modal if <Layout />'s state (or even better props) contains modal equal to some string representing modal name. Something like:
render() {
return (
<div className="page-wrapper">
<Header />
<Content>
{this.props.children}
</Content>
{this.props.modal ?
<Modal key={this.props.modal} /> :
null
}
<Footer />
</div>
);
}
<Modal /> returns modal component depends on given key (In case of our login-form key we want to receive <Login /> component).
Okay, let's go to router. Consider following code snippet.
const modal = (key) => {
return class extends React.Component {
static displayName = "ModalWrapper";
componentWillMount() {
// this is redux code that adds modal to ui store. Replace it with your's flux
store.dispatch(uiActions.setModal(key));
}
componentWillUnmount() {
store.dispatch(uiActions.unsetModal());
}
render() {
return (
<div className="Next">{this.props.children}</div>
);
}
}
};
...
<Route path="users" component={Next}>
<IndexRoute component={Users}>
<Route path="login" component={modal('login-form')}>
<IndexRoute component={Users} />
</Route>
</Route>
(Don't care about Next - I add it here for simplicity. Imagine it just renders this.props.children)
modal() function returns react component that triggers change in ui store. So as soon as router gets /users/login it adds login-form to ui store, <Layout /> get it as prop (or state) and renders <Modal /> which renders corresponding for given key modal.
To programmatically assess to a new URL, pass the router to your component and use push. push for example will be call in the callback trigger by the user action.
When setting your router set a route to /projects/:status. then, in your component route, you can read the value of status using this.props.param.status. Read "whats-it-look-lik" from react-router for an example.
I am trying to create a job site. Following pages shows list of all the jobs which is shown once user hits search button from home page. So basically this is the second page.
In this page i am catching all the search parameter from url and fetching data from api and result is shown as below:
Once the user clicks individual joblist, detail page should load on the same page without changing header and fixed component with unique URL for the detail page. Expected result shown below:
My Problem:
I manage to create a nested Route, which renders detail page on the same page and also has a unique url. But it renders on top of existing job list. I mean if user clicks on joblist1, detail page renders on top of subsiquent list(above list: 2, 3, 4). But expected result is to only render detail page but not list of jobs when individual job list is clicked.
My code: I have only shown part of the code for brevity and simplicity.
1) jobs.js: Passes state data to child component to show list.
return(
<div>
<div>
fixed component
</div>
<div>
<RouteHandler />
<JobLists joblists={this.state.joblists} />
</div>
</div>
)
2) jobList.js: uses .map function to go through all data and handleclick function generate url and opens that url once user clicks individual link. Router catches nested route and loads value inside jobs.js in " ".
handleClick: function(i){
var base_path = window.location.protocol + '//' + window.location.host;
base_path += '/#/jobs-detail';
window.location= base_path;
},
render: function(){
var jobListRow = this.props.joblists.map(function(jobrowobj, i){
return(
<div key={jobrowobj.id} onClick={this.handleClick.bind(this, i)}>
<img src={jobrowobj.logo} alt="" />
<h3>{jobrowobj.title}</h3>
</div>
)
}.bind(this));
return(
<ul id="joblists">
{jobListRow}
</ul>
)
}
3) Route file:
var routes = (
<Route handler={App}>
<DefaultRoute handler={Home} />
<Route name="jobs" path="jobs" handler={Jobs}>
<Route name="jobs-detail" handler={JobDetail} />
</Route>
<NotFoundRoute handler={NotFoundPage} />
</Route>
);
I am not sure what is the best way to switch certain section (component) on a page as in my case switching between joblist component and jobdetail component. As you can see i am only able to load other component on top of existing component which is not the expected result.
Also would appreciate if any hint is given to maintain scroll position on the job list on user hitting back button.
I suggest you to upgrade your react-router to 1.0.0-rc1, and the API is more clear. Your problem is similar to the official introduction. The nested component will be passed as this.props.children, and you can insert it into the jobListRow.
About the scroll position, there's a github issue discussing how to restore it :)