Redux component, can I write click events without using class - reactjs

I am writing the following redux component (dumb)
const TopMenuComponent = () => (
<div className={styles.topMenuIndex}>
<header role="banner">
<nav role='navigation'>
<ul>
<li className={styles.expando}>☰</li>
<li>Home</li>
<li>About</li>
</ul>
</nav>
</header>
</div>
);
Now on the click event of the ☰ link I want to write some code to perform some function. I have two questions around this
Is it possible to do this without deriving this from the React.Component class using the above code. My concern is that this is a dumb component and if we derive this from React.Component then we are adding more intelligence into this.
Should this expansion click trigger a stage change on the redux store? This is just a simple expansion of a bunch of links thats all

If you want to make it a dumb component, then you can pass in a property, e.g. isOpen, which defines if the menu is expanded or not, and a function, e.g. handleClick, which should be called on a button click. This way, the state can be managed by a parent component.
Now whether you manage this state in the internal state of parent component or with your redux store is entirely up to you. You should choose redux if you will need to open and close the menu remotely.

There are two ways to achieve what you want to without affecting the redux store
First, Passing function as a prop to the component from the parent component and then calling this onClick
const TopMenuComponent = (props) => {
return (
<div className={styles.topMenuIndex}>
<header role="banner">
<nav role='navigation'>
<ul>
<li className={styles.expando} onClick={props.myhandler}>☰</li>
<li>Home</li>
<li>About</li>
</ul>
</nav>
</header>
</div>
)
};
Second way is to define a onClick handler in the component itsef like
const TopMenuComponent = () => {
const myHandler = (e) => {console.log('clicked'); // some other code}
return (
<div className={styles.topMenuIndex}>
<header role="banner">
<nav role='navigation'>
<ul>
<li className={styles.expando} onClick={myhandler}>☰</li>
<li>Home</li>
<li>About</li>
</ul>
</nav>
</header>
</div>
)
};

Related

React - Replacing a component with another on button click

I've two React components, I want a button click in one to replace it entirely with the second.
This is my App Component:
const App = () =>{
const[showMarketing, setShowMarketing] = useState(false);
const[showLanding, setShowLanding] = useState(true)
const toggleViews = () =>{
setShowMarketing(!showMarketing);
setShowLanding(!showLanding);
}
return (
<div>
{showMarketing && <Marketing/>} {showLanding && <Landing toggleViewFn={toggleViews}/> }
</div>
);
}
export default App;
This is the Component that is rendered by default:
const Landing = ({toggleViewFn}) =>(
<div className="shrink-wrapper">
<header className="small-vertical">
<figure className="logo">
<a href="">
<img src={logo} onClick={() => toggleViewFn()}/>
</a>
</figure>
<nav>
</nav>
</header>
</div>
);
This is the component that I want to render on the img click:
const Marketing = () => (
<div>
......Something here
</div>
);
In the current code, the switch happens for a second and the the UI is back to the Landing component. I can't seem to understand why?
I'm new to React, any help appreciated.
The problem is that your <img/> with the onClick is wrapped with an anchor-- when you click the <img/>, the click handler is fired but the anchor click is also registered and the browser navigates. I suspect if you remove the wrapping <a> this will work as expected.
Ultimately, if you are trying to use this for routing, you'd be better off leveraging a library like React Router; while you could try to manage your own routing on your own, it can be complex and is rife with accessibility pitfalls (for instance, using anchors with empty hrefs to wrap items with onClick handlers is not particularly accessible). Hope this helps.

Target specific child inside React loop with refs and state onClick

I've got a mobile nav menu with a couple dropdown menus:
/* nav structure */
/about
/portfolio
/services
/service-1
/service-2
contact
etc..
I'm creating this menu dynamically with a loop in my render function.
I've set up a state variable to assign a class to the active dropdown menu. I'm using an onClick event/attribute on the trigger element (a small arrow image) to apply an active class to the respective dropdown menu. Kinda...
const myNavMenu = () => {
const [isSubMenuOpen, toggleSubMenu] = useState(false)
return (
<nav id="mainNav">
<ul>
{navItems.items.map((item, i) => (
<li key={i} className={
(item.child_items) !== null ? 'nav-item has-child-items' : 'nav-item'}>
<Link to={item.slug}>{item.title}</Link>
{(item.child_items === null) ? null :
<>
<img className="arrow down" src={arrowDownImg} onClick={() => toggleSubMenu(!isSubMenuOpen)} />
{printSubMenus(item)}
</>
}
</li>
))}
</ul>
</nav>
)
}
/**
* Prints nav item children two levels deep
* #param {Obj} item a navigation item that has sub items
*/
function printSubMenus(item) {
return (
<ul className={(isSubMenuOpen.current) ? "sub-menu sub-menu-open" : "sub-menu"}>
{item.child_items.map( (childItem, i) => (
<li className="nav-item" key={i}>
<Link to={childItem.slug}>{childItem.title}</Link>
{(childItem.child_items === null) ? null :
<>
<ul className="sub-sub-menu">
{childItem.child_items.map( (subItem, i) => (
<li className="sub-nav-item" key={i}>
<img className="arrow right" src={arrowRight} alt={''} />
<Link to={subItem.slug}>{subItem.title}</Link>
</li>
))}
</ul>
</>
}
</li>
))}
</ul>
)
}
}
*<Link> is a Gatsby helper component that replaces <a> tags.
The issue is that when I click my trigger, the active class is being applied to both (all) sub-menus.
I need to insert some sort of index (or Ref) on each trigger and connect it to their respective dropdowns but I'm not quite sure how to implement this.
I was reading up on useRef() for use inside of function components. I believe that's the tool I need but I'm not sure how to implement it in a loop scenario. I haven't been able to find a good example online yet.
Thanks,
p.s. my functions and loops are pretty convoluted. Very open to refactoring suggestions!
Move sub-menu to a new react component and put all the related logic in there (applying class included).

