I use material-ui components in react with react-router. I have a problem when I want to display list-items that are supposed to work as link elements, but also contain a submenu inside that should not trigger the parent link. It does and I don't know how to disable it.
var iconMenu =
<IconMenu iconButtonElement={<IconButton><MoreVertIcon /></IconButton>}>
<MenuItem primaryText='change name' onTouchTap={this.edit}/>
<MenuItem primaryText='delete' onTouchTap={this.delete} />
</IconMenu>
<ListItem
key={i}
containerElement={<Link to={`/items/${item.id}`} />}
rightIconButton={iconMenu}
/>
When I click the iconMenu button, I do not want the <Link to={`/items/${item.id}`} /> to be triggered, so that I stay in the page. But it does. So how can I fix this problem? I tried to add event handler to run stopPropagation() but it was not successful...
Thanks!
For React Router v4, add
onTouchTap={() => this.props.history.push(`/items/${item.id}`)}
to the ListItem, instead of containerElement.
Use import { withRouter } from 'react-router-dom' and export default withRouter(Foo) to add history to the component's props.
First of all, I'd like to admit that I do not like material-ui and thus do not recommend it to people who consider starting a project with it. The reasoning behind is that you sacrifice too much time customising the components to your needs - a solution that is opposite to the idea of React. It also uses inline styles that you always have to overwrite in the component file, not in your scss or less. This sucks big time. I don't even mention all the UI interaction actions that are handled with JS that could make your performance ache.
Another short mention is to the react-router. Unfortunately I'm not a fan of it either. Guys, why do you change the API in every next release? Why is it so damn difficult to just reset the location queries? Just look at FlowRouter and see how fantastic a route API should be implemented.
Anyways, my solution was to implement a wrapper around the <Link /> component and move the <IconMenu /> outside of the <Link /> wrapper:
<li key={i}>
<ListItem
key={i}
containerElement={<Link to={`/items/${item.id}`} />}
/>
{iconMenu}
</li>
Related
I have a component that is only visible when a user hovers over it. In that component I have a button which allows the user to add something to the local storage. If the button is clicked the component is removed from the DOM. This works fine, but I want to show the user a toast when the action is completed. The issue is that the toast is also removed when the button is clicked because it's part of that component:
return (
<React.Fragment>
<Overlay backdrop_path={movie.backdrop_path}>
<div>
<AddMovie onClick={() => addMovie(movie)}>Add movie to your watchlist</AddMovie>
</div>
</Overlay>
<Snackbar
open={open}
onClose={handleClose}
TransitionComponent={Slide}
message="Movie has been added"
/>
</React.Fragment>
)
I rather not put the Snackbar toast in a different component because this component is responsible for adding a movie to the local storage, and I don't want to do a lot of props lifting etc to get the result.
So I thought, maybe it's possible to append the Snackbar element to the body instead of the components element. This way if the components element is removed the Snackbar should still be visible. Not sure if this logic will actually work though.
Is it possible to append a element/component to another part of the DOM structure, if so: how?
Seems appending rendered elements into other DOM elements isn't difficult:
return ReactDOM.createPortal(
<Snackbar
open={true}
onClose={handleClose}
TransitionComponent={Slide}
message="Movie has been added"
/>,
document.body
);
But this element also gets removed the instant the component that renders it is removed. Which makes sense. So it looks I need to render the Snackbar in a different element. Shame.
// edit. By using Redux it might be possible to create a global state for using the Snackbar elements > https://browntreelabs.com/snackbars-in-react-redux-and-material-ui/
// edit#2. https://github.com/iamhosseindhv/notistack this package makes it a lot easier and is supported by the Material UI team.
There is a larger wrapping component, called Sidenav:
const Sidenav = () => (
<SidebarGroup>
<SidebarLink to={routes.example1.path} name="example1" />
<SidebarLink to={routes.example2.path} name="example2" />
</SidebarGroup>
);
And the component that I'm mostly concerned about:
const SidebarLink = ({ to, name }) => (
<li className={styles.sidebarlink}>
<NavLink to={to} activeClassName={styles.active}>{name}</NavLink>
</li>
);
I have 2 problems here.
1) Normally, to works perfectly to redirect to the destination URL. However, when I switched to using NavLink from ReactStrap vs. Link, to no longer works - href has to be used instead in order to correctly perform the click-through action. Other examples online of people using NavLink are all using to - is there some kind of dependency I'm missing?
2) I want to apply activeClassName to the menu item that is currently tabbed to or is being clicked on. Hypothetically, again, the syntax is correct but it is not working as anticipated - specifically, the .active class is not being applied when you click on or tab through the menu item in question. Again, is this a dependency error, or something else? withRouter was suggested in one place, but that did not make a difference.
Thank you in advance for any guidance!
I have created tabs using React Router, with a different route for each tab. However, I would like to maintain the tab state between tab transitions by keeping the hidden tabs mounted. How do I achieve this? React router remounts each component every time the route switches.
Someone has already asked this question here, but has not received an answer
Ideally I would find a solution which keeps the tabs which are not displayed mounted after they are hit for the first time
I'd have to do a little more digging to confirm this actually works, but reading through React Router docs I found this about the Route component. Using the component prop makes the component remount every time the route changes. But using the other render methods, you might be able to achieve what you're looking for. I'd go with render, but children might work too?
This is the recommended method of routing by react-router-dom-v5 doc over render,children and component prop of <Route/>. This way our component gets re-initialized & re-mounted everytime path is matched.
<Switch>
<Route exact path="/">
<Home />
</Route>
<Route path="/contact">
<Contact />
Route>
<Route path="/about">
<About />
</Route>
</Switch>
As you(Kat) want to maintain the tab state between tab transitions by keeping the hidden tabs mounted.
you can achieve this by mounting all the tabs at once and then switch between the tabs by using react-router-dom's pathname.
const { pathname } = useLocation();
{pathname === "/"? <Home/>: null}
{pathname === "/contact"? <Contact/>: null}
{pathname === "/about"? <About/>: null}
This way your component will not get re-initialized and re-mounted everytime path is matched and hence component states will not be disturbed and will be taken care of automatically accross the tabs.
Here is the working DEMO: https://codesandbox.io/s/react-router-experiment-ilfhq?file=/src/component/Home.js:166-201
Hope I answered your question.
Here is the second solution DEMO using CSS: https://codesandbox.io/s/react-router-mouted-routes-32dxf?file=/src/App.js
This is more of an architectural question regarding react than a specific issue, but what is considered best practice for managing state/props with a layout component and a several child components which are rendered based on the url?
Note: I'm aware that similar questions have been asked, but this is a little bit different. [How to update ReactJS component based on URL / path with React-Router
Lets say I have something like the following code: A profile page (main layout view) with navigation links for profile sub-sections (settings, preferences, account details, etc), and a main panel where each of the sub-section is rendered.
So currently I would have something like this:
my router
routes.js
<Router history={browserHistory}>
<Route path='/profile' component={Profile} >
<IndexRoute component={Summary} />
<Route path='/profile/settings' component={Settings} />
<Route path='/profile/account' component={Account} />
<Route path='/profile/preferences' component={Preferences} />
</Route>
</Router>
and a stripped down version of my profile layout component
profile.js
class Profile extends React.Component {
constructor(props) {
super(props)
}
render(){
let pathName = this.props.location.pathname;
return(
<div className='container profile-page'>
<div className='side-nav'>
<ul>
<li><Link to='/profile'>Summary</Link></li>
<li><Link to='/profile/settings'>Settings</Link></li>
<li><Link to='/profile/account'>My Account</Link></li>
<li><Link to='/profile/preferences'>Preferences</Link></li>
</ul>
</div>
<div className='main-content'>
{this.props.children}
</div>
</div>
)
}
}
export default Profile;
So this kind of works. The child components will render based on the url. But then how do I manage state and props? The way I understand React and Flux, I want the Profile component to manage state and listen to changes on my stores, and to propagate those changes to its children. Is this correct?
My problem is that there doesn't seem to be an intuitive way to pass props to components rendered by this.props.children, which makes me feel like my current architecture and/or understanding of flux is not correct.
A bit of guidance would be much appreciated.
I feel what you are doing is perfectly fine. You are on the right path.
There's a combination of APIs that React provides you with that will take care of exactly what you're not certain about of how to achieve ( way to pass props to components rendered by this.props.children )
First, you need to take a look at cloneElement
It will basically take a React element, clone it, and return another with props that you can change, alter or replace entirely based on your needs.
Furthermore, combine it with the Children Utilities - loop through the children that were provided to your top level component and make the necessary changes to each element individually.
A proposed sample usage could be as simple as
<div className='main-content'>
{React.children.map(this.props.children, (child, index) => {
//Get props of child
const childProps = child.props;
//do whatever else you need, create some new props, change existing ones
//store them in variables
return React.cloneElement(child, {
...childProps, //these are the old props if you don't want them changed
...someNewProps,
someOldPropOverwritten, //overwrite some old the old props
});
)}
</div>
Use these tools to create truly generic and re-usable components, anywhere. More commonly used utilities from Children are map, forEach and toArray. Each with their own respective goals.
Hope this helps.
I'm relatively new to React and I'm trying to figure out how I should compose a complex application (not just a simple TODO app).
I have basically a structure like this (greatly simplified):
<Application>
<MenuBar />
<Router>
<Route path="/page1" component={Page1} />
<Route path="/page2" component={Page2} />
<Route path="/page3" component={Page3} />
</Router>
</Application>
<MenuBar> is basically an AppBar, however with the left icon not visible at all times.
There will be many Page components (visible below the MenuBar) and a few of them will use a Drawer for varying reasons.
Depending on the available screen resolution I want my application to be responsive and either:
use the Drawer component on small screens, or
show a fixed sidebar on large screens (like the opened Drawer, but without covering the main content)
This screenshot makes it easier to understand, perhaps:
The content of the Drawer and the sidebar will be exactly the same, as only either one is visible.
Therefore, I'd like to create a <DynamicDrawer> component that can be used at the top level of any Page component:
render() {
const selectionList = <div>will be visible in the drawer/sidebar</div>;
const myContent = <div>will be the main content of the page</div>;
return (
<DynamicDrawer
drawerContent={selectionList}
mainContent={myContent}
/>
);
}
I have no problem implementing that <DynamicDrawer>, however the <MenuBar> of the application component needs some connection to the active <DynamicDrawer>:
when using the Drawer, the <MenuBar> must show the left icon, otherwise not
when the user clicks/taps on that icon, the <Drawer> must be toggled
Should I use some store like Redux to solve this problem? Or pass handlers and state manually around? Should I redesign the component hierarchy completely?
React makes DOM manipulations very straightforward and alarmingly fast.
However, most of the changes on your DOM should be determined by data, and not manually manipulated by you (even if you have the power to). Break this rule and hell will let loose as your app begins to get increasingly complex.
So no, you shouldn't pass handlers and state manually around, and yes, you may very likely have to redesign the hierarchy (flow is a bit cut off from the MenuBar, almost like it's meant to be static).
I might not be able to say specifically what your new hierarchy should be. This is purely dependent on how you want data to flow.
Here's why React users love libraries like Flux and Redux. Both preach that data should only flow in one direction, and all state changes should only occur though a single dispatch call.
Since you're new to React, and have gotten a hang of the basics, I think it's time to look at Redux. Once you understand it, it will be clear to you where to place not just the Menu bar, but also any other component you wish to add.
Edit 1
Since you will love to manipulate the Menubar using the state of the top-level component, instead of just css rules, then the <MenuBar /> component should be within the top-level component, and not with the Router (as a matter of fact, seeing the <MenuBar /> with the router is very strange anyways :).
Your top-level component's render() could look like this:
render() {
const selectionList = <div>will be visible in the drawer/sidebar</div>;
const myContent = <div>will be the main content of the page</div>;
return (
<div>
<Menubar />
{this.props.children}
</div>
);
}
While your <DynamicDrawer /> stay the same. This way, you could pass callbacks as props and watch for changes in the <DynamicDrawer /> that could be used to influence the visibility of the sidebar.
I'd have to add though. If it's only 'screen resolution' that affects the sidebar visibility, having css rules in place could be one way to go.
By re-reading the Router documentation I noticed that <Route> can be nested and still each <Route> level can have it's own components.
That solves my problem in a very elegant way:
<Router>
<Route path="/" component={AppOuter}>
<Route path="/page1" component={Page1} />
<Route path="/page2" component={Page2} />
<Route path="/page3" component={Page3} />
</Route>
</Router>
...with my MenuBar being a child of AppOuter:
render() {
return (
<div>
<MenuBar />
{this.props.children}
</div>
);
}
That means that while being on page1 this results into...
<AppOuter>
<MenuBar />
<Page1 />
</AppOuter>
That way I can keep my Page components and the permanent parts of my application (like the MenuBar) at the same level, i.e. AppOuter and PageX can receive the same props and I can pass callbacks to the page components which belong to the top level component (containing the Router).
Still, it's probably better to go with this hierarchy but use Redux (or similar) to manage the state.
...React is awesome ;-)