How to pass a method to a child react component? - reactjs

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!

Related

Show / Hide element by data attribute (react=

How can i show/hide elements in a list using react? I have buttons with data-attribute
and i want when click show elements with this classname and hide the others.
example here:
class ModelosItems extends React.Component {
handleCheck(e) {
console.log(e.currentTarget.dataset.gama );
}
render() {
return (
<section className="section">
<div className="container">
<h2 className="title is-size-4 has-text-centered is-uppercase has-text-weight-bold">Gama kia</h2>
<div className="tabs-container">
<div className="fade"></div>
<div className="tabs">
<ul>
<li className="is-active">Gama Completa</li>
<li>Cidatidos e Familiares</li>
<li>Suv e Crossover</li>
</ul>
</div>
</div>
<ModelsList />
</div>
</section>
);
}
};
export default ModelosItems;
Thank you!
For hide the other datas and just show the special data, you have to filter your list. After, you can for example, add a CSS class whose this class hide the other datas, and you'll have just your special data showed.
A tutorial : https://www.youtube.com/watch?v=HqQ-kOchqHM
Utilize local state for this. Initialize it with the first value (probably todos) and update it when you select another type correspondingly.
Next, pass this state data attribute to ModelsList component as a prop and simply filter the items within the list.
Introduce state to ModelosItems
constructor(props) {
super(props);
this.state = {currentGama: null};
}
on click update state
handleCheck(e) {
this.setState({
currentGama: e.currentTarget.dataset.gama
});
}
Add new prop to ModelosItems gama and feed currentGama state value to it
<ModelsList gama={this.state.gama} />
In your ModelList component, I'm guessing here
cars.filter(car => car.gama === this.props.gama).map(...
this will result in rendering only cars that have right gama. This does not use classnames to hide cars. But I think this is what you need.

Can't pass a value from list item to function with onClick event when hyperlink element is nested in list item element

I'm trying to pass a parameter from a number of list items to differentiate them when clicked.
class SettingsGeneral extends React.Component {
constructor(props) {
super(props);
this.state = {
form_fields: ["Select option"],
selectedSetting: "general",
configSettings: {}
};
this.handleClick = this.handleClick.bind(this);
}
handleClick(e) {
const { param } = e.target.dataset;
console.log(param); // e.currentTarget.value would be equivalent
}
render() {
return (
<ul className="settings-list">
<li data-param="value" onClick={this.handleClick}>
<a className="is-active" href="#g" ><i
className="icon icon-settings-general"></i> General</a>
</li>
</ul>
);
}
}
With the current code, when I do console.log(param) in the handleClick(e) method, I get "undefined"
When I remove the hyperlink element
<a className="is-active" href="#g" ><i
className="icon icon-settings-general"></i> General</a>
so now it's just
<ul className="settings-list">
<li data-param="value" onClick={this.handleClick}>
<h1> click</h1>
</li>
</ul>
And then I call the handleClick(e) and console.log(param) again, it logs "value" to the console which is exactly what I want.
Any idea why I can't pass a param to my handleClick function if there's a hyperlink element there? I don't want to remove the hyperlink at this time as it messes up the CSS and a UX designer created the CSS so I don't really have time to mess around with it.
Can I pass a value to my function with this hyperlink element there?
...while I can't explain the 'why' (hopefully someone else will be able to because it's always worth knowing) you can always pass the values directly on the onClick ie:
onClick={e => this.handleClick(e, someValue)}
then in the callback it get's passed in as a second param handleClick(e, someValue)

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.

Redux component, can I write click events without using class

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

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