React: Adding event.preventDefault() Method Into Functional Component - javascript

Currently I'm trying to avoid refreshing page by adding a preventDefault() call into the onClick handler of a functional component (BookList defined in bookList.js). I know I can make it with from class component to functional. However, is there any way to call preventDefault() in the onClick event handler in BookList?
Here is my sample code:
BookListElement.js
import React from 'react';
import { connect } from 'react-redux';
import BookList from '../components/bookList';
import { deleteBook } from '../store/actions/projectActions';
const BookListElement = ({books, deleteBook}) => {
if(!books.length) {
return (
<div>
No Books
</div>
)
}
return (
<div>
{Array.isArray(books) ? books.map(book => {
return (
<BookList book={book} deleteBook={deleteBook} key={book._id} />
);
}): <h1>something wrong.</h1>}
</div>
);
};
const mapStateToProps = state => {
return {
books: state.books
};
};
const mapDispatchToProps = dispatch => {
return {
deleteBook: _id => {
dispatch(deleteBook(_id));
}
};
};
export default connect(
mapStateToProps,
mapDispatchToProps
)(BookListElement);
bookList.js
import React, { Component } from 'react';
const styles = {
borderBottom: '2px solid #eee',
background: '#fafafa',
margin: '.75rem auto',
padding: '.6rem 1rem',
maxWidth: '500px',
borderRadius: '7px'
};
const BookList = ({ book: { author, publication, publisher, _id }, deleteBook }) => {
return (
<form>
<div className="collection-item" style={styles} key={_id}>
<h2>{author}</h2>
<p>{publication}</p>
<p>{publisher}</p>
<button className="btn waves-effect waves-light" onClick={() => {deleteBook(_id)}}>
<i className="large material-icons">delete_forever</i>
</button>
</div>
</form>
);
};
export default BookList;
action.js
export const deleteBookSuccess = _id => {
return {
type: DELETE_BOOK,
payload: {
_id
}
}
};
export const deleteBook = _id => {
return (dispatch) => {
return axios.delete(`${apiUrl}/${_id}`)
.then(response => {
dispatch(deleteBookSuccess(response.data))
})
.catch(error => {
throw(error);
});
};
};
reducer.js
case DELETE_BOOK:
let afterDelete = state.filter(book => {
return book._id !== action.payload._id
});
return afterDelete;

If you don't want a button to trigger form submission, add type="button" attribute to the button element.
By default a button submits the form (has type set to submit).
Setting type="button" signifies that it has no default behavior.
<form>
<button type="button">type button doesn't trigger refresh</button>
<button>no type triggers refresh</button>
</form>

The preventDefault method needs to be called on an event. However in the way you are setting up your onClick handler, the event is not passed to your handler.
Here is how you can fix this issue (in bookList.js):
import React from 'react';
const BookList = ({ book: { author, publication, publisher, _id }, deleteBook }) => {
const handleClick = event => {
event.preventDefault();
deleteBook(_id);
}
return (
<form>
<div>
<h2>{author}</h2>
<p>{publication}</p>
<p>{publisher}</p>
<button onClick={ handleClick }>
<i>delete_forever</i>
</button>
</div>
</form>
);
};
So onClick will pass the event (by default) then just call preventDefault on that event, and then call deleteBook.

