I work in a big Vue project, I was trying to switch classes => couldn't so tried something easier ( testing purposes only) => change El style with.style.
So, the idea here is to switch the form backgroundColor when focus state is active on certain input... I was able to switch that same Input bgColor with this eventListener but never the form. Here's the code:
<template>
<form #submit.prevent ref="formElement" class="w-full form">
<h1
class="mt-5 mb-3 text-2xl text-primary font-bold capitalize md:mt-0 md:mb-5 md:text-3xl"
>
{{ t('fullNameForm.title') }}
</h1>
<p class="mb-5 text-sm text-secondary md:mb-10">
{{ t('fullNameForm.description') }}
</p>
<div class="flex flex-col gap-y-4">
<Input
id="firstName"
autocomplete="given-name"
:errors="v$.firstName.$errors"
v-model.trim="v$.firstName.$model"
>
{{ t('firstName') }}
</Input>
<Input
id="lastName"
autocomplete="family-name"
:errors="v$.lastName.$errors"
v-model.trim="v$.lastName.$model"
>
{{ t('lastName') }}
</Input>
</div>
<div
class="w-full h-24 px-4 py-6 absolute left-0 bottom-0 sm:static sm:px-0"
>
<Button
type="submit"
:loading="loading"
:disabled="v$.$invalid"
class="w-full h-12 capitalize"
#click="submit()"
>
{{ t('next') }}
</Button>
</div>
</form>
</template>
<script lang="ts">
import { defineComponent, ref, reactive, onMounted } from 'vue'
import { useStore } from 'vuex'
import { useI18n } from 'vue-i18n'
import useVuelidate from '#vuelidate/core'
import { required, minLength, maxLength } from '#vuelidate/validators'
import Input from '#/components/Input.vue'
import Button from '#/components/ButtonV2.vue'
export default defineComponent({
name: 'FullNameForm',
components: {
Input,
Button
},
emits: ['submit'],
setup(props, { emit }) {
const store = useStore()
const { t } = useI18n()
// focus events don't bubble, must use capture phase
// document.body.addEventListener(
// 'focus',
// e => {
// const target = e.target
// {
// document.body.classList.add('keyboard')
// }
// },
// true
// )
// document.body.addEventListener(
// 'blur',
// () => {
// document.body.classList.remove('keyboard')
// },
// true
// )
const formElement = ref<HTMLElement | null>(null)
const formEl = document.querySelector('.form') as HTMLElement | null
onMounted(() => {
window.addEventListener('keydown', event => {
if (event.key === 'Enter') submit()
})
const lastnameInput = document.querySelector(
'#lastName'
) as HTMLElement | null
if (lastnameInput || formEl) {
lastnameInput?.addEventListener('focus', () => {
lastnameInput.style.backgroundColor = 'red'
if (formElement.value && formEl && lastnameInput) {
formElement.value.style.backgroundColor = 'orange'
formElement.value.classList.add('keyboard')
formEl.style.backgroundColor = 'gray'
}
})
}
})
return {
t,
form,
v$,
loading,
submit,
formElement
}
}
})
</script>
<style scoped>
.form.keyboard {
background: rgb(81, 179, 146);
}
</style>
I discovered 2 things:
the second if never works
Any formEl or formElement in the first if doesn't work because it's a possible null Element, I already tried to switch operators => || to && => Nothing works, even the input stop reacting.`
Related
I´m having issues with creating my checkbox component(s).
Since I´m mapping over an object´s entries, where the keys are the sectiontitles and the values are arrays, filled with the checkable features.
I´m also using tailwind. I will let the code speek for itself I think :D
Checkboxes
Object structure, which im passing as a prop, down to the checkboxes component:
const features = {
Features: [
'Mintable',
'Burnable',
'Pausable',
'Permit',
'Votes',
'Flash Minting',
'Snapchots'
],
'Access Control': ['Ownable', 'Roles'],
Upgradeability: ['Transparent', 'UUPS']
};
Checkboxes component, which renders the checkbox component multiple times by mapping over the object (Ik the id is missing, but I dont know how to control the ID´s:
import React from 'react';
import { connect, useDispatch } from 'react-redux';
import Checkbox from '../../layout-blueprints/Checkbox';
import { featureCheckboxClick } from './Redux/actions';
const FeaturesComponent = ({ features }) => {
const dispatch = useDispatch();
return (
/* Card layout and heading */
<div className="bg-white p-4 rounded col-span-2 row-span-5">
{Object.entries(features).map(([key, value]) => {
return (
<div key={key}>
<h5 className="text-black m-t-4">{key}</h5>
<div className="border-bottom border-primary pb-2 mb-4 mr-20">
{/* Render feature checkboxes */}
{value.map((feature) => {
return (
<div className="flex justify-between m-2" key={feature}>
<div>
<Checkbox
onChange={() => dispatch(featureCheckboxClick(feature))}
/>
<span className="pl-2 text-lg">{feature}</span>
</div>
</div>
);
})}
</div>
</div>
);
})}
</div>
);
};
export default connect()(FeaturesComponent);
checkbox-component:
import React from 'react';
import { FontAwesomeIcon } from '#fortawesome/react-fontawesome';
import { faCheck } from '#fortawesome/free-solid-svg-icons';
const Checkbox = () => {
return (
<label htmlFor="check-box-1" className="cursor-pointer relative">
<input
type="checkbox"
className="check-box-1 appearance-none h-4.5 w-4.5 border-2 border-primary rounded-sm relative translate-y-0.5"
/>
<FontAwesomeIcon
icon={faCheck}
className="-translate-x-4 translate-y-0.5 h-4.5 w-4.5 text-sm absolute text-white text-opacity-0 transition check-1 duration-200"
/>
</label>
);
};
export default Checkbox;
css tailwind directives (should be readable, even if you never worked with tailwind):
#tailwind components;
#tailwind utilities;
.check-box-1:checked {
#apply bg-primary;
}
.check-box-1:checked ~ .check-1 {
#apply text-opacity-100;
}
That´s all the code. Like I said, I´d usually work with a useState array or so, but It´s kinda hard to control it, bc there is so much nesting in the mapping, or am I thinking too complicated?
I´m honestly clueless and am glad for any help I can get, cheers!
I hope this helps
import { NextPage } from "next"
import { useState } from "react"
import CheckboxList from "../../../components/learn22/CheckboxList"
const myFoodList = [
'김치',
'고구마',
'감자',
'피자',
'햄버거',
'라면',
'제육볶음',
'삼계탕',
'닭곰탕',
'추어탕'
]
const MyCheckList: NextPage = () => {
const [checkedFoods, setCheckedFoods] = useState<string[]>([])
const onMyFoodChecked = (checked : string[]) => {
setCheckedFoods(checked)
console.log(checked)
}
return(
<CheckboxList
checkedFoods ={checkedFoods}
onCheck = {onMyFoodChecked}
foods = {myFoodList}
/>
)
}
export default MyCheckList;
import { FunctionComponent } from "react";
interface Props {
checkedFoods ?: string[];
onCheck: (checked : string[]) => void;
foods ?: string[];
}
const CheckBoxList : FunctionComponent<Props> = ({
checkedFoods =[],
onCheck,
foods =[]
}) => {
return (<ol>{foods.map((food, idx)=>
<li key={food + '-' + idx}>
<input type='checkbox' checked={checkedFoods?.includes(food)}onChange={(e)=>{
if(e.target.checked){
onCheck([...checkedFoods!, food])
} else{
onCheck(checkedFoods.filter((_food)=> _food !== food));
}
}}/>
<span>{food}</span>
</li>
)}
</ol> );
}
export default CheckBoxList;
[blog post that shows how to Handle multiple checkbox components in React][1]
[1]: https://codemasterkimc.tistory.com/459
I am using React Query to make API calls.
I have an OTP Generation API in which I am making a POST API call to generate an OTP as a response from the API I receive the status of OTP deliverance.
/* eslint-disable no-unused-vars */
import React, { useState } from 'react';
import { useNavigate } from 'react-router-dom';
import { TextField } from '../Input/TextField';
import { CustomButton } from '../Button/CustomButton';
import { MOBILE_NUMBER } from '../Common/Placeholder';
import { getOtpData } from '../../hooks/getOtp.hook';
export function MobileNumber() {
const navigate = useNavigate();
const [mobileno, setMobileNo] = useState('');
const [isTermsAgree, setisTermsAgree] = useState(false);
const [isDisplayLoader, setDisplayLoader] = useState(false);
const [isDisplayError, setDisplayError] = useState(false);
const { mutate, isError, isSuccess, isLoading, isIdle, data } =
getOtpData();
// Onchnage event for input mobile number
const handleNumberChange = (
e: React.ChangeEvent<HTMLInputElement>,
) => {
setMobileNo(e.target.value);
};
// Onchnage event for Checkbox
const TermsAgreeChange = () => {
setisTermsAgree((current) => !current);
};
// onClick Event Confirm Btn //Generate OTP API call Goes Here
const getOtp = () => {
mutate(mobileno);
if (isSuccess) {
if (data?.data.otpSent) {
console.log('Sent - true');
navigate('/phone-otp-confirmation', {
state: { phoneNumber: mobileno },
});
}
if (data?.data.maxOtpRetriesExceeded) {
setDisplayError(true);
}
}
if (isError) {
console.log('error');
}
};
return (
<div className="bg-grey-800 h-1/2 mt-40 flex flex-col justify-evenly font-Manrope ">
<section>
<div className=" flex-col flex items-center md:items-baseline md:pl-36 ">
<p className=" text-3xl "> Enter Mobile Number </p>
</div>
<div>
<p className="text-l flex-col flex items-center mt-1 md:items-baseline md:pl-36 ">
<span className=" text-gray-400 text-center ">
Enter Mobile Number used for instant login
</span>
</p>
</div>
<div className="flex-col flex items-center md:items-baseline md:pl-36 mt-5">
<div className=" flex items-center sm:justify-start sm:px-0 ">
<div>
<div className=" flex w-18 px-3 justify-center items-center bg-transparent rounded-bl-lg rounded-tl-lg border text-2xl md:text-3xl border-gray-700 h-12 md:h-16 focus:outline-none focus:bg-transparent">
<span>+91</span>
</div>
</div>
<div>
<TextField
width="w-48"
height="h-12"
padding="px-5"
placeholder={MOBILE_NUMBER}
maxlen={10}
other="rounded-br-lg rounded-tr-lg px-5 md:w-72 md:h-16"
type="text"
onChangeFunction={handleNumberChange}
val={mobileno}
error={false}
/>
</div>
</div>
</div>
</section>
<div className=" flex-col flex mt-16 items-center md:items-baseline md:pl-36 md:mt-5 ">
<div className="flex items-center w-72">
<TextField
width="w-7"
height="h-7"
type="checkbox"
other="form-checkbox"
onChangeFunction={TermsAgreeChange}
/>
<p className="ml-3 text-sm md:text-base tracking-wide text-gray-400 font-extralight">
I have read the OneCard{' '}
<a
href="http://"
className="underline text-sm md:text-base text-gray-400"
>
Terms and Conditions & Privacy Policy
</a>{' '}
</p>
</div>
<div className="mt-8 ">
<CustomButton
clickEvent={getOtp}
btntext="Get OTP"
isbuttonactive={mobileno.length === 10 && isTermsAgree}
/>
</div>
{/* <h2>Loader</h2>
<h2>Error</h2> */}
</div>
</div>
);
}
OTP Generation hook
import { useMutation } from 'react-query';
import axios from 'axios';
import { WEB } from '../constants/constants';
interface IGetOTPResult {
otpSent: boolean;
maxOtpRetriesExceeded: boolean;
}
const getOTP = async (mobileNumber: string) => {
const response = await axios.post<IGetOTPResult>(
`${process.env.REACT_APP_URL}/`,
{
mobile: mobileNumber
},
{
headers: {
Authorization: '',
'Content-Type': 'application/json',
},
},
);
return response;
};
export const getOtpData = () => {
return useMutation(getOTP);
};
PROBLEM : As soon as I make this API call through the frontend as I click the button, it goes into isIdle state to be true.
Only the second time, I click the button, isSuccess becomes true.
However, bot the times the API call is made and I receive a 200 response!
I want to ensure my API call never enters isIdle state.
Plus, there is no significant information given about isIDle in any of react-queries documentation.
How do I go about this?
This is not how state in react works. when you call mutate, react-query updates state in your component, and on the next render cycle, it will be available. It is the same concept as setState, you can't really do:
function MyComponent() {
const [foo, setFoo] = React.useState('foo')
return <button onClick={() => {
setFoo('something')
console.log(foo) // 🚨 this will still log "foo", not "something"
}}>click</button>
}
if you want to get access directly to the response, you have to either:
use the provided callbacks of mutate:
mutate(
mobileno,
{
onSuccess: (response) => {
// handle success here
},
onError: (error) => {
// handle error here
}
)
use mutateAsync and await:
try {
const response = await mutateAsync(mobileno)
// handle success here
} catch(error) {
// handle error here
}
side question: how can a hook be called getOtpData ? It has to start with use...
I am making a multi-part form and I am using stripe to collect the card information. However, whenever I switch from one part to another and switch back to the card element stripe provides there is a black background on my input. Does anyone know the issue?
I've tried messing with the card options stripe provides and thought maybe there was some styling stripe provides for empty or invalid cards but that doesn't seem to be the case here.
import classes from "./Form.module.css"
import Button from "../UI/Button"
import Heading1 from "../UI/Heading1"
import Input from "./Input"
import { Transition } from "react-transition-group"
import useInput from "../../hooks/useInput"
import validator from "validator"
import { useState, useEffect, useRef } from "react"
import ButtonDisabled from "../UI/ButtonDisabled"
import Select from "./Select"
import { CardElement } from "#stripe/react-stripe-js"
const CARD_OPTIONS = {
iconStyle: "solid",
hidePostalCode: true,
classes: {
focus: classes.cardContainerFocus,
},
style: {
base: {
iconColor: "#fe019a",
color: "#f1a6d3",
backgroundColor: 'transparent',
fontWeight: 500,
fontFamily: "Roboto, Open Sans, Segoe UI, sans-serif",
fontSize: "16px",
},
invalid: {
iconColor: "red",
color: "red",
},
},
}
const FormPayment = (props) => {
const [formIsValid, setFormIsValid] = useState(false)
const currencyRef = useRef('')
const {
value: amountValue,
error: amountError,
errorMessage: amountErrorM,
touched: amountTouched,
onChangeHandler: amountOnChange,
onBlurHandler: amountOnBlur,
} = useInput('Enter A Valid Amount', (value) => validator.isNumeric(value.trim()))
const {
value: cardNameValue,
error: cardNameError,
errorMessage: cardNameErrorM,
touched: cardNameTouched,
onChangeHandler: cardNameOnChange,
onBlurHandler: cardNameOnBlur,
} = useInput("Empty Name", (value) => !validator.isEmpty(value.trim()))
useEffect(() => {
const formHasError = (
amountError ||
cardNameError
)
const formIsFilled =
!!(amountValue &&
cardNameValue)
if(!formHasError && formIsFilled) {
setFormIsValid(true)
} else {
setFormIsValid(false)
}
}, [
amountError,
cardNameError,
amountValue,
cardNameValue,
])
const onSubmitHandler = () => {
props.incrementCount()
props.updateInfo({
Currency: currencyRef.current.value,
Amount: amountValue,
CardName: cardNameValue,
})
}
return (
<Transition in={props.in} mountOnEnter={false} unmountOnExit timeout={200}>
{(state) => (
<div
className={`${classes.formContainer}
${state === "exiting" && classes.exiting}
${state === "entering" && classes.entering}
`}
>
<Heading1 className={classes.title}>Payment Info</Heading1>
<div className={classes.container}>
<Select
className={classes.shrinkSmall}
ref={currencyRef}
name="Currency"
data={["CAD", "USD", "GBP"]}
></Select>
<Input
value={amountValue}
onBlur={amountOnBlur}
onChange={amountOnChange}
error={amountError}
errorMessage={amountErrorM}
touched={amountTouched}
name="Amount"
></Input>
</div>
<div className={classes.container}>
<Input
value={cardNameValue}
onBlur={cardNameOnBlur}
onChange={cardNameOnChange}
error={cardNameError}
errorMessage={cardNameErrorM}
touched={cardNameTouched}
name="Card Holder Name"
></Input>
</div>
<div className={classes.cardContainer}>
<CardElement options={CARD_OPTIONS}></CardElement>
</div>
<div className={classes.splitContainer}>
<Button onClick={props.decrementCount}>{"<"} Back</Button>
{formIsValid ? (
<Button onClick={onSubmitHandler}>Next {">"}</Button>
) : (
<ButtonDisabled>Next {">"}</ButtonDisabled>
)}
</div>
</div>
)}
</Transition>
)
}
export default FormPayment
After tinkering around it about, it seems to be one of my chrome extensions acting funny, after going into incognito the problem fixed itself.
I have created the following component that wraps Vuetify VHover, VTooltip and VBtn to simplify my app.
<template>
<div>
<v-hover v-if="tooltip">
<v-tooltip
slot-scope="{ hover }"
bottom
>
<v-btn
slot="activator"
:theme="theme"
:align="align"
:justify="justify"
:disabled="disabled"
:depressed="type === 'depressed'"
:block="type === 'block'"
:flat="type === 'flat'"
:fab="type === 'fab'"
:icon="type === 'icon'"
:outline="type === 'outline'"
:raised="type === 'raised'"
:round="type === 'round'"
:color="hover ? colorHover : color"
:class="{ 'text-capitalize': label, 'text-lowercase': icon }"
:size="size"
#click="onClick()"
>
<span v-if="label">{{ label }}</span>
<v-icon v-else>{{ icon }}</v-icon>
</v-btn>
<span>{{ tooltip }}</span>
</v-tooltip>
</v-hover>
<v-hover v-else>
<v-btn
slot-scope="{ hover }"
:theme="theme"
:align="align"
:justify="justify"
:disabled="disabled"
:depressed="type === 'depressed'"
:block="type === 'block'"
:flat="type === 'flat'"
:fab="type === 'fab'"
:icon="type === 'icon'"
:outline="type === 'outline'"
:raised="type === 'raised'"
:round="type === 'round'"
:color="hover ? colorHover : color"
:class="{ 'text-capitalize': label, 'text-lowercase': icon }"
:size="size"
#click="onClick()"
>
<span v-if="label">{{ label }}</span>
<v-icon v-else>{{ icon }}</v-icon>
</v-btn>
</v-hover>
</div>
</template>
<script>
import VueTypes from 'vue-types'
export default {
name: 'v-btn-plus',
props: {
align: VueTypes.oneOf(['bottom', 'top']),
justify: VueTypes.oneOf(['left', 'right']),
color: VueTypes.string.def('primary'),
colorHover: VueTypes.string.def('secondary'),
disabled: VueTypes.bool.def(false),
icon: VueTypes.string,
label: VueTypes.string,
position: VueTypes.oneOf(['left', 'right']),
tooltip: VueTypes.string,
size: VueTypes.oneOf(['small', 'medium', 'large']).def('small'),
theme: VueTypes.oneOf(['light', 'dark']),
type: VueTypes.oneOf(['block', 'depressed', 'fab', 'flat', 'icon', 'outline', 'raised', 'round']).def('raised')
},
methods: {
onClick() {
this.$emit('click')
}
},
created: function() {
// Workaround as prop validation on multiple props is not possible
if (!this.icon && !this.label) {
console.error('[Vue warn]: Missing required prop, specify at least one of the following: "label" or "icon"')
}
}
}
</script>
<style scoped>
</style>
I want to test VHover and VTooltip and have defined the following spec file.
import { createLocalVue, mount } from '#vue/test-utils'
import Vuetify from 'vuetify'
import VBtnPlus from '#/components/common/VBtnPlus.vue'
describe('VStatsCard.vue', () => {
let localVue = null
beforeEach(() => {
localVue = createLocalVue()
localVue.use(Vuetify)
})
it('renders with default settings when only label is specified', async () => {
const label = 'Very cool'
const defaultColor = 'primary'
const defaultType = 'raised'
const defaultSize = 'small'
const wrapper = mount(VBtnPlus, {
localVue: localVue,
propsData: { label }
})
expect(wrapper.text()).toMatch(label)
expect(wrapper.html()).toContain(`${defaultType}="true"`)
expect(wrapper.html()).toContain(`size="${defaultSize}"`)
expect(wrapper.html()).toContain(`class="v-btn theme--light ${defaultColor} text-capitalize"`)
expect(wrapper.html()).not.toContain(`v-icon"`)
wrapper.find('button').trigger('mouseenter')
await wrapper.vm.$nextTick()
const btnHtml = wrapper.find('.v-btn').html()
expect(btnHtml).toContain('secondary')
expect(btnHtml).not.toContain('primary')
const tooltipId = btnHtml.match(/(data-v-.+?)(?:=)/)[1]
const tooltips = wrapper.findAll('.v-tooltip_content')
let tooltipHtml = null
for (let tooltip of tooltips) {
const html = tooltip.html()
console.log(html)
if (html.indexOf(tooltipId) > -1) {
tooltipHtml = html
break
}
}
expect(tooltipHtml).toContain('menuable_content_active')
})
})
The wrapper.find('button').trigger('mouseenter') does not work as expected. When I look at the html code after the nextTick it is the same as before trigger was called. It looks like I'm missing a part of the html. I was excpecting to see the following html.
<div data-v-d3e326b8="">
<span data-v-d3e326b8="" class="v-tooltip v-tooltip--bottom">
<span>
<button data-v-d3e326b8="" type="button" class="v-btn v-btn--depressed theme--light orange text-lowercase" size="small">
<div class="v-btn__content"><i data-v-d3e326b8="" aria-hidden="true" class="v-icon mdi mdi-account theme--light"></i></div>
</button>
</span>
</span>
</div>
All I'm getting is the <button> part.
Any suggestions how to get this to work?
Explicitly triggering mouseenter events doesn't normally have an effect, likely because browsers ignore simulated/non-trusted events. Instead, you could set v-hover's value to true to force the hover state:
VBtnPlus.vue
<template>
<div>
<v-hover :value="hovering">...</v-hover>
</div>
</template>
<script>
export default {
data() {
return {
hovering: false,
}
},
//...
}
</script>
VBtnPlus.spec.js
it('...', async () => {
wrapper.vm.hovering = true;
// test for hover state here
})
I just ran into this myself using Nuxt, Vuetify, and Jest. I followed
this example for Vuetify 2.x.
Below is a very simple example of what I did with my code.
As a side note, when I tried to set the wrapper.vm.[dataField] = [value] directly, Jest threw an error not allowing direct set access on the object. In the example.spec.js below, calling wrapper.setData({...}) will allow you to set the data value without any issue.
example.vue:
<template lang="pug">
v-hover(
v-slot:default="{ hover }"
:value="hoverActive"
)
v-card#hoverable-card(
:elevation="hover ? 20 : 10"
)
</template>
<script>
export default {
data() {
return {
hoverActive: false
}
}
</script>
example.spec.js
import { mount, createLocalVue } from '#vue/test-utils'
import Vuetify from 'vuetify'
import example from '#/components/example.vue'
const localVue = createLocalVue()
localVue.use(Vuetify)
describe('example', () => {
const wrapper = mount(example, {
localVue
})
it('should have the correct elevation class on hover', () => {
let classes = wrapper.classes()
expect(classes).toContain('elevation-10')
expect(classes).not.toContain('elevation-20')
wrapper.setData({ hoverActive: true })
classes = wrapper.classes()
expect(classes).not.toContain('elevation-10')
expect(classes).toContain('elevation-20')
})
})
https://github.com/vuejs/vue-test-utils/issues/1421
it('2. User interface provides one help icon with tooltip text', async (done) => {
// stuff
helpIcon.trigger('mouseenter')
await wrapper.vm.$nextTick()
requestAnimationFrame(() => {
// assert
done()
})
})
I made Todo app using react. On clicking enter the todo gets added in array, then on click on entered todo the todo gets stricked-off. Now i am facing problem to edit the entered todo. I want when i double click the entered todo, it converts into editing mode and then i can edit the todo n save it on enter keypress. My code goes like this:
class App extends React.Component {
constructor(){
super();
this.state={
todo:[]
};
};
entertodo(keypress){
var Todo=this.refs.inputodo.value;
if( keypress.charCode == 13 )
{
this.setState({
todo: this.state.todo.concat({Value:Todo, checked:false, editing:false})
});
this.refs.inputodo.value=null;
};
};
todo(todo,i){
return (
<li className={todo.checked===true? 'line':'newtodo'}>
<div onClick={this.todoCompleted.bind(this, i)}>
<input type="checkbox" className="option-input checkbox" checked={todo.checked} />
<div key={todo.id} className="item">
{todo.Value}
<span className="destroy" onClick={this.remove.bind(this, i)}>X</span>
</div>
</div>
</li>
);
};
remove(i){
this.state.todo.splice(i,1)
this.setState({todo:this.state.todo})
};
todoCompleted(i){
var todo=this.state.todo;
todo[i].checked =todo[i].checked? false:true;
this.setState({
todo:this.state.todo
});
};
allCompleted=()=>{
var todo = this.state.todo;
var _this = this
todo.forEach(function(item) {
item.className = _this.state.finished ? "newtodo" : "line"
item.checked = !_this.state.finished
})
this.setState({todo: todo, finished: !this.state.finished})
};
render() {
return (
<div>
<h1 id='heading'>todos</h1>
<div className="lines"></div>
<div>
<input type="text" ref= "inputodo" onKeyPress={this.entertodo.bind(this)}className="inputodo"placeholder='todos'/>
<span onClick={this.allCompleted}id="all">^</span>
</div>
<div className="mainapp">
<ul className="decor">
{this.state.todo.map(this.todo.bind(this))}
</ul>
</div>
</div>
);
}
}
ReactDOM.render(<App/>,document.getElementById('app'));
.line {
text-decoration: line-through;
color: red;
}
.newtodo{
text-decoration: none;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/0.14.8/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/0.14.8/react-dom.min.js"></script>
<div id="app"></div>
I've been trying something similar, so this gist might help you on the right track. I think you're especially interested the handleClick method. It debounces received click events. Then you can listen for a certain amount of consecutive clicks, like I did on line 33.
However, the transition between view and edit seems to be quite slow (maybe I did something wrong :shrug:) , so if editing should happen frequently, it might be better to fake this behavior with css.*
Style an input type to look like ordinary text, then onFocus, style it like a field.
There is an option for the click events called onDoubleClick={this.handler} at this point in time.
you may use the component
import _ from 'lodash'
import { useState } from 'react'
const inputValue = (e: any): string => e.target.value
function isEnterOrEscapeKeyEvent(event: React.KeyboardEvent<HTMLInputElement>) {
return event.key === 'Enter' || event.key === 'Escape'
}
const EditOnDblClick = () => {
const [isEditing, setisEditing] = useState(false)
const [text, settext] = useState('yoga chitta')
const onEditEnd = () => {
setisEditing(false)
}
return isEditing ? (
<input
value={text}
className="bg-transparent border-2 border-black border-solid"
onKeyDown={(event) => {
if (isEnterOrEscapeKeyEvent(event)) {
event.preventDefault()
event.stopPropagation()
onEditEnd()
}
}}
onChange={_.flow(inputValue, settext)}
onBlur={onEditEnd}
autoFocus
/>
) : (
<div className="select-none" onDoubleClick={() => setisEditing(true)}>
{text}
</div>
)
}
export default EditOnDblClick
Note: Classes are from tailwindcss