How to initialise React Materialize Drop down after componentDidMount

My Navbar component doesn't have the Materialize Dropdown markup until a user signs in at which point I use conditional rendering and add the new markup. However I don't seem to be able to use React.createRef() to reference the new markup in order to initialise the Dropdown.
Please find a link to my project where you can create an account and see the issue for yourself.
What I think the problem is, is that I cannot recreate the ref after compononentDidMount.
I have tried creating the ref(React.createRef()) in componentWillReceiveProps and componentWillUpdate but I get a null value when I try to create the ref there.
class Navbar extends Component {
userRef = React.createRef();
render() {
const { isAuthenticated, user } = this.props.auth;
// show these when logged
const authLinks = (
<ul id="nav-mobile" className="right hide-on-med-and-down">
<li className="avatar">
<a href="#" ref={this.userRef} data-target="dropdown1" className="dropdown-trigger">
<div className="avatar">
</div>
{user.username}
</a>
<ul id="dropdown1" className="dropdown-content">
<li><Link to="/dashboard">Dashboard</Link></li>
</ul>
</li>
</ul>
);
// show these when not logged in
const guestLinks = (
<ul id="nav-mobile" className="right hide-on-med-and-down">
<li><Link to="/login">Login</Link></li>
<li><Link to="/register">Register</Link></li>
</ul>
);
return (
<nav>
{isAuthenticated ? authLinks : guestLinks}
</nav>
)
}
}
I need to reference the Dropdown DOM element when my Navbar receives new props(when the users successfully has signed in) so I can initialise it(window.M.Dropdown.init(this.dropdownRef.current)) when it has the right markup.

How to pass a method to a child react component?