I made a simple illustration (component) where one click event triggers the reload, and the other doesn't. If this doesn't work for you, the problem is another place, meaning in a different component(pieces of code you haven't provided). I hope it helps, if not maybe it will help someone in the future :)
class Example extends React.Component {
constructor(props) {
super(props);
this.preventDefault = this.preventDefault.bind(this);
this.dontPrevent = this.dontPrevent.bind(this);
}
// handler recieves the `e` event object
dontPrevent() {
alert('This page will reload :)');
}
preventDefault(e) {
alert('Page will NOT reload');
e.preventDefault();
}
render() {
return (
<div>
<form onSubmit={this.preventDefault}>
<div>
<h3>NO Reload Example</h3>
<p>
<button>Submit</button>
</p>
</div>
</form>
<hr/>
<form>
<div>
<h3>Reload Example</h3>
<p>
<button onClick={this.dontPrevent}>Submit</button>
</p>
</div>
</form>
</div>
);
}
}
ReactDOM.render(
<Example />,
document.getElementById('app')
);
<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="app"></div>

Related

Callback from Child to Parent React

I am very new to React and this is my first task on an existing project..! Okay, coming to the point, So there is a Parent component ContactForm.js that has child component UserInfoStep.js. There is a button in parent component that I need to enable/disable based on a toggle button in the child component. I have tried using callback but it gives me error..
TypeError: Cannot read properties of undefined (reading 'callbackFunction')
ContactForm.js
const callbackFunction = (isChecked) => {
//do enable/disable
this.Button.disabled = isChecked;
};
function ContactForm() {
const [buttonState, setButtonState] = useState(true);
const { PUID,FirstName, LastName, EurofinsLegalEntity, EurofinsBusinessUnit, StartDate, EndDate, frequency, test, exp, signed, laptop, transfer, existing, newL, categories} = state;
const steps = [
<UserInfoStep parentCallback = {this.callbackFunction} {...{ PUID,FirstName, LastName, EurofinsLegalEntity, EurofinsBusinessUnit, StartDate, EndDate}} />];
return (
<ContactFormContext.Provider value={{ dispatch }}>
//some code removed
<Button
disabled={buttonState}
type="submit"
className={classes.button}
color="primary"
variant="outlined"
onClick={e => {
e.preventDefault();
if (isLast) {
handleSubmit();
} else {
goForward();
}
}}
>
{isLast ? "Submit" : "Next"}
</Button>
//some code removed
</ContactFormContext.Provider>
);
}
export default ContactForm;
UserInfoStep.js
function UserInfoStep ({ PUID, FirstName, LastName, EurofinsLegalEntity, EurofinsBusinessUnit, StartDate, EndDate }) {
const { dispatch } = useContext(ContactFormContext);
const [checked, setChecked] = useState(false);
const handleChange = () => {
setChecked((prev) => !prev);
//as soon as this is checked, enable the button in contactForm
sendData(checked);
};
const sendData = (isChecked) => {
this.parentCallback(isChecked);
//Enabled = isChecked;
};
return(
//some controls
<FormControlLabel
control={<Switch checked={checked} onChange={handleChange} />}
/>
);
}
export default UserInfoStep;
You're using function components so there's no need to use this.
Your state isn't being used because...
...your callback function is outside of the component and trying to set this.Button which doesn't exist.
So (this is a very simplified version of your code): move the handler inside the component so that it can update the state directly, and use the button state to inform the disabled property of the button which state it should be in.
const { useState } = React;
function UserInfoStep({ handleChange }) {
return (
<button onClick={handleChange}>Toggle parent button</button>
);
}
function ContactForm() {
const [ buttonState, setButtonState ] = useState(true);
function handleChange() {
setButtonState(!buttonState);
}
return (
<div>
<div class="child">
Child component
<UserInfoStep handleChange={handleChange} />
</div>
<div class="parent">
Parent component
<button disabled={buttonState}>
Parent button
</button>
</div>
</div>
);
};
ReactDOM.render(
<ContactForm />,
document.getElementById('react')
);
.child, .parent { margin: 0.5em 0 0.5em 0; }
.child button:hover { cursor: pointer; }
.parent button { background-color: #b3ffcc; }
.parent button:disabled { background-color: #ff9980; }
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.2/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.2/umd/react-dom.production.min.js"></script>
<div id="react"></div>
You can have a function inside the ContactForm component which toggles the buttonState. You can then pass that function as a prop to the child component and call that function for enabling/disabling the button.
in ContactForm component
For eg:
const toggler = () =>{
setButtonState(!buttonState);
}
pass this toggler function as a prop to the child component and attach it to the onClick handler of the toggle button.

React.js updates some part of DOM nodes when I'm deleting only one element

As I know React creates a new Virtual DOM and compares it with previous one, then it updates Browser DOM with least number of changes possible without rendering the entire DOM again. (in short)
In React documentation I have also read how key should work.
for demonstrating this, I have created todo app, but when I'm deleting one element, all previous elements are re-rendered again (except if I'm not deleting recently added element)
Here is screenshot:
(In Chrome developer tool paint flashing is active for showing renders)
My questions:
Why do previous elements re-render again?
Why key could not fix this problem?
Here is the entire code:
TodoApp.jsx
import React, { useState } from 'react';
import Input from './Input';
import List from './List';
const TodoApp = () => {
const [inputText, setInputText] = useState('');
const [todos, setTodos] = useState([]);
const onSubmitChange = e => {
e.preventDefault();
setTodos([
...todos,
{ text: inputText, completed: false, id: Math.random() * 1000 },
]);
setInputText('');
};
const onChangeEvent = e => {
setInputText(e.target.value);
};
return (
<div>
{todos.map(todo => {
return (
<List todos={todos} setTodos={setTodos} todo={todo} key={todo.id} />
);
})}
<Input
onSubmit={onSubmitChange}
onChange={onChangeEvent}
inputText={inputText}
/>
</div>
);
};
export default TodoApp;
List.jsx
import React from 'react';
import "../todolist/css/TodoApp.css"
const List = ({ todo, todos, setTodos }) => {
const deleteHandle = () => {
setTodos(todos.filter(el => el.id !== todo.id));
};
const completeHandle = () => {
setTodos(
todos.map(el => {
if (el.id === todo.id) {
return { ...el, completed: !el.completed };
}
return el;
})
);
};
return (
<div className={`${todo.completed ? 'completed' : ''}`}>
<div>{todo.text}</div>
<div className="btns">
<button onClick={deleteHandle} className="btn btn-delete">
Delete
</button>
<button onClick={completeHandle} className="btn btn-complete">
complete
</button>
</div>
</div>
);
};
export default List;
Input.jsx
import React from 'react';
const Input = ({ onSubmit, onChange, inputText }) => {
return (
<form onSubmit={onSubmit}>
<input
onChange={onChange}
value={inputText}
type="text"
name="myInput"
autoComplete="off"
/>
</form>
);
};
export default Input;

React close modal on click outside

I have created a basic modal using react without any library and it works perfectly, now when I click outside of the modal, I want to close the modal.
here is the CodeSandbox live preview
my index.js:
import React from "react";
import ReactDOM from "react-dom";
import "./styles.css";
class App extends React.Component {
constructor() {
super();
this.state = {
showModal: false
};
}
handleClick = () => {
this.setState(prevState => ({
showModal: !prevState.showModal
}));
};
render() {
return (
<>
<button onClick={this.handleClick}>Open Modal</button>
{this.state.showModal && (
<div className="modal">
I'm a modal!
<button onClick={() => this.handleClick()}>close modal</button>
</div>
)}
</>
);
}
}
ReactDOM.render(<App />, document.getElementById("root"));
The easiest way to get this is to call the closeModal function in the wrapper and stop propagation in the actual modal
For example
<ModalWrapper onClick={closeModal} >
<InnerModal onClick={e => e.stopPropagation()} />
</ModalWrapper>
Without using ref, it would be a little tricky
Watch this CodeSandBox
Or
import React from "react";
import ReactDOM from "react-dom";
import "./styles.css";
class App extends React.Component {
constructor() {
super();
this.state = {
showModal: false
};
}
handleClick = () => {
if (!this.state.showModal) {
document.addEventListener("click", this.handleOutsideClick, false);
} else {
document.removeEventListener("click", this.handleOutsideClick, false);
}
this.setState(prevState => ({
showModal: !prevState.showModal
}));
};
handleOutsideClick = e => {
if (!this.node.contains(e.target)) this.handleClick();
};
render() {
return (
<div
ref={node => {
this.node = node;
}}
>
<button onClick={this.handleClick}>Open Modal</button>
{this.state.showModal && (
<div className="modal">
I'm a modal!
<button onClick={() => this.handleClick()}>close modal</button>
</div>
)}
</div>
);
}
}
ReactDOM.render(<App />, document.getElementById("root"));
You can do it by creating a div for the modal backdrop which sits adjacent to the modal body. Make it cover the whole screen using position absolute and 100% height and width values.
That way the modal body is sitting over the backdrop. If you click on the modal body nothing happens because the backdrop is not receiving the click event. But if you click on the backdrop, you can handle the click event and close the modal.
The key thing is that the modal backdrop does not wrap the modal body but sits next to it. If it wraps the body then any click on the backdrop or the body will close the modal.
const {useState} = React;
const Modal = () => {
const [showModal,setShowModal] = useState(false)
return (
<React.Fragment>
<button onClick={ () => setShowModal(true) }>Open Modal</button>
{ showModal && (
<React.Fragment>
<div className='modal-backdrop' onClick={() => setShowModal(false)}></div>
<div className="modal">
<div>I'm a modal!</div>
<button onClick={() => setShowModal(false)}>close modal</button>
</div>
</React.Fragment>
)}
</React.Fragment>
);
}
ReactDOM.render(
<Modal />,
document.getElementById("react")
);
.modal-backdrop {
position: absolute;
top: 0;
left: 0;
background: #252424cc;
height: 100%;
width: 100vw;
}
.modal {
position: relative;
width: 70%;
background-color: white;
border-radius: 10px;
padding: 20px;
margin:20px auto;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.1/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.1/umd/react-dom.production.min.js"></script>
<div id="react"></div>
Please see the attached Codesandbox for a working example.
You were almost there. Firstly, you need to do a callback function in your handleClick() that will add a closeMenu method to the document:
handleClick = event => {
event.preventDefault();
this.setState({ showModal: true }, () => {
document.addEventListener("click", this.closeMenu);
});
};
And then toggle the state inside closeMenu():
closeMenu = () => {
this.setState({ menuOpen: false }, () => {
document.removeEventListener('click', this.closeMenu);
});
}
Any time you click outside of the component, then it'll close it. :)
This worked for me:
const [showModal, setShowModal] = React.useState(false)
React.useEffect(() => {
document.body.addEventListener('click', () => {
setShowModal(false)
})
})
return <>
<Modal
style={{ display: showModal ? 'block' : 'none'}}
onClick={(e) => e.stopPropagation()}
/>
<button onClick={(e) => {
e.stopPropagation()
setShowModal(true)
}}>Show Modal</button>
</>
This works for me:
Need to use e.stopPropagation to prevent loop
handleClick = e => {
if (this.state.showModal) {
this.closeModal();
return;
}
this.setState({ showModal: true });
e.stopPropagation();
document.addEventListener("click", this.closeModal);
};
then:
closeModal = () => {
this.setState({ showModal: false });
document.removeEventListener("click", this.closeModal);
};
Hope will help
This is how I solved it:
BTW, I"m a junior dev, so check it, GL.
In index.html:
<div id="root"></div>
<div id="modal-root"></div>
In index.js:
ReactDOM.render(
<React.StrictMode>
<ModalBase />
</React.StrictMode>,
document.getElementById("modal-root")
);
In the App.js:
const [showModal, setShowModal] = useState(false);
{showModal && (
<ModalBase setShowModal={setShowModal}>
{/*Your modal goes here*/}
<YourModal setShowModal={setShowModal} />
</ModalBase>
In the modal container:
import React, { useEffect, useRef, useState } from "react";
import ReactDOM from "react-dom";
const modalRoot: HTMLElement | null = document.getElementById("modal-root");
const Modal: React.FC<{
children: React.ReactNode;
setShowModal: React.Dispatch<boolean>;
}> = ({ children, setShowModal }) => {
const [el] = useState(document.createElement("div"));
const outClick = useRef(el);
useEffect(() => {
const handleOutsideClick = (
e: React.MouseEvent<HTMLDivElement, MouseEvent> | MouseEvent
) => {
const { current } = outClick;
console.log(current.childNodes[0], e.target);
if (current.childNodes[0] === e.target) {
setShowModal(false);
}
};
if (modalRoot) {
modalRoot.appendChild(el);
outClick.current?.addEventListener(
"click",
(e) => handleOutsideClick(e),
false
);
}
return () => {
if (modalRoot) {
modalRoot.removeChild(el);
el.removeEventListener("click", (e) => handleOutsideClick(e), false);
}
};
}, [el, setShowModal]);
return ReactDOM.createPortal(children, el);
};
export default Modal;
Use the following onClick method,
<div className='modal-backdrop' onClick={(e) => {
if (e.target.className === 'modal-backdrop') {
setShowModal(false)
}
}}></div>
<div className="modal">
<div>I'm a modal!</div>
<button onClick={() => setShowModal(false)}>close modal</button>
</div>
</div>
.modal-backdrop {
position: absolute;
top: 0;
left: 0;
background: #252424cc;
height: 100%;
width: 100vw;
}
You can check the event.target.className if it's contain the parent class you can close the Modal as below, in case you clicked inside the popup div it will not closed:
handleClick = () => {
if (e.target.className === "PARENT_CLASS") {
this.setState(prevState => ({
showModal: false
}));
}
// You should use e.stopPropagation to prevent looping
e.stopPropagation();
};
I will explain with functional components:
first create ref to get a reference to the modal element
import { useEffect, useState, useRef } from "react";
const [isModalOpen,setIsModalOpen]=useState(false)
const modalEl = useRef();
<div className="modal" ref={modalEl} >
I'm a modal!
<button onClick={() => this.handleClick()}>close modal</button>
</div>
second in useEffect create an event handler to detect an event outside the modal element. For this we need to implement capture phase on an element. (explained here: What is event bubbling and capturing? ). Basically, we are going to register an event handler so that when the browser detects any event, browser will start to look for the event handlers from the top parent HTML element and if it finds it, it will call it.
useEffect(() => {
const handler = (event) => {
if (!modalEl.current) {
return;
}
// if click was not inside of the element. "!" means not
// in other words, if click is outside the modal element
if (!modalEl.current.contains(event.target)) {
setIsModalOpen(false);
}
};
// the key is using the `true` option
// `true` will enable the `capture` phase of event handling by browser
document.addEventListener("click", handler, true);
return () => {
document.removeEventListener("click", handler);
};
}, []);

How can i pass props through methods inside components?

i have a react component named "List" that renders smaller components "Post" using a button through method "Addpost()" that takes 2 props from the input form. I have saved the input in 2 varables but i don't know how to pass these props to the Addpost() method inside the return of List's render().
//=========== List component ==============
class List extends React.Component{
renderPost(title,content){
return(
<Post titolo={title} contenuto={content}/>
);
}
renderPost just render the Post component in a in the HTML
addPost(title,content){
title = document.getElementById("inputTitle").value;
content = document.getElementById("inputContent").value;
console.log(title, content)
this.renderPost(title,content);
}
addPost should take the input value and use renderPost to render the Post component with that title and content
render(){
return(
<div>
{this.renderPost("testTitle","testContent")}
<form>
Title:<br></br>
<input type="text" id="inputTitle"/><br></br>
Content:<br></br>
<input type="text" id="inputContent"/>
</form><br></br>
<button className="square"
how can i make this work? title and content are not defined
onClick={() =>
this.addPost(title,content)
Add Post!
</button>
</div>
);
}
}
//=========== Post component ==============
class Post extends React.Component {
render() {
return (
<li className="w3-padding-16">
<img src="/w3images/workshop.jpg" alt="Imagedf" className="w3-left w3-margin-right" />`enter code here`
<span className="w3-large">
{this.props.titolo}
</span><br></br>
<span>{this.props.contenuto}</span>
</li>
);
}
}
Basically, whenever you're dealing with forms and inputs, you would use refs.
App.js
import React from 'react';
import logo from './logo.svg';
import './App.css';
import PostList from './components/PostList'
import AddPostForm from './components/AddPostForm'
class App extends React.Component {
constructor(props) {
super(props)
this.state = {
posts: [] //state is handled here
}
this.addPost = this.addPost.bind(this)
}
addPost(title, content) {
let newPost = { title, content }
this.setState(({ posts }) => { return { posts: [...posts, newPost] } } )
}
render() {
const { posts } = this.state
return (
<div>
<AddPostForm onNewPost={this.addPost} /> //we pass addPost to the component
<br />
<PostList posts={posts} />
</div>
);
}
}
export default App;
Post.js
import React from 'react';
function Post({titolo, contenuto}) {
return (
<li className="w3-padding-16">
<img src="/w3images/workshop.jpg" alt="Imagedf" className="w3-left w3-margin-right" />`enter code here`
<span className="w3-large">
{titolo}
</span><br></br>
<span>{contenuto}</span>
</li>
);
}
export default Post
AddPostForm.js
import React from 'react';
const addPostForm = ({onNewPost = f => f}) => { //onNewPost method is passed by props from the parent
let _titleInput, _contentInput //these are our refs, see the docs for more information
const submit = (e) => {
e.preventDefault()
onNewPost(_titleInput.value, _contentInput.value) //here we call the addPost function that was passed to the component
_titleInput.value = '' //empty the inputs
_contentInput.value = ''
_titleInput.focus() //set focus
}
return (
<form onSubmit={submit}>
Title:<br></br>
<input type="text" ref={title => _titleInput = title} /><br></br>{/* Note the ref attribute */}
Content:<br></br>
<input type="text" ref={content => _contentInput = content} />
<button className="square">Add a new post</button>
</form>
)
}
export default addPostForm
PostList.js
import React from 'react';
import Post from './Post';
const PostList = ({ posts=[] }) => {
return (
<div className="post-list">
{
posts.map((post, index) =>
<Post key={index} titolo={post.title} contenuto={post.content} />
)
}
</div>
)
}
export default PostList
And the result:
edit
renderPost just render the Post component in a in the HTML
state = { inputTitle: '', inputContent: '' }
addPost(title,content){
title = document.getElementById("inputTitle").value;
content = document.getElementById("inputContent").value;
console.log(title, content)
this.renderPost(title,content);
}
addPost should take the input value and use renderPost to render the Post
component with that title and content
render(){
return(
<div>
{this.renderPost("testTitle","testContent")}
<form>
Title:<br></br>
<input type="text" value={this.inputTitle} onChnage={event => setState({ inputTitle: event.target.value }) }><br></br>
Content:<br></br>
<input type="text" value={this.inputContent} onChnage={event => setState({ inputContent: event.target.value }) } />
</form><br></br>
<button className="square"
on click function
onClick={() =>
this.addPost(this.inputTitle,this.inputContent)
Add Post!
</button>
</div>
);
}
}

Event Handlers in React Stateless Components

Trying to figure out an optimal way to create event handlers in React stateless components. I could do something like this:
const myComponent = (props) => {
const myHandler = (e) => props.dispatch(something());
return (
<button onClick={myHandler}>Click Me</button>
);
}
The drawback here being that every time this component is rendered, a new "myHandler" function is created. Is there a better way to create event handlers in stateless components that can still access the component properties?
Applying handlers to elements in function components should generally just look like this:
const f = props => <button onClick={props.onClick}></button>
If you need to do anything much more complex it's a sign that either a) the component shouldn't be stateless (use a class, or hooks), or b) you should be creating the handler in an outer stateful container component.
As an aside, and undermining my first point slightly, unless the component is in a particularly intensively re-rendered part of the app there's no need to worry about creating arrow functions in render().
Using the new React hooks feature it could look something like this:
const HelloWorld = ({ dispatch }) => {
const handleClick = useCallback(() => {
dispatch(something())
})
return <button onClick={handleClick} />
}
useCallback creates a memoised function, meaning a new function will not be regenerated on each render cycle.
https://reactjs.org/docs/hooks-reference.html#usecallback
However, this is still at proposal stage.
How about this way :
const myHandler = (e,props) => props.dispatch(something());
const myComponent = (props) => {
return (
<button onClick={(e) => myHandler(e,props)}>Click Me</button>
);
}
If the handler relies on properties that change, you will have to create the handler each time since you lack a stateful instance on which to cache it. Another alternative which may work would be to memoize the handler based on the input props.
Couple implementation options
lodash._memoize
R.memoize
fast-memoize
Here is my simple favorite products list implemented with react and redux writing in typescript. You can pass all arguments you need in the custom handler and return a new EventHandler which accepts origin event argument. It's MouseEvent in this example.
Isolated functions keep jsx cleaner and prevent from breaking several linting rules. Such as jsx-no-bind, jsx-no-lambda.
import * as React from 'react';
import { DispatchProp, Dispatch, connect } from 'react-redux';
import { removeFavorite } from './../../actions/favorite';
interface ListItemProps {
prod: Product;
handleRemoveFavoriteClick: React.EventHandler<React.MouseEvent<HTMLButtonElement>>;
}
const ListItem: React.StatelessComponent<ListItemProps> = (props) => {
const {
prod,
handleRemoveFavoriteClick
} = props;
return (
<li>
<a href={prod.url} target="_blank">
{prod.title}
</a>
<button type="button" onClick={handleRemoveFavoriteClick}>×</button>
</li>
);
};
const handleRemoveFavoriteClick = (prod: Product, dispatch: Dispatch<any>) =>
(e: React.MouseEvent<HTMLButtonElement>) => {
e.preventDefault();
dispatch(removeFavorite(prod));
};
interface FavoriteListProps {
prods: Product[];
}
const FavoriteList: React.StatelessComponent<FavoriteListProps & DispatchProp<any>> = (props) => {
const {
prods,
dispatch
} = props;
return (
<ul>
{prods.map((prod, index) => <ListItem prod={prod} key={index} handleRemoveFavoriteClick={handleRemoveFavoriteClick(prod, dispatch)} />)}
</ul>
);
};
export default connect()(FavoriteList);
Here is the javascript snippet if you are not familiar with typescript:
import * as React from 'react';
import { DispatchProp, Dispatch, connect } from 'react-redux';
import { removeFavorite } from './../../actions/favorite';
const ListItem = (props) => {
const {
prod,
handleRemoveFavoriteClick
} = props;
return (
<li>
<a href={prod.url} target="_blank">
{prod.title}
</a>
<button type="button" onClick={handleRemoveFavoriteClick}>×</button>
</li>
);
};
const handleRemoveFavoriteClick = (prod, dispatch) =>
(e) => {
e.preventDefault();
dispatch(removeFavorite(prod));
};
const FavoriteList = (props) => {
const {
prods,
dispatch
} = props;
return (
<ul>
{prods.map((prod, index) => <ListItem prod={prod} key={index} handleRemoveFavoriteClick={handleRemoveFavoriteClick(prod, dispatch)} />)}
</ul>
);
};
export default connect()(FavoriteList);
solution one mapPropsToHandler and event.target.
functions are objects in js so its possible to attach them properties.
function onChange() { console.log(onChange.list) }
function Input(props) {
onChange.list = props.list;
return <input onChange={onChange}/>
}
this function only bind once a property to a function.
export function mapPropsToHandler(handler, props) {
for (let property in props) {
if (props.hasOwnProperty(property)) {
if(!handler.hasOwnProperty(property)) {
handler[property] = props[property];
}
}
}
}
I do get my props just like this.
export function InputCell({query_name, search, loader}) {
mapPropsToHandler(onChange, {list, query_name, search, loader});
return (
<input onChange={onChange}/>
);
}
function onChange() {
let {query_name, search, loader} = onChange;
console.log(search)
}
this example combined both event.target and mapPropsToHandler. its better to attach functions to handlers only not numbers or strings. number and strings could be passed with help of DOM attribute like
<select data-id={id}/>
rather than mapPropsToHandler
import React, {PropTypes} from "react";
import swagger from "../../../swagger/index";
import {sync} from "../../../functions/sync";
import {getToken} from "../../../redux/helpers";
import {mapPropsToHandler} from "../../../functions/mapPropsToHandler";
function edit(event) {
let {translator} = edit;
const id = event.target.attributes.getNamedItem('data-id').value;
sync(function*() {
yield (new swagger.BillingApi())
.billingListStatusIdPut(id, getToken(), {
payloadData: {"admin_status": translator(event.target.value)}
});
});
}
export default function ChangeBillingStatus({translator, status, id}) {
mapPropsToHandler(edit, {translator});
return (
<select key={Math.random()} className="form-control input-sm" name="status" defaultValue={status}
onChange={edit} data-id={id}>
<option data-tokens="accepted" value="accepted">{translator('accepted')}</option>
<option data-tokens="pending" value="pending">{translator('pending')}</option>
<option data-tokens="rejected" value="rejected">{translator('rejected')}</option>
</select>
)
}
solution two. event delegation
see solution one. we can remove event handler from input and put it to its parent that holds other inputs too and by the help delegation technique we can be use event.traget and mapPropsToHandler function again.
Like for a stateless component, just add a function -
function addName(){
console.log("name is added")
}
and it is called in the return as onChange={addName}
If you only have a few functions in you props that you are worried about you can do this:
let _dispatch = () => {};
const myHandler = (e) => _dispatch(something());
const myComponent = (props) => {
if (!_dispatch)
_dispatch = props.dispatch;
return (
<button onClick={myHandler}>Click Me</button>
);
}
If it gets much more complicated, I usually just go back to having a class component.
After continuous effort finally worked for me.
//..src/components/atoms/TestForm/index.tsx
import * as React from 'react';
export interface TestProps {
name?: string;
}
export interface TestFormProps {
model: TestProps;
inputTextType?:string;
errorCommon?: string;
onInputTextChange: React.ChangeEventHandler<HTMLInputElement>;
onInputButtonClick: React.MouseEventHandler<HTMLInputElement>;
onButtonClick: React.MouseEventHandler<HTMLButtonElement>;
}
export const TestForm: React.SFC<TestFormProps> = (props) => {
const {model, inputTextType, onInputTextChange, onInputButtonClick, onButtonClick, errorCommon} = props;
return (
<div>
<form>
<table>
<tr>
<td>
<div className="alert alert-danger">{errorCommon}</div>
</td>
</tr>
<tr>
<td>
<input
name="name"
type={inputTextType}
className="form-control"
value={model.name}
onChange={onInputTextChange}/>
</td>
</tr>
<tr>
<td>
<input
type="button"
className="form-control"
value="Input Button Click"
onClick={onInputButtonClick} />
</td>
</tr>
<tr>
<td>
<button
type="submit"
value='Click'
className="btn btn-primary"
onClick={onButtonClick}>
Button Click
</button>
</td>
</tr>
</table>
</form>
</div>
);
}
TestForm.defaultProps ={
inputTextType: "text"
}
//========================================================//
//..src/components/atoms/index.tsx
export * from './TestForm';
//========================================================//
//../src/components/testpage/index.tsx
import * as React from 'react';
import { TestForm, TestProps } from '#c2/component-library';
export default class extends React.Component<{}, {model: TestProps, errorCommon: string}> {
state = {
model: {
name: ""
},
errorCommon: ""
};
onInputTextChange = (event: React.ChangeEvent<HTMLInputElement>) => {
const field = event.target.name;
const model = this.state.model;
model[field] = event.target.value;
return this.setState({model: model});
};
onInputButtonClick = (event: React.MouseEvent<HTMLInputElement>) => {
event.preventDefault();
if(this.validation())
{
alert("Hello "+ this.state.model.name + " from InputButtonClick.");
}
};
onButtonClick = (event: React.MouseEvent<HTMLButtonElement>) => {
event.preventDefault();
if(this.validation())
{
alert("Hello "+ this.state.model.name+ " from ButtonClick.");
}
};
validation = () => {
this.setState({
errorCommon: ""
});
var errorCommonMsg = "";
if(!this.state.model.name || !this.state.model.name.length) {
errorCommonMsg+= "Name: *";
}
if(errorCommonMsg.length){
this.setState({ errorCommon: errorCommonMsg });
return false;
}
return true;
};
render() {
return (
<TestForm model={this.state.model}
onInputTextChange={this.onInputTextChange}
onInputButtonClick={this.onInputButtonClick}
onButtonClick={this.onButtonClick}
errorCommon={this.state.errorCommon} />
);
}
}
//========================================================//
//../src/components/home2/index.tsx
import * as React from 'react';
import TestPage from '../TestPage/index';
export const Home2: React.SFC = () => (
<div>
<h1>Home Page Test</h1>
<TestPage />
</div>
);
Note: for text box filed binding "name" attribute & "property name" (e.g: model.name) should be same then only "onInputTextChange" will work.
"onInputTextChange" logic can be modified by your code.
How about something like this:
let __memo = null;
const myHandler = props => {
if (!__memo) __memo = e => props.dispatch(something());
return __memo;
}
const myComponent = props => {
return (
<button onClick={myHandler(props)}>Click Me</button>
);
}
but really this is overkill if you don't need to pass the onClick to lower/inner components, like in the example.

Categories

Resources