I want to toggle a class 'active' into my
currently i have this code:
class VideoListItem extends React.Component {
render() {
const {video, onVideoSelect} = this.props
const{} =this.props
const imageUrl=video.snippet.thumbnails.default.url;
return (
<li onClick={() => onVideoSelect(video)} className="list-group-item">
<div className="video-list media">
<div className="media-left">
<img className="media-object" src={imageUrl} />
</div>
<div className="media-body">
<div className="media-heading">{video.snippet.title}</div>
</div>
</div>
</li>
)
}
}
So eventually I would get "list-group-item active" aftert clicking on that li.
The problem is that I have another function that runs onClick.
I tried this approach
class VideoListItem extends React.Component {
constructor(props) {
super(props);
this.state = {
animate: false
}
this.handleClick = this.handleClick.bind(this);
}
handleClick(e) {
// modify the state, this will automatically recall render() below.
this.setState((prevState) => {
return { animate: !prevState.animate }
});
}
onClick(event) {
() => onVideoSelect(video);
this.handleClick;
}
render() {
let animationClasses = (this.state.animate ? ' active': '');
const {video, onVideoSelect} = this.props
const imageUrl=video.snippet.thumbnails.default.url;
return (
<li className={`list-group-item${animationClasses}`} onClick{this.onClick} >
<div className="video-list media">
<div className="media-left">
<img className="media-object" src={imageUrl} />
</div>
<div className="media-body">
<div className="media-heading">{video.snippet.title}</div>
</div>
</div>
</li>
)
}
}
But I receive the error that: "Uncaught TypeError: Cannot read property 'handleClick' of undefined".
And onClick event does not fire up at all in this case. How can I toggle the class when clicking on this ?
In your constructor you should write:
this.handleClick = this.handleClick.bind(this);
OR definition of onClick should be:
onClick = (event) => {}
These two ways bind the context this to the function. Arrow function implicitly bind this to the function.
bind also your onClick method in the constructor, and check you onClick method.
constructor(props) {
super(props);
this.state = {
animate: false
}
this.handleClick = this.handleClick.bind(this);
this.onClick = this.onClick.bind(this);
}
onClick(event) {
this.props.onVideoSelect(video);
this.handleClick();
}
Related
I'm making a simple todo app, where i have put in the logic to edit and delete the todos as well. I'm trying to update the parent state from child component but when i'm trying to click on delete it is throwing me an error e.preventDefault() is not a function and it is removing all of the todos here are the components:
PARENT
export default class App extends React.Component {
constructor(props){
super(props);
this.state = {
listArr: [],
}
}
deleteTodos(i) {
var lists = this.state.listArr;
lists.splice(i, 1);
this.setState({listArr: lists})
}
render() {
.......
<ToDoList {...this.state} passDeleteTodos={this.deleteTodos} />
......
}
CHILD
export class ToDoList extends React.Component {
constructor(props) {
super(props);
this.state = {
editing: false,
};
handleDelete(e, i) {
e.preventDefault();
this.props.passDeleteTodos()
}
renderDisplay() {
return(
<div>
{
this.props.listArr.map((list,i) => {
return(
<div key={i} index={i} ref="text">
<li>{list}
<div style={{float: 'right'}}>
<button className="btn btn-danger btn-xs glyphicon glyphicon-trash"
onClick={() => this.handleDelete(i)}
/>
</div>
</div>
</div>
You need to pass the event object to handleDelete function when you make use of Arrow function as done in your implementation.
You can think of an arrow function like a function that calls another function to which you need to pass the arguments. Event object is a parameter to the arrow function and you indeed need to pass this on to the handleDelete function
onClick={(e) => this.handleDelete(e, i)}
However after this change you still need to bind the deleteTodos function in the parent, since the context of this inside this function won't be that of the React class component, you can do it like
deleteTodos = (i) => {
var lists = this.state.listArr;
lists.splice(i, 1);
this.setState({listArr: lists})
}
or
constructor(props){
super(props);
this.state = {
listArr: [],
}
this.deleteTodos = this.deleteTodos.bind(this);
}
I change e.preventDefault() => e.preventDefault and bind the function.
Example
export default class App extends React.Component {
constructor(props) {
super(props)
this.state = {
listArr: [],
}
this.deleteTodos = this.deleteTodos.bind(this)
}
handleDelete(e, i) {
e.preventDefault;
this.props.passDeleteTodos()
...
}
render() {
return(
<div>
{
this.props.listArr.map((list,i) => {
return(
<div key={i} index={i} ref="text">
<li>{list}
<div style={{float: 'right'}}>
<button className="btn btn-danger btn-xs glyphicon glyphicon-trash"
onClick={(e,i) => this.handleDelete(e,i)}
/>
</div>
</div>
)}
}
</div>
You are not sending e to the correspondent method.
You could also bind the event
onClick={this.handleDelete.bind(this, i)}
Same applies for deleteTodos in the App component.
Either way you can use the same approach or bind it in the constructor:
export default class App extends React.Component {
constructor(props) {
super(props)
this.state = {
listArr: [],
}
this.deleteTodos = this.deleteTodos.bind(this)
}
...
}
doesn't behave the same way as an so you can't expect the same preventDefault call.
But your problem is you in bind the order of params change. So you're binded param becomes first in the function. See my snippet below.
const App = () => {
const _click = (externalVar, e) => {
console.log("PARAMS", externalVar, e);
};
const externalVar = 1
return (
<button onClick={_click.bind(undefined, externalVar)}>click me</button>
);
};
ReactDOM.render(<App />, document.getElementById('root'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="root"></div>
Like it says here
fun.bind(thisArg[, arg1[, arg2[, ...]]])
arg1, arg2, ... Arguments to prepend to arguments provided to the
bound function when invoking the target function.
arrow function in react doesn't need to bind to this.
But during call to the functions, for example to call this function handleDelete
handleDelete(e, i) {
e.preventDefault();
this.props.passDeleteTodos()
}
we will use synatx as:
handleDelete.bind(i)
handleDelete(e, i) {
e.preventDefault();
this.props.passDeleteTodos()
...
}
onClick={(e,i) => this.handleDelete(e,i)}
if the above code is not working properly try this.
handleDelete(i) {
this.props.passDeleteTodos()
...
}
onClick={(e,i) => {e.preventDefault(); this.handleDelete(i)}}
What I have
I have a class that's not being exported but being used internally by a file. (The classes are all in the same file)
class SearchResults extends Component {
constructor()
{
super();
this.fill_name = this.fill_name.bind(this);
}
fill_name(event, name)
{
console.log("search results", event.target);
this.props.fill_name(name, event.target);
}
render()
{
return (
<div className="search-results-item" onClick={ () => this.fill_name(event, this.props.name)}>
<div className="highlight">
{this.props.name}
</div>
</div>
)
}
}
I'm trying to get the <div> element to be sent back to the parent, which is defined below (skipping irrelevant stuff):
class PublishComponent extends Component {
fill_name(name, me)
{
console.log(me);
$("#company_input").val(name);
this.setState({ list: { ...this.state.list, company: [] } });
}
}
me is the event.
What I'm getting
The console posts the following:
search results <react></react>
undefined
so the event.target is <react></react> for some reason, while the me is getting undefined.
Expected behaviour
It should return the element i.e. <div className="search-results-item"...></div>
You are not passing the event object
Change This
<div
className="search-results-item"
onClick={() => this.fill_name(event, this.props.name)}
/>
To This
<div
className="search-results-item"
// pass the event here
onClick={event => this.fill_name(event, this.props.name)}
/>
This should work for you:
class SearchResults extends Component {
constructor() {
super();
this.fill_name = this.fill_name.bind(this);
}
fill_name() {
this.props.fill_name(this.props.name, this.ref)
}
render() {
return (
<div className="search-results-item" ref={ref => { this.ref = ref }} onClick={this.fill_name}>
<div className="highlight">
{this.props.name}
</div>
</div>
)
}
}
I have just started using React and working on a small app, in the meantime I made a small show and hide modal. I wanted to know the way I have made it is a wrong way to do it. If this is an anti-pattern how should I go about it?
class App extends Component {
constructor(props) {
super(props);
this.state = {show: false};
this.showModal = this.showModal.bind(this);
}
render() {
return (
<div>
<h2 className={styles.main__title}>Helloooo!</h2>
<Modal ref='show'/>
<button onClick={this.showModal} className={styles.addtask}>➕</button>
</div>
);
}
showModal(){
this.setState({
show: true
});
this.refs.show.showModal();
}
}
The modal component which i have made is using this logic, it hooks the dom elements and modifies using the document.queryselector. Is this a right way to do the dom manipulation in react.
The modal code which i have used is this :
class Modal extends Component {
constructor() {
super();
this.hideModal = this.hideModal.bind(this);
this.showModal = this.showModal.bind(this);
this.state = { modalHook: '.'+styles.container };
}
render() {
return (
<div>
<div onClick={this.hideModal} className={styles.container}>
<div className={styles.container__content}>
<div className={styles.card}>
<div className={styles.card__header}>
<h2>Add new task</h2>
</div>
<div className={styles.card__main}>
<Input type="text" placeholder="enter the task title" />
<Input type="textarea" placeholder="enter the task details" />
</div>
<div className={styles.card__actions}>
</div>
</div>
</div>
</div>
</div>
);
}
showModal(){
let container = document.querySelector(this.state.modalHook);
container.classList.add(styles.show);
}
hideModal(e){
let container = document.querySelector(this.state.modalHook);
if(e.target.classList.contains(styles.container)){
container.classList.remove(styles.show);
}
}
}
Your example looks good and simple, but accordingly to this it is better don't overuse refs.
And also it might be helpful to lifting state up, like described here.
Here my example:
class Modal extends React.Component {
constructor(props) {
super(props);
this.state = {show: props.show};
}
componentDidUpdate(prevProps, prevState) {
let modal = document.getElementById('modal');
if (prevProps.show) {
modal.classList.remove('hidden');
} else {
modal.className += ' hidden';
}
}
render() {
return (
<div id="modal" className={this.state.show ? '' : 'hidden'}>
My modal content.
</div>
);
}
}
class App extends React.Component {
constructor(props) {
super(props);
this.state = {show: false};
this.handleClick = this.handleClick.bind(this);
}
handleClick() {
this.setState(prevState => ({
show: !prevState.show
}));
}
render() {
return (
<div>
<button onClick={this.handleClick}>
Launch modal
</button>
<Modal show={this.state.show} />
</div>
);
}
}
ReactDOM.render(<App />, document.getElementById('root'));
Here i don't pretend for ultimate truth, but try to provide another option how you can reach desired result.
To do what you require you don't need to use refs at all. You can pass the state down the to child component as a prop. When the state updates the prop will automatically update. You can then use this prop to switch a class. You can see it in action on jsbin here
const Modal = (props) => {
return (
<div className={props.show ? 'show' : 'hide'}>modal</div>
)
}
const styles = {
main__title: 'main__title',
addtask: 'addtask'
}
class App extends React.Component {
constructor(props) {
super(props);
this.state = {show: false};
this.toggleModal = this.toggleModal.bind(this);
}
render() {
return (
<div>
<h2 className={styles.main__title}>Helloooo!</h2>
<Modal show={this.state.show} />
<button onClick={this.toggleModal} className={styles.addtask}>➕</button>
</div>
);
}
toggleModal(){
this.setState({
show: !this.state.show
});
}
}
ReactDOM.render(<App />, document.getElementById('root'));
My showroom component is as follows :
export default class Showrooms extends React.Component {
constructor(props){
super(props);
this.state = {
blockView: true
};
}
handleChangeView(view){
console.log(view);
this.setState({blockView: view});
}
render(){
const language = this.props.language;
return (
<div className="col-lg-10 col-md-9 col-sm-9 col-xs-12 text-center">
<div className="lists">
<div className="listsTitle">{language.portal.introPageTitle}</div>
<ViewBar handleChangeView={this.handleChangeView.bind(this)}/>
<div className="allLists">
<div className="view">
{this.props.allLists.map( (list, i) => <View key={i} list={list} language={language} blockView={this.state.blockView}/>)}
<div className="clearfix"/>
</div>
</div>
<div className="clearfix"/>
</div>
</div>
);
}
}
and my viewBar component is as follows :
export default class ViewBar extends React.Component {
constructor(props){
super(props);
this.state = {
blockView: true
};
}
setBlockView(event){
event.preventDefault();
this.setState({blockView: true}, this.props.handleChangeView(this.state.blockView));
}
setListView(event){
event.preventDefault();
this.setState({blockView: false}, this.props.handleChangeView(this.state.blockView));
}
render(){
let blockViewAddBorder = this.state.blockView ? "activeView" : "";
let listViewAddBorder = !this.state.blockView ? "activeView" : "";
return (
<div className="viewBar">
<Link to="" onClick={this.setListView.bind(this)} className={`listViewIcon ${listViewAddBorder}`}>
<FontAwesome name="list" className="portalFaIcon"/>
</Link>
<Link to="" onClick={this.setBlockView.bind(this)} className={blockViewAddBorder}>
<FontAwesome name="th-large" className="portalFaIcon"/>
</Link>
</div>
)
}
}
In viewBar component I have two onClick functions where I update the state and the I call an function from showroom component to update the state.
Depending on that state I change the way how I display the content.
But the problem is, when the function setListView is called first time, the state doesn't change to false. When I second time call setListView then it sets the state to false.
this.props.handleChangeView function is an callback function, and it should be called after the state is updated.
Any advice?
Second argument in setState should be function
this.setState({ blockView: true }, () => {
this.props.handleChangeView(this.state.blockView);
})
in your example, you pass result to setState from handleChangeView
this.setState({
blockView: true
}, this.props.handleChangeView(this.state.blockView));
handleChangeView returns nothing, it means that you pass to setState undefined
this.setState({
blockView: true
}, undefined);
so you don't call handleChangeView after setState
I am building a menubar using React JS. On clicking a menu item, respective megamenu should open. On clicking outside of the megamenu, it should close itself. I have made upto this by toggling the state of the megamenu. But I also want to close the megamenu, when the menu-item is clicked second-time(i.e. toggle close and open of megamenu by clicking menu-item). I am stuck here, on clicking the menu-item second time, the state is not toggling back.
class Menubar extends React.Component {
constructor() {
super();
this.state = {
clicked: false
};
this.handleClick = this.handleClick.bind(this);
this.handleOutsideClick = this.handleOutsideClick.bind(this);
}
componentWillMount() {
document.addEventListener('click', this.handleOutsideClick, false);
}
componentWillUnmount(){
document.removeEventListener('click', this.handleOutsideClick, false);
}
handleClick() {
this.setState({clicked: !this.state.clicked});
}
handleOutsideClick(){
if (this.refs.megaMenu.contains(event.target)) {
} else {
this.setState({
clicked: false
});
}
}
render() {
return (
<div className="container">
<div className="menu-bar">
{/* Menu*/}
<div className="menu-bar-item">
<a className="menu-bar-link" href="#" onClick={this.handleClick}>Points</a>
<div className={"mega-menu"+" "+this.state.clicked} ref="megaMenu">
<div className="mega-menu-content">
<p>Points Menu</p>
</div>
</div>
</div>
</div>
</div>
);
}
}
ReactDOM.render(
<Menubar />,
document.getElementById('example')
);
Codepen Demo
You need to move ref="megaMenu" so it includes button as well, otherwise when you click on button handleOutsideClick is also triggered and you flip this.state.clicked twice. Also, you forgot to pass event in handleOutsideClick handler.
class Menubar extends React.Component {
constructor() {
super();
this.state = {
clicked: false
};
this.handleClick = this.handleClick.bind(this);
this.handleOutsideClick = this.handleOutsideClick.bind(this);
}
componentWillMount() {
document.addEventListener('click', this.handleOutsideClick, false);
}
componentWillUnmount() {
document.removeEventListener('click', this.handleOutsideClick, false);
}
handleClick() {
this.setState({clicked: !this.state.clicked});
}
handleOutsideClick(event) {
if (!this.menu.contains(event.target)) {
this.setState({
clicked: false
});
}
}
render() {
return (
<div className="container">
<div className="menu-bar">
{/* Menu*/}
<div className="menu-bar-item" ref={el => this.menu = el}>
<a className="menu-bar-link" href="#" onClick={this.handleClick}>Points</a>
<div className={"mega-menu" + " " + this.state.clicked}>
<div className="mega-menu-content">
<p>Points Menu</p>
</div>
</div>
</div>
</div>
</div>
);
}
}
ReactDOM.render(
<Menubar />,
document.getElementById('example')
);
I've fixed your Codepen Demo
Also, consider using callback refs like ref={el => this.menu = el}
It's a better way to do it.