I'm trying to create an editable table that will turn a specific cell in to an <input> once it's clicked and then will run the handleSubmit() method once the user presses return.
Below is an example of a <td> cell using an onClick event to run handleClick() method and turn it's <td></td> in to <form><input></input></form>.
<td onClick={ e => this.handleClick(e)} style={{padding:'5px'}} key={cellID} id={cellID}>{frame.rows[i][idx]}</td>
handleClick(e:React.MouseEvent<HTMLTableDataCellElement, MouseEvent>) {
if(this.state.editing == false){
let form = `<form onSubmit=${ (e:any) => {this.handleSubmit(e)} } ><input type="text" value=${e.currentTarget.innerText} className="input-small gf-form-input width-auto"/></form>`
e.currentTarget.innerHTML = form;
}
this.setState({editing: true})
}
handleSubmit(e){
e.preventDefault()
}
Using e.preventDefault() does not seem to work in this instance. Every time i press return after changing the text, the page refreshes. How do i stop the page from refreshing in this instance?
I'm guessing you're wanting to achieve something where you can editing columns, modify or abandon changes, and then update things as needed.
This example is with local state, but you could still do it with fetching data.
Click the "Run code snippet" below to see a working example.
// main.js
const { useState } = React;
const App = () => {
// State
const [data, setData] = useState([{ id: 1, name: 'John', editing: false }, { id: 2, name: 'Kevin', editing: false }]);
// Functions
const onSubmitForm = index => event => {
// To prevent form submission
event.preventDefault();
// To prevent td onClick
event.stopPropagation();
const newData = [...data];
newData[index].name = newData[index].temp;
newData[index].editing = false;
delete newData[index].temp;
setData(newData);
}
const onClickToggleEditing = index => event => {
// To prevent td onClick and button conflicting with each other for toggling back on
event.stopPropagation();
const newData = [...data];
newData[index].editing = !newData[index].editing;
newData[index].temp = newData[index].name;
setData(newData);
}
const onInputChange = index => event => {
const newData = [...data];
newData[index].temp = event.target.value;
setData(newData);
}
// Render
// This basically like having its own component
const editing = ( data, index, onChange, onSubmit, onCancel) => {
const onKeyUp = index => event => {
if (event.key === 'Escape') {
onCancel(index)(event);
}
}
return <form onSubmit={onSubmit(index)}><input onKeyUp={onKeyUp(index)} onClick={e => e.stopPropagation()} type="text" value={data.temp} placeholder="Enter text" onChange={onChange(index)} /><button onClick={onSubmit(index)} type="submit">Save</button><button type="button" onClick={onCancel(index)}>Cancel</button></form>
}
return <main>
<h1>Table Editing</h1>
<p><small>Click to edit cell for <b>Name</b>.</small></p>
<table>
<thead>
<tr>
<th>ID</th>
<th>Name</th>
</tr>
</thead>
{data && data.length > 0 && <tbody>
{data.map((i, k) => <tr key={`row-${k}`}>
<td>{i.id}</td>
<td className="editable" onClick={onClickToggleEditing(k)}>{i.editing ? editing(i, k, onInputChange, onSubmitForm, onClickToggleEditing) : i.name}</td>
</tr>)}
</tbody>}
</table>
<hr />
<p><b>Data Manipulation:</b></p>
<pre><code>{JSON.stringify(data, null, '\t')}</code></pre>
</main>
}
ReactDOM.render(<App />, document.querySelector('#root'));
body {
padding: 0;
margin: 0;
font-family: Arial,sans-serif;
}
main {
padding: 0 20px;
}
h1 {
font-size: 18px;
}
table {
width: 100%;
border-spacing: 0;
}
table tr td,
table tr th {
border: 1px solid #efefef;
height: 30px;
line-height: 30px;
text-align: left;
padding: 6px;
}
table tr th:first-child {
width: 100px;
}
.editable:hover {
background: #efefef;
cursor: pointer;
}
table input {
height: 30px;
line-height: 30px;
font-size: 14px;
padding: 0 6px;
margin-right: 6px;
}
table button {
height: 32px;
border: none;
background: red;
color: white;
font-size: 14px;
padding: 0 10px;
border-radius: 4px;
margin-right: 5px;
cursor: pointer;
}
table button[type=submit] {
height: 32px;
border: none;
background: green;
color: white;
font-size: 14px;
padding: 0 10px;
border-radius: 4px;
}
hr {
border: none;
height: 1px;
background: #999;
margin: 20px 0;
}
pre {
background: #efefef;
padding: 6px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>
<div id="root"></div>
There are a few issues in your code. I'd better fix them, rather than trying to fix the issue with the form submission. And once it is done, you won't have to fix the issue with the form - there simply won't be any.
First, let's take a look into your editable cell:
<td onClick={ e => this.handleClick(e)} style={{padding:'5px'}} key={cellID} id={cellID}>{frame.rows[i][idx]}</td>
This element should be rendered differently, based on some state. We can achieve this easily with React:
// JSX snippet
<td onClick={ e => this.handleClick(e)}
style={{padding:'5px'}}
key={cellID} id={cellID}>
{this.state.editing ? <Input ... /> : <Label ... />}
</td>
I do not provide all the code, because I believe the components are self-explainable (and you are welcome to name them as you'd like to, I give them very simple names to make their aim clear).
<Input /> encapsulates everything related to editing logic
<Label /> simply renders a text or whatever you need (probably frame.rows[i][idx])
... means that they will most probably have some values/handlers passed as props
In your code, you have this:
let form = `<form onSubmit=${ (e:any) => {this.handleSubmit(e)} } ><input type="text" value=${e.currentTarget.innerText} className="input-small gf-form-input width-auto"/></form>`
I believe it deserves to be a separate component with its own state and logic (e.g. submit). In fact, this is what <Input ... /> is in my example. And if you make it as a separate component - the following code will work (because it will be a part of that separate component):
handleSubmit(e) {
e.preventDefault()
}
Finally, avoid doing something like that:
e.currentTarget.innerHTML = form;
Reconsider your approach and you simply won't need to do something like that.
hi you can use it like below:
1- i assume you have a return button like below so you can submit in return not using form submit event:
_handleReturn(){
let val = document.getElementById("your input id");
//you can post above text to server if you want
//Do Somthing
}
<button id="btn_return" onClick={this._handleReturn} />
2- i don't see where you trigger handleSubmit, but submitting form cause refresh, you should use ajax if you don't want.
Related
I made 5 blocks and want to make the letters on each block thick when the mouse is hover. I made isHover state and changed the thickness of the writing according to the state, but the problem is that the thickness of all five changes. I think I can solve it by using conditional rendering, but I don't know how to use it. Of course, it can be implemented only with css, but I want to implement it with conditional rendering because I am practicing the code concisely.
import "./styles.css";
import styled from "styled-components";
import { useState } from "react";
export default function App() {
const array = [
{ id: "1", title: "ABC" },
{ id: "2", title: "DEF" },
{ id: "3", title: "GHI" },
{ id: "4", title: "JKL" },
{ id: "5", title: "MNO" }
];
const [isHover, setIsHover] = useState(false);
return (
<Head isHover={isHover}>
<div className="header">
{array.map((content, id) => {
return (
<div
className="header__title"
onMouseEnter={() => {
setIsHover(true);
}}
onMouseLeave={() => {
setIsHover(false);
}}
>
{content.title}
</div>
);
})}
</div>
</Head>
);
}
const Head = styled.div`
position: fixed;
top: 0;
bottom: 0;
left: 0;
right: 0;
.header {
display: inline-flex;
border: 1px solid black;
box-sizing: border-box;
}
.header__title {
border: 1px solid red;
padding: 5px 10px;
font-weight: ${(props) => (props.isHover ? "700" : "400")};
}
`;
codesandbox
https://codesandbox.io/s/aged-cherry-53pr2r?file=/src/App.js:0-1170
The problem is that you are using the same state for all the 5 blocks. There are multiple approaches you could take to solve this problem.
1. Multiple states
You could create 5 different isHover<N> states (maybe a single one, but as an array)
2. Component extraction
You could just extract out a component for each entry in array and do state management in that component.
function App() {
const array = [...];
return (
<Head>
<div className="header">
{array.map((content, id) => (
<HeaderTitle key={content.id} content={content} />
)}
</div>
</Head>
);
}
function HeaderTitle({ content }) {
const [isHover, setIsHover] = useState(false);
return (
<StyledHeaderTitle
isHover={isHover}
onMouseEnter={() => setIsHover(true)}
onMouseLeave={() => setIsHover(false)}
>
{content.title}
</StyledHeaderTitle>
);
}
const StyledHeaderTitle = styled.div`
font-weight: ${(props) => (props.isHover ? "700" : "400")};
`
3. Using style prop
Directly apply the font weight using the style prop (An extension to approach 2)
function HeaderTitle({ content }) {
const [isHover, setIsHover] = useState(false);
return (
<StyledHeaderTitle
onMouseEnter={() => setIsHover(true)}
onMouseLeave={() => setIsHover(false)}
style={{ fontWeight: isHover ? "700" : "400" }}
>
{content.title}
</StyledHeaderTitle>
);
}
4. CSS
CSS already allows you to track hover states over different elements and you don't need to manually track it in javascript.
.header__title {
border: 1px solid red;
padding: 5px 10px;
font-weight: 400;
&:hover {
font-weight: 700;
}
}
There's no need to use React state and event listeners here, you can do it all in CSS instead:
.header__title {
border: 1px solid red;
padding: 5px 10px;
font-weight: 400;
}
.header__title:hover {
font-weight: 700;
}
Just add this pseudo class and you're good to go
.header__title:hover {
font-weight: 700;
}
I am trying to create a to-do list app. The basic functionality includes adding and deleting. So when a user selects one or multiple items, a delete button will appear and it will be deleted. My problem is I am implementing a toggle state which when a user clicks on todo item, it will be strikethrough( A strikethrough text decoration will be added via CSS).
The problem arises when I add two items. When I click on the first item , it gets a strike through and when I delete it, the first one goes but the second item gets the strike through this time.
The running sample in codesandbox. Just try adding two items and delete the first one. The second one also gets a strike through.
I believe its because the toggle state value is being remembered.
This is the Content.js component
import "./styles/content.css";
import Individual from "./Individual";
import { useEffect, useState } from "react";
import { updateItem, markIncomplete } from "./action/action";
const Contents = (props) => {
const items = useSelector((state) => state.todoReducer.items);
const dispatch = useDispatch();
const handleClick = (e, isComplete, content, id) => {
// console.log(isComplete);
if (isComplete === false) {
//evaluates false to true
const newobj = {
isComplete: true,
content,
id
};
dispatch(updateItem(newobj));
props.deletebutton(true);
} else {
const falseobj = {
isComplete: false,
content,
id
};
dispatch(markIncomplete(falseobj));
}
};
useEffect(() => {
console.log("statechanging of contents");
});
return (
<div className="content-ui">
<div>
{items.map((vals) => (
<Individual
vals={vals}
deletebutton={props.deletebutton}
handleClick={handleClick}
/>
))}
</div>
</div>
);
};
export default Contents;
This is the individual.js which deals with toggle function
import { useDispatch, useSelector } from "react-redux";
import "./styles/content.css";
import { updateItem, markIncomplete } from "./action/action";
const Individual = (props) => {
console.log("child" + props.vals.isComplete);
const [toggle, isToggled] = useState(false);
const handleToggle = () => {
const mytoggle = !toggle;
isToggled(mytoggle);
};
return (
<div>
<div
className={toggle ? "toggled" : "card-elements"}
onMouseDown={handleToggle}
onClick={(e) => {
props.handleClick(
e,
props.vals.isComplete,
props.vals.content,
props.vals.id
);
handleToggle;
}}
>
{props.vals.content}
</div>
</div>
);
};
export default Individual;
Css of toggler
.toggled {
/* border: 1px solid rgb(94, 94, 94); */
box-shadow: rgba(0, 0, 0, 0.05) 0px 0px 0px 1px;
text-align: left;
box-sizing: border-box;
padding: 10px 3px 10px 7px;
margin-top: 4px;
margin-bottom: 8px;
border-radius: 5px;
background-color: white;
font-size: 12px;
font-family: "Roboto ", monospace;
text-decoration: line-through;
}
.card-elements {
/* border: 1px solid rgb(94, 94, 94); */
box-shadow: rgba(0, 0, 0, 0.05) 0px 0px 0px 1px;
text-align: left;
box-sizing: border-box;
padding: 10px 3px 10px 7px;
margin-top: 4px;
margin-bottom: 8px;
border-radius: 5px;
background-color: white;
font-size: 12px;
font-family: "Roboto ", monospace;
}
You need to add keys to your mapped items (there should also be a warning about this in the console).
Keys help React identify which items have changed, are added, or are removed.
As is stated from React's documents.
return (
<div className="content-ui">
<div>
{items.map((vals) => (
<Individual
key={vals.id} // <-- add unique key
vals={vals}
deletebutton={props.deletebutton}
handleClick={handleClick}
/>
))}
</div>
</div>
);
I am trying to make a "My Favorite Movies" list page where users can add and rate movies. This program should include:
1) a form where you can add to the list and rate it
2) a table of all the things you've added
3) delete button for each row of the table that lets you remove elements from the list (what i'm having trouble on)
Instead of deleting only one row, it deletes every appended movie/rating in the table. Also if you click anywhere else, it deletes everything as well.
4) bonus: sort feature, so i can sort entries in the table by the their title or their rating.
example here: rithm school example
$(function() {
$('#addMovieButton').click(function() {
var addToTitle = $('#title').val();
$('#tableTitle').append('<tr><td>' + addToTitle + '</td></tr>');
var addToRating = $("#rating").val();
$('#tableRating').append('<tr><td>' + addToRating + '</td></tr>');
$('#tableDelete').append('<tr><td><input type="button" value="Delete All"</tr></td>');
$('#tableRow').on('click', function() {
$('#tableTitle').last().children().remove();
$('#tableRating').last().children().remove();
$('#tableDelete').last().children().remove();
});
});
});
h1 {
text-align: center;
}
table {
width: 100%;
border-radius: 10px;
}
table,
td {
border: 1px solid black;
padding: 15px;
}
th {
height: 50px;
text-align: center;
}
td {
text-align: center;
}
body {
font-family: helvetica;
}
form {
text-align: center;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<form>
<label><b>Title</b></label>
<input id="title" type="text" value="Movie Title">
<label><b>Rating</b></label>
<input id="rating" type="text" value="Rate The Movie from 0 to 10">
<button type='button' id="addMovieButton">Add Movie</button>
</form>
<table>
<tr id="tableRow">
<th id="tableTitle">Title</th>
<th id="tableRating">Rating</th>
<th id="tableDelete">Delete</th>
</tr>
</table>
<table> Structure
The structure of the appended "row" are not valid HTML. A <table> will have at least one <tbody>. If the user doesn't add it the browser will. Although most methods, function, and properties will treat the <table> as the direct parent of <tr>, there are some advantages to targeting <tbody> instead. If there's a <thead> then targeting the <tbody> can free you from extra steps trying to avoid the <th>.
Keep these rules in mind when structuring a <table>
<tbody> can only have <tr> as children (direct descendants)
<tr> can only have <td> and <th> as children
<td> and <th> can have anything as descendants.
Make sure rows are structured like so:
<tr><td</td>...<td></td></tr>
Add row to <table> or <tbody>
Demo
The following demo has detailed comments within the HTML, and CSS, as well as step by step details commented in the JavaScript
$(function() {
/*
Bind the <form> to the 'submit', 'click', and 'change' events.
Pass the Event Object thru
*/
$('form').on('submit click change', function(event) {
// Reference the type of event
let eType = event.type;
// if the 'submit' event was triggered...
if (eType === 'submit') {
// Stop the <form> from sending data to a server and resetting
event.preventDefault();
// Get the values of the <input>
let name = $('.title').val();
let rate = $('.rating').val();
// Declare a htmlString using a Template Literal
const row = `
<tr><td>${name}</td>
<td>${rate}</td>
<td><input class='sel' type='checkbox'>
</td></tr>`;
// Render the htmlString as the last child of the <tbody>
$('.data').append(row);
// Reset <form>
$(this).trigger('reset');
// ...otherwise if the 'click' event triggered...
} else if (eType === 'click') {
// ...and the clicked tag has class 'del'...
if ($(event.target).hasClass('del')) {
/*
Collect all checked <input class='sel'>
then on .each() one...
*/
$('.sel:checked').each(function(i) {
/*
Get the ancestor <tr> of the current .sel
and remove it
*/
$(this).closest('tr').remove();
});
// Reset the <form>
$('form').trigger('reset');
}
// ...otherwise if the 'change' event was triggered...
} else if (eType === 'change') {
// ...and the changed tag id is 'all'...
if (event.target.id === 'all') {
// Check if #all is checked or not
let allChk = $('#all').is(':checked');
// Loop thru each() <input class='sel'>...
$('.sel').each(function(i) {
/*
and check current .sel if #all is checked
or uncheck current .sel if #all is NOT checked
*/
$(this).prop('checked', allChk);
});
}
}
// Stop any events from bubbling any further up the event chain
event.stopPropagation();
});
});
:root {
font: 400 3vw/1.2 Arial;
}
form {
text-align: center;
}
table {
width: 100%;
border-radius: 10px;
table-layout: fixed;
margin: 12px auto
}
table,
td {
border: 1px solid black;
padding: 15px;
}
th {
height: 30px;
width: 20%;
}
th:first-of-type {
width: 60%;
}
td {
text-align: center;
}
button,
input,
label {
display: inline-block;
font-size: initial;
}
.all {
font-weight: 400;
padding: 3px 6px;
border: 1.5px inset rgba(0, 28, 255, 0.3);
margin-top: 3px;
}
.all::after {
content: 'Selected'
}
/*
When input#all is :checked the label.all that follows
#all will change
the content of its pseudo-element from 'Selected' to 'All'
*/
#all:checked+.all::after {
content: 'All'
}
button:hover {
cursor: pointer;
outline: 3px outset rgba(0, 28, 255, 0.4);
color: rgba(0, 28, 255, 0.6);
}
.all:hover {
cursor: pointer;
color: rgba(0, 28, 255, 0.8);
background: rgba(0, 28, 255, 0.2);
}
.rating {
text-align: right;
width: 4ch;
}
.title {
padding-left: 5px;
width: 27ch;
}
/*
The checkbox #all is not visible to user but is accessible through the label.all
which it is synced with (see comments in HTML
*/
#all {
display: none
}
<form>
<label>Title</label>
<!--
The [required] attribute enables built-in form validation
If the submit event is triggered
and either <input> is blank, the submit event is interrupted and
a tooltip will notify
user that the <input> cannot be empty
-->
<input class="title" type="text" placeholder="Pulp Fiction" required>
<label>Rating</label>
<!-- See previous comment -->
<input class="rating" type="number" min='0' max='10' placeholder="10" required>
<!--
<button type='submit'> or <input type='submit'>
or <button> within a <form> will trigger a submit event by default
-->
<button>Add Movie</button>
<table>
<thead>
<tr>
<th>Title</th>
<th>Rating</th>
<th>
<button class='del' type='button'>Remove</button>
<!--
A <label> and a form control (ie <input>, <textarea>, <select>, etc) can be synced by
matching the [for] attribute value to the form controls #id:
1. <label for='XXX'>
2. <input id='XXX'>
When synced, clicking one will remotely click the other
-->
<input id='all' type='checkbox'>
<label class='all' for='all'></label></th>
</tr>
</thead>
<!--
See post on <table> structure
-->
<tbody class='data'></tbody>
</table>
</form>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
you've put all titles in a parent, and all rating in another parent ,and all delete buttons in another one . you should place the information about each row in a parent and then you can delete by row easily.
(also you can add td,tbody it's just sample to showing the way)
$('#addMovieButton').click(function () {
var addToTitle = $('#title').val();
var addToRating = $("#rating").val();
$('#table').append('<tr><th>' + addToTitle + '</th><th>' + addToRating + '</th><th><input type="button" value="Delete All" class="tableDelete"></th></tr>');
$('.tableDelete').click(function () {
$(this).parents('tr').remove();
});
});
h1 {
text-align: center;
}
table {
width: 100%;
border-radius: 10px;
}
table,
td {
border: 1px solid black;
padding: 15px;
}
th {
height: 50px;
text-align: center;
}
td {
text-align: center;
}
body {
font-family: helvetica;
}
form {
text-align: center;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<form>
<label><b>Title</b></label>
<input id="title" type="text" value="Movie Title">
<label><b>Rating</b></label>
<input id="rating" type="text" value="Rate The Movie from 0 to 10">
<button type='button' id="addMovieButton">Add Movie</button>
</form>
<table id="table">
</table>
I created a Dropdown that when I click outside of it the dropdown disappears. I used a click event listener to determine if I clicked outside the dropdown.
After a few clicks, the page slows down and crashes. Perhaps the state is being rendered in a loop or too many events are being fired at once?
How do I fix this?
Also, is there a more React way to determine if I clicked outside an element? (Instead of using a document.body event listener)
Here is the codepen:
const items = [
{
value: 'User1'
},
{
value: 'User2'
},
{
value: 'User3'
},
{
value: 'User4'
},
{
value: 'User5'
}
];
class Dropdown extends React.Component {
state = {
isActive: false,
}
render() {
const { isActive } = this.state;
document.addEventListener('click', (evt) => {
if (evt.target.closest('#dropdownContent')) {
//console.warn('clicked inside target do nothing');
return;
}
if (evt.target.closest('#dropdownHeader')) {
//console.warn('clicked the header toggle');
this.setState({isActive: !isActive});
}
//console.warn('clicked outside target');
if (isActive) {
this.setState({isActive: false});
}
});
return (
<div id="container">
<div id="dropdownHeader">select option</div>
{isActive && (
<div id="dropdownContent">
{items.map((item) => (
<div id="item" key={item.value}>
{item.value}
</div>
))}
</div>
)}
</div>
);
};
}
ReactDOM.render(
<Dropdown items={items} />,
document.getElementById('root')
);
#container {
position: relative;
height: 250px;
border: 1px solid black;
}
#dropdownHeader {
width: 100%;
max-width: 12em;
padding: 0.2em 0 0.2em 0.2em;
margin: 1em;
cursor: pointer;
box-shadow: 0 1px 4px 3px rgba(34, 36, 38, 0.15);
}
#dropdownContent {
display: flex;
flex-direction: column;
position: absolute;
top: 3em;
width: 100%;
max-width: 12em;
margin-left: 1em;
box-shadow: 0 1px 4px 0 rgba(34, 36, 38, 0.15);
padding: 0.2em;
}
#item {
font-size: 12px;
font-weight: 500;
padding: 0.75em 1em 0.75em 2em;
cursor: pointer;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<div id="root">
<!-- This element's contents will be replaced with your component. -->
</div>
There's a pretty simple explanation for what you're experiencing. :)
The way I was able to figure it out was the number of warnings that were showing up in the terminal every time I clicked somewhere was getting higher and higher, especially when the state changed.
The answer though is that since you were adding the event listener code in the render function, every time the code re-rendered it would add more and more event listeners slowing down your code.
Basically the solution is that you should move the adding of event listeners to componentDidMount so it's only run once.
Updated working javascript:
const items = [
{
value: 'User1'
},
{
value: 'User2'
},
{
value: 'User3'
},
{
value: 'User4'
},
{
value: 'User5'
}
];
class Dropdown extends React.Component {
state = {
isActive: false,
}
// added component did mount here
componentDidMount(){
const { isActive } = this.state;
document.addEventListener('click', (evt) => {
if (evt.target.closest('#dropdownContent')) {
console.warn('clicked inside target do nothing');
return;
}
if (evt.target.closest('#dropdownHeader')) {
console.warn('clicked the header toggle');
this.setState({isActive: !isActive});
}
console.warn('clicked outside target');
if (isActive) {
this.setState({isActive: false});
}
});
}
render() {
const { isActive } = this.state;
//removed event listener here
return (
<div id="container">
<div id="dropdownHeader">select option</div>
{isActive && (
<div id="dropdownContent">
{items.map((item) => (
<div id="item" key={item.value}>
{item.value}
</div>
))}
</div>
)}
</div>
);
};
}
ReactDOM.render(
<Dropdown items={items} />,
document.getElementById('root')
);
I want to have a custom directive I can attach to any <input> which will limit it to alpha chars only. Here's what I have so far:
https://jsfiddle.net/m473tpfu/
It works, however, the Vue.js docs state:
Apart from el, you should treat these arguments as read-only and never modify them.
So modifying vnode is a bad pattern. How can I achieve this differently?
You could do it like this: your directive has an input handler that checks whether forbidden chars are included, and if so, strips them out and issues a new input Event so that everything updates.
The usual issue of cursor moving to the end of the field when you rewrite its value applies.
Vue.directive('limiter', {
inserted(el, binding, vnode) {
el.addEventListener('input', event => {
const newValue = event.target.value;
const cleaned = newValue.replace(/[^A-Za-z]/g, '');
if (newValue !== cleaned) {
event.target.value = cleaned;
event.target.dispatchEvent(new Event('input'));
}
});
}
});
new Vue({
el: "#app",
data() {
return {
name: '',
};
}
})
body {
background: #20262E;
padding: 20px;
font-family: Helvetica;
}
#app {
background: #fff;
border-radius: 4px;
padding: 20px;
transition: all 0.2s;
}
li {
margin: 8px 0;
}
h2 {
font-weight: bold;
margin-bottom: 15px;
}
del {
color: rgba(0, 0, 0, 0.3);
}
<script src="//unpkg.com/vue#latest/dist/vue.js"></script>
<div id="app">
<input v-model="name" v-limiter />
<div>
Your name is {{ name }}.
</div>
</div>