I have an onChange-event on an input listening for when target.value is greater than 0 to enable classes to then display a button.
This works on desktop however, not functional on iPad Safari - I'm unsure why?
Note I am using styled components and PDInput is declared as an input.
const PDInput = styled.input`
background: #fff;
border: none;
border-bottom: 1px solid #f2f2f5;
padding: 8px 0;
padding-left: ${props => props.amount ? "15px" : "0"};
font-size: 16px;
color: #0c2074;
width: 100%;
margin-bottom: 24px;
&::placeholder {
color: #0c2074;
opacity: 0.5;
}
`
const updateRef = e => {
if(e.target.value > 0) {
setRef(true);
} else {
setRef(false);
}
}
<PDInput type="text" placeholder="E.G Rent" onChange={(e) => updateRef(e)} />
Here's the solution - the issue was that e.target.value isn't technically > 0. It returns false. You want to check if the e.target.value !== ''.
Code:
const updateRef = e => {
if(e.target.value > 0 || e.target.value !== '') {
setRef(true);
} else {
setRef(false);
}
}
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;
}
My custom react toast component was working well until I tried to implement automatic dismissal of notifications after a set time.
I am trying to make it so that after a set time, the pop-up "toast" notifications will have a CSS fade-out animation play and then be deleted — unless the user is hovering over that notification, in which case it will hold off on dismissing that one until the user moves their mouse off of it.
Sometimes it works properly, other times it stops displaying anything and adds the notifications back one by one, other times it... well, it just behaves in a very strange and unexpected manner.
Here is my code:
Toast.css
.toast-container {
font-size: 24px;
box-sizing: border-box;
position: fixed;
z-index: 10;
}
.toast-popup {
padding: 12px;
display: flex;
align-items: center;
justify-content: space-between;
width: 500px;
border: solid #f2f2f2;
border-radius: 8px;
box-shadow: 0 0 10px #999;
margin-bottom: 1rem;
opacity: 0.9;
}
.toast-popup:hover {
box-shadow: 0 0 12px deepskyblue;
opacity: 1 !important;
animation-play-state: paused;
}
.success {
background-color: #5cb85c;
}
.info {
background-color: #5bc0de;
}
.warning {
background-color: #f0ad4e;
}
.danger {
background-color: #d9534f;
}
.toast-text {
justify-self: flex-start;
width: 100%;
padding: 6px 0 6px 6px;
opacity: 0.9;
}
.toast-title {
font-weight: 700;
font-size: 32px;
text-align: left;
padding-bottom: 0px;
color: #f2f2f2;
}
.toast-message {
padding-top: 0px;
text-align: left;
color: #f2f2f2;
}
.toast-icon {
float: left;
margin: 0 20px 0 10px;
opacity: 0.9;
}
.toast-icon img {
width: 50px;
height: 50px;
fill: #f2f2f2;
opacity: 0.9;
}
.close-button {
float: right;
align-self: flex-start;
font-weight: 600;
color: #f2f2f2;
background: none;
border: none;
opacity: 0.9;
cursor: pointer;
}
.top-right {
top: 2rem;
right: 2rem;
}
.top-right-slide {
top: 2rem;
right: 2rem;
transition: transform .6s ease-in-out;
animation: toast-in-right .7s;
}
.bottom-right {
bottom: 2rem;
right: 2rem;
}
.bottom-right-slide {
bottom: 2rem;
right: 2rem;
transition: transform .6s ease-in-out;
animation: toast-in-right .7s;
}
.top-left {
top: 2rem;
left: 2rem;
}
.top-left-slide {
top: 2rem;
left: 2rem;
transition: transform .6s ease-in;
animation: toast-in-left .7s;
}
.bottom-left {
bottom: 2rem;
left: 2rem;
}
.bottom-left-slide {
bottom: 2rem;
left: 2rem;
transition: transform .6s ease-in;
animation: toast-in-left .7s;
}
.fadeout {
animation: 4s linear 5s 1 normal forwards running toast-fadeout;
}
#keyframes toast-in-right {
from { transform: translateX(100%); }
to { transform: translateX(0); }
}
#keyframes toast-in-left {
from { transform: translateX(-100%); }
to { transform: translateX(0); }
}
#keyframes toast-fadeout {
from { opacity: 0.9; }
to { opacity: 0; }
}
Toast.js - Please excuse the generous peppering of console.logs...
import React, {useEffect, useState} from 'react';
import icon_success from './icons/feathericons/check-circle.svg';
import icon_info from './icons/feathericons/info.svg';
import icon_warning from './icons/feathericons/alert-triangle.svg';
import icon_danger from './icons/feathericons/alert-octagon.svg';
import './Toast.css';
const Toast = (props) => {
const {toastList, position} = props;
const [list, setList] = useState(toastList);
const [prevId, setPrevId] = useState(0);
// This useEffect updates the list of toasts to display
useEffect(() => {
console.log('useEffect()');
console.log('useEffect() toastList:');
console.log(toastList);
setList([...toastList]);
}, [toastList]);
const markForDeletion = (toast) => {
if( toast.isDeleting ) {
return;
}
console.log(`toast ${toast.id} marked for deletion`)
toast.isDeleting = true;
setTimeout(() => {attemptDeletion(toast)}, 5000);
}
const attemptDeletion = (toast) => {
console.log(`attempting to delete toast ${toast.id}. canDelete = ${toast.canDelete}`);
if( toast.canDelete ) {
deleteToast(toast.id);
}
else {
console.log(`cannot delete toast ${toast.id}. `);
}
}
const getIcon = (variant) => {
switch( variant ) {
case 'success':
return icon_success;
break;
case 'info':
return icon_info;
break;
case 'warning':
return icon_warning;
break;
case 'danger':
return icon_danger;
break;
}
}
const generateId = (toast) => {
if( typeof(toast.id) === 'number' ) {
return toast.id;
}
toast.id = prevId + 1;
setPrevId(toast.id);
return toast.id;
}
const deleteToast = (id) => {
console.log(`deleting toast ${id}`);
const deletionIdxList = list.findIndex(e => e.id === id);
const deletionIdxToastList = toastList.findIndex(e => e.id === id);
console.log(`deletionIdxToastList: ${deletionIdxToastList}`);
if(deletionIdxList == null || deletionIdxList === -1) {
console.log(`cannot find list idx of id ${id}`);
console.log('list:');
console.log(list);
return;
}
if(deletionIdxToastList == null || deletionIdxToastList === -1) {
console.log(`cannot find toastList idx of id ${id}`);
console.log('toastList:');
console.log(toastList);
return;
}
console.log('list before deletion:');
console.log(list);
console.log('toastList before deletion:');
console.log(toastList);
console.log('list[deletionIdxList]:');
console.log(list[deletionIdxList]);
list.splice(deletionIdxList, 1);
console.log('toastList[deletionIdxToastList]:');
console.log(toastList[deletionIdxToastList]);
toastList.splice(deletionIdxToastList, 1);
setList([...list]);
console.log(`toast ${id} deleted successfully`);
console.log('list after deletion:');
console.log(list);
console.log('toastList after deletion:');
console.log(toastList);
}
return (
<>
<div className={`toast-container ${position}`} >
{
list.map((toast, i) => (
<div
key={i}
className={`toast-popup ${toast.variant} ${toast.isDeleting ? (position + ' fadeout') : (position + '-slide')}`}
onLoad={() => {
if( !toast.isLoaded ) {
toast.Id = generateId(toast);
toast.canDelete = true;
toast.isDeleting = false;
toast.isLoaded = true;
console.log(`on load ${toast.id}`);
setTimeout(() => markForDeletion(toast), 500);
}
}}
onMouseOver={() => {
toast.canDelete === true ? toast.canDelete = false : null;
toast.isDeleting === true ? toast.isDeleting = false : null;
console.log(`mouse over ${toast.id}`);
}}
onMouseLeave={() => {
toast.canDelete === false ? toast.canDelete = true : null;
markForDeletion(toast);
console.log(`mouse leave ${toast.id}`);
}}
>
<div className={'toast-icon'}>
<img src={getIcon(toast.variant)} />
</div>
<div className={'toast-text'}>
<div className={'toast-title'}>
{toast.variant.charAt(0).toUpperCase() + toast.variant.slice(1)}
</div>
<div className={'toast-message'}>{toast.message}</div>
</div>
<button
className={'close-button'}
onClick={() => {
toast.canDelete = true;
deleteToast(toast.id)
}
}>
X
</button>
</div>
))
}
</div>
</>
)
}
Toast.defaultProps = {
position: 'bottom-right'
}
export default Toast;
Snippet of Home.js where I am testing this new Toast component - A class component as I'm working on updating a pre-existing application to remove dependency on the react-toastify library
// Leaving out constructor and other irrelevant code...
toastSuccess() {
const newToast = {
variant: 'success',
message: 'This is a test of the success variant toast pop-up.'
}
this.setState({
toastList: [...this.state.toastList, newToast]
});
}
toastInfo() {
const newToast = {
variant: 'info',
message: 'This is a test of the info variant toast pop-up.'
}
this.setState({
toastList: [...this.state.toastList, newToast]
});
}
toastWarning() {
const newToast = {
variant: 'warning',
message: 'This is a test of the warning variant toast pop-up.'
}
this.setState({
toastList: [...this.state.toastList, newToast]
});
}
toastDanger() {
const newToast = {
variant: 'danger',
message: 'This is a test of the danger variant toast pop-up.'
}
this.setState({
toastList: [...this.state.toastList, newToast]
});
}
render() {
return (
<div className="Home" style={{height:'100%'}}>
<Toast
toastList={this.state.toastList}
position={'bottom-right'}
/>
<div style={{display:'flex', justifyContent:'center'}}>
<Button onClick={() => this.toastSuccess()}>Success</Button>
<Button onClick={() => this.toastInfo()}>Info</Button>
<Button onClick={() => this.toastWarning()}>Warning</Button>
<Button onClick={() => this.toastDanger()}>Danger</Button>
</div>
{// ...}
</div>
);
}
Let me know if there's a way to get this code running here on StackOverflow using that Code Snippet feature, as that would be really helpful so that you readers can see the issue first-hand. Unfortunately I've never had any luck getting it to work, but I'll keep trying for a bit to see if I can figure it out.
EDIT:
Thanks to #Cristian-FlorinCalina for recommending StackBlitz as a good shareable test environment. I've got it set up there now, here's a link:
https://react-ts-ybunlg.stackblitz.io
First problem that I see with your code is that you are keeping two sources of truth for toast list. One is passed from the parent via props, and one is the internal state list in the Toast component. This is an antipattern that can generate a lot of issues.
Second BIG issue is that you are altering the list that you receive from the parent. That is a huge antipattern in React since props are readonly -- All React components must act like pure functions with respect to their props. (since you are altering an object inside an array apparently it works for the load update but it does not work when you are trying to call splice on the list -- this is why even if you deleted the element and applied the deletion effect, when it gets updated on the parent (next render) -> it will come back without it being removed and clicking again on another toast generate button will show you the previously deleted toast as well).
I think the big problem here is that you are not using composition properly. Instead of passing the toast list to the Toast component, you should keep the list on the parent, move the map from the child inside the parent. You will have one instance of Toast component per each element in the list.
Maybe you can have a ToastList component as well, that handles Toast compoonents based on their position... So when you click on Upper Left Toast Generator for example, it will add a new entry inside an array of toasts, with a position key. That array will be sent to the ToastList component, which will generate Toast components that handle their state internally (deletion, etc) and do not update the actual list. You can pass a function to the Toast component called onDelete that will be called by the Toast component on deletion, and you will update the ToastList state based on those events (probably propagate the delete event to the parent to update the list there).
Hope it makes sense.
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.
I have written following test:
it('componentDidUpdate should mount and change props', () => {
const onChange = jest.fn();
const wrapper = enzyme
.mount(
<JsonInput
onChange={onChange}
onValueChange={mockOnValueChange}
value={exampleJsonStringValidated}
/>,
{ wrappingComponent: withTestThemeWrapper },
);
console.log(wrapper.debug());
expect(wrapper.find(JsonInput).hasClass('valid')).toEqual(true);
wrapper.setProps({ value: exampleJsonStringNotValidated });
expect(wrapper.find(JsonInput).hasClass('invalid')).toBe(true);
});
and console.log shows:
<JsonInput onChange={[Function: mockConstructor]} onValueChange={[Function: mockConstructor]} value="{"firstName":"John","lastName":"Doe","age":210}">
<styled.textarea onChange={[Function]} value="{"firstName":"John","lastName":"Doe","age":210}" height="">
<StyledComponent onChange={[Function]} value="{"firstName":"John","lastName":"Doe","age":210}" height="" forwardedComponent={{...}} forwardedRef={{...}}>
<textarea onChange={[Function]} value="{"firstName":"John","lastName":"Doe","age":210}" height="" className="sc-bdVaJa lavZWj" />
</StyledComponent>
</styled.textarea>
</JsonInput>
In the component the code className="sc-bdVaJa lavZWj" is valid and invalid but now I see that there is no readable names of classes, how to test it?
Component (styled part)
export const StyledTextArea = styled.textarea<{ height: string }>`
margin: 0;
box-sizing: border-box;
width: 350px;
outline: none;
border: none;
height: ${props => props.height};
box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.23);
background-color: ${props => props.theme.palette.foreground};
color: ${props => props.theme.palette.text};
cursor: text;
&:focus{
border-bottom: 2px solid ${props => props.theme.palette.active};
}
&:valid{
border-bottom: 2px solid ${props => props.theme.palette.positive};
}
&:invalid{
border-bottom: 2px solid ${props => props.theme.palette.negative};
}
`;
and render:
render() {
// to exclude unknown property 'onValueChange' for JsonInput for DevTools
const { height = '', onValueChange, ...restProps } = this.props;
return (
<StyledTextArea
ref={this.JsonInputRef}
{...restProps}
onChange={this.handleValueChange}
height={height}
/>
);
}
So you does not need(and cannt) to test classnames themselves since :valid and :invalid are state/pseudoselector.
For toHaveStyleRule from jest-styled-components there is 3rd argument options where we could provide desired state like :hover or :valid.
Try this:
expect(wrapper
.find('textarea')
.toHaveStyleRule(
'border-color',
'border-bottom: 2px solid red',
{modifier: ':invalid'}
)
).toBeTruthy();
So im writing some code for a website that i've implemented which is a simple hide/show function. And i've been thinking about how I can go about writing this or similar code shorter, concise or more efficient.
To be specific, how to easily have a "switch" state that switches between 2 if statements.
let selectedArrow = document.getElementsByClassName('arrows');
let state = 0;
for (let i = 0; i < selectedArrow.length; i++) {
selectedArrow[i].addEventListener('click', function() {
let id = event.target;
let target = event.target.parentElement.nextElementSibling;
if(state === 0) {
id.style.transform = 'rotate(-90deg)'
target.style.display = 'none';
state = 1;
} else if(state === 1) {
id.style.transform = 'rotate(0deg)'
target.style.display = 'grid';
state = 0;
}
});
}
This code works perfectly fine, just wondering if others have any other tricks as i'm a beginner coder.
I hope this helps, here i use marker class in dom to store state:
let bindClick = e => e.onclick = e => e.target.classList.toggle('active');
[...document.querySelectorAll('.arrows')].forEach(bindClick)
.arrows {
border: solid;
width: 50px;
display: inline-block;
line-height: 50px;
text-align: center;
font: 44px arial;
margin: 10px;
cursor: pointer;
transition: ease-in 300ms;
}
.active {
color: red;
transform: rotate(-45deg);
}
<div class='arrows active'>1</div> <div class='arrows'>2</div> <div class='arrows'>3</div>
<div class='arrows active'>4</div> <div class='arrows'>5</div> <div class='arrows'>6</div>
[...expr] is used because forEach iteration over NodeList which returned from document.querySelectorAll dont implemented in some browsers... or implemented not so long time ago
Here is alternative if you dont want to use styling for manage state represenation:
[...document.querySelectorAll('.arrows')].forEach(e => e.onclick = e => {
e.target.classList.toggle('active');
let v = e.target.classList.contains('active');
e.target.style.transform = `rotate(${v ? -45 : 0}deg)`;
e.target.style.color = v ? 'red' : 'black';
})
.arrows {
border: solid;
width: 50px;
height: 50px;
display: inline-block;
line-height: 50px;
text-align: center;
font-size: 40px;
margin: 10px;
cursor: pointer;
}
<div class='arrows'>1</div> <div class='arrows'>2</div> <div class='arrows'>3</div>
<div class='arrows'>4</div> <div class='arrows'>5</div> <div class='arrows'>6</div>
Firstly, function switch will error out - this doesn't need the switch (it's reserved anyway). Secondly, you can use some fancy ternaries and template literals:
let selectedArrow = document.getElementsByClassName('arrows');
let state = 0;
for (let i = 0; i < selectedArrow.length; i++) {
selectedArrow[i].addEventListener('click', function() {
let id = event.target;
let target = event.target.parentElement.nextElementSibling;
id.style.transform = `rotate(${-90 + (state * 90)})`;
target.style.display = id ? "grid" : "none";
state = state ? 0 : 1;
});
}