folks!
I'm starting to construct an app with webpack, react, redux, etc. And im not sure if im facing the situation as i should:
I want to develop a single page app, having a single react node "<myApp/>" that will contain everything inside. The first thing that will appear on my page, is a bootstrap navbar. What i want, is to create a react component that renders the navbar and to pass him 1) a list of strings, and 2) a function that should be called when one of that items is clicked. I want to do it in that way, because i would like to track at top level the "page" where i am (it will be always one of the navbar items).
Here my myApp component:
export default class myApp extends React.Component {
constructor(props) {
super(props);
this.handleNavbarClick = ::this.handleNavbarClick;
this.state = {
currentPage: 'Home',
navbarItems : ['Home', 'contact', 'etc']
};
}
handleNavbarClick(e) {
//debug alert:
alert(e);
this.state.currentPage = e;
}
render() {
return (
<div>
<NavBar items={this.state.navbarItems} itemClickHandler={this.handleNavbarClick}/>
<Wellcome />
<LandingMenu />
CurrentPage: {this.state.currentPage}
</div>
);
}
}
and here my navbar component:
export default class NavBar extends React.Component {
constructor(props) {
super(props);
}
render() {
var handler = this.props.itemClickHandler;
var itemsHtml = this.props.items.map(function(i) {
return <li className="nav-item"> <a className="nav-link" onClick={handler(i)}>{i}</a></li>
});
return (
<nav className="navbar navbar-static-top navbar-dark bg-inverse">
<a className="navbar-brand" href="#"><img src={LogoSrhSmall} /></a>
<ul className="nav navbar-nav">
{itemsHtml}
</ul>
</nav>
)
}
}
As it is, when i click one of the navbar items, nothing happens, but when the page loads, the alert of the function handleNavbarClick is automatically called for each item, and i have no idea why :(
Can somebody please tell me what im doing wrong? im facing the problem in the right way?
Thanks in advance,
John
#janaka-stevens, #Jamby, Thank you!
Applying some changes from your answers, i was able to get the name out by storing it on the attribute "id" and accessing it throw e.target.id.
For this case that's fine, but supose that instead of work with simple strings, i would like to work with an array of more complex objects, let's say:
[
{ name: 'home', url: '/someroute', importantData: {...} },
{ name: 'otherLink', url: '/someOtherroute' , importantData: {...} }
]
and i would like to pass the complete selected object to the parent, myApp?
At the moment, it looks like that:
on myApp:
...
handleNavbarClick(e) {
this.setState({currentPage : e.target.id}) ;
}
...
and on the NavBar:
...
renderNavItem(item) {
return (<li className="nav-item">
<a id={item} className="nav-link" onClick={this.props.itemClickHandler}>{item}</a>
</li>)
}
render() {
return (
<nav className="navbar navbar-static-top navbar-dark bg-inverse">
<a className="navbar-brand" href="#"><img src={LogoSrhSmall} /></a>
<ul className="nav navbar-nav">
{this.props.items.map(this.renderNavItem, this)}
</ul>
</nav>
)
}
...
but if on the <a> tag, instead of to use:
onClick={this.props.itemClickHandler}
i use:
onClick={this.props.itemClickHandler(item)}
the itemClickHandler is called on each render iteration, as react would "call" the method when iterating through the items to display them (without any user interaction).... any idea why?
Thanks in advance,
John
I'm going to suggest some changes to your code to make it more readible:
For the itemClickHandler you use something other than (e) because e usually means event. For that you should do itemClickHandler(page).
And then inside the Navbar component, you probably want to do something like this:
export default class NavBar extends React.Component {
constructor(props) {
super(props);
}
renderNavItem(item) {
<li className="nav-item">
<a className="nav-link" onClick={this.props.handleNavbarClick(item)}>{item}</a>
</li>
}
render() {
return (
<nav className="navbar navbar-static-top navbar-dark bg-inverse">
<a className="navbar-brand" href="#"><img src={LogoSrhSmall} /></a>
<ul className="nav navbar-nav">
{this.props.items.map(this.renderNavItem)}
</ul>
</nav>
)
}
}
BUT the main thing you're doing wrong is that you're NEVER setting state.
The #1 rule in React is you never want to alter a prop or a state (by doing this.prop.whatever = blar or this.state.whatever = blar. If you do that, then nothing will change and components you're passing props or state to may not have the correct information. So if you want to change state you must call this.setState({currentPage: page}).
So inside your handleNavbarClick you want to change it to:
handleNavbarClick(page) {
//debug alert:
alert(page);
this.setState({currentPage: page});
}
This will tell React that you've changed state and wish to re-render.
You have two problems. The first is the e in handleNavbarClick(e) is event. What you would want to do is have an identifier for each of your items list. Then you would get e.target.id. Of course you will also need to add the id in your mapped list. The second problem is you need to pass 'this' to your mapped list like so;
render() {
var itemsHtml = this.props.items.map(function(i) {
return <li className="nav-item"> <a id={i.id} className="nav-link" onClick={this.props.itemClickHandler}>{i}</a></li>
}, this);
Note the second parameter in map.
I had the problem where the handlers ran by themselves without actually clicking the buttons. I was calling the handler with an argument like you were are first. I removed that, and the behavior stopped. I tried the bind(null, props) trick and it worked for the props, but I didn't know how to access the event itself in the handler so I could e.preventDefault().
After looking for a minute, I found this gem, which iterates through all the unnamed arguments a function has received.
for (var i=0; i<arguments.length; i++) {
console.log(arguments[i])
};
Using this, I could access the event in the handler with
e = arguments[1]
and now all is well.
Thanks for giving me a starting point!

How does the ReactBootstrap OverlayTrigger container property work?

I have a popover inside OverlayTrigger.
I define it as
const myOverlayTrigger = <ReactBootstrap.OverlayTrigger
placement='bottom' overlay={<ReactBootstrap.Tooltip>...</ReactBootstrap.Tooltip>}>
{myContent}
</ReactBootstrap.OverlayTrigger>;
Then I render it inside one of my elements like that:
<li>{myOverlayTrigger}</li>
I want to render OverlayTrigger itself inside <li> but it renders inside body, as defined in documentation. I'm trying to use container attribute to render it inside parent <li>.
First, I tried to assign ID to <li> and pass this ID as a string to container=... (which isn't a best way).
Second, I tried to create additional element <span></span> and render it inside along with {myOverlayTrigger}. Also I pass it (assigned to variable) to container attribute
const c = <span></span>;
... container={c} ...
<li>{c} {myOverlayTrigger}</li>
Both approaches consistently gives an error not a dom element or react component.
Obviously assigning <li>...</li> itself as a container doesn't work either as it being defined after myOverlayTrigger is defined.
Question: how to use it right?
ReactBootstrap.Overlay is recommended for the reason listed in the document.
The OverlayTrigger component is great for most use cases, but as a
higher level abstraction it can lack the flexibility needed to build
more nuanced or custom behaviors into your Overlay components. For
these cases it can be helpful to forgo the trigger and use the Overlay
component directly.
For your case, the code below renders the ReactBootstrap.Overlay component into a list item with React ref attribute.
getInitialState() {
return (
show: false
);
},
render() {
return (
<ul>
<li ref="dest" onClick={ () => {
this.setState( { show: !this.state.show } );
}}>my contents</li>
<ReactBootstrap.Overlay placement="bottom"
show={ this.state.show } container={ this.refs.dest }>
<ReactBootstrap.Tooltip>tooltip</ReactBootstrap.Tooltip>
</ReactBootstrap.Overlay>
</ul>
);
}
When the tooltip is displayed by clicking, the resulting HTML would be
<ul data-reactid=".0.1.0.0.0.0.0.1.1.1.1:$3.1.1">
<li data-reactid=".0.1.0.0.0.0.0.1.1.1.1:$3.1.1.0">
contents
<div>
<div role="tooltip" class="fade in tooltip right" data-reactid=".3">
<div class="tooltip-arrow" data-reactid=".3.0"></div>
<div class="tooltip-inner" data-reactid=".3.1">My tooltip</div>
</div>
</div>
</li>
<span data-reactid=".0.1.0.0.0.0.0.1.1.1.1:$3.1.1.1">,</span>
<noscript data-reactid=".0.1.0.0.0.0.0.1.1.1.1:$3.1.1.2"></noscript>
</ul>

Resources