I'm working on a simple Todo App in Svelte. I recently added some edit functionality, which is a little buggy. Initially there is only an input with a submit button. Under the hood, todo is bound using bind:value between the input value and the variable, todo.
Until I added the edit functionality, I didn't notice the bug. However, the binding between todo and the input field does not cease to exist when the submit button is clicked. I'm curious if there is a way to remove a binding as a side effect to an event?
App
<script>
import AddTodo from "./AddTodo.svelte";
import DisplayTodoItem from "./DisplayTodoItem.svelte";
let todoItems = [];
const handleTodoButtonClick = event => {
todoItems = [...todoItems, event.detail.value];
};
const handleDeleteButtonClick = event => {
todoItems = todoItems.filter(value => value != event.detail.value);
};
</script>
<style>
main {
font-family: sans-serif;
text-align: center;
}
</style>
<main>
<AddTodo on:addTodoMessage={handleTodoButtonClick}/>
{#each todoItems as item}
<DisplayTodoItem item={item} on:deleteItemMessage={handleDeleteButtonClick}/>
{/each}
</main>
AddTodo
<script>
import { createEventDispatcher } from "svelte";
const dispatch = createEventDispatcher();
let todo;
</script>
<input bind:value={todo}>
<button on:click={ ()=>{dispatch('addTodoMessage', {value:todo})} }> submit </button>
DisplayTodoItem
<script>
import { createEventDispatcher } from "svelte";
const dispatch = createEventDispatcher();
export let item;
let notEdit = true;
</script>
{#if notEdit}
<div style="display: inline;">
<h1> {item} </h1>
<button on:click={ () => dispatch('deleteItemMessage', {value:item} )}>
delete
</button>
<button on:click={()=> notEdit=false}> edit</button>
</div>
{:else}
<input bind:value={item}/>
<button on:click={()=> notEdit=true}> Done editing </button>
{/if}
Ideally, after clicking submit and adding a todo to the array todoItems, the binding would be removed, such that buggy behavior did not occur later when edits to todoItems occur.
Is this possible?
The item is not bound, so the changes are not saved in the list, you need:
<DisplayTodoItem bind:item .../>
Otherwise, any update will load the original value from when the item was added. It has nothing to do with the add input, that is entirely separate.
(Also, when removing items not just from the end, one should use a keyed {#each} and the delete will remove all items with the same value. Maybe switch that to index-based if the items are primitive values.)
Related
i want to improve my code, with several buttons that has custom class names (attr), when clicked should add to body tag (toggle), now is adding the first button only because for ("button")[0] but should work for each button
import React, { useState, useEffect } from "react"
function Test() {
const [isClass, setIsClass] = useState(false)
useEffect(() => {
const x = document.getElementsByTagName("button")[0].getAttribute("custom-class")
document.body.classList.toggle(x, isClass)
}, [isClass])
return (
<>
<button custom-class='test1' onClick={() => setIsClass(!isClass)}>
Setting test1 className
</button>
<button custom-class='test2' onClick={() => setIsClass(!isClass)}>
Setting test2 className
</button>
</>
)
}
export default Test
Thanks
Please use this code.
let oldStyle = "";
const handleClick = (index) => {
const x = [...document.getElementsByTagName("button")].map(value => value.getAttribute("custom-class"));
document.body.classList.contains(x[index]) ? document.body.classList.remove(x[index]) : document.body.classList.add(x[index]);
if(document.body.classList.length > 1) document.body.classList.replace(oldStyle, x[index]);
oldStyle = x[index];
}
return (
<>
<button custom-class='test1' onClick={() => handleClick(0)}>
Setting test1 className
</button>
<button custom-class='test2' onClick={() => handleClick(1)}>
Setting test2 className
</button>
</>
)
It is better not to use DOM querying and manipulation directly with elements that are created and controlled by react. In your particular example it is ok to use document.body, but not ok to search for buttons, especially when you try to find them by tag name. To actually toggle a class in classList you don't need second parameter in most cases, so additional state is also not needed.
React way to get reference to element renderend by React would be to use Ref. However, in your particular case side effect can be launched inside event handler, so you don't need useEffect or useRef.
Your onClick handler can accept event object that is Synthetic Event. It holds property target that holds reference to your button.
So, the easiest way would be simply to write like this:
function Test() {
function clickHandler(event) {
let classToToggle = event.target.getAttribute("custom-class");
document.body.classList.toggle(classToToggle);
}
return (
<>
<button key="test1" custom-class="test1" onClick={clickHandler}>
Setting test1 className
</button>
<button key="test2" custom-class="test2" onClick={clickHandler}>
Setting test2 className
</button>
</>
);
}
export default Test;
If you need to have only single className from the list, you can decide which class to enable or disable with a bit of a state. Since anything can add classes on body it might be useful to operate only on some set of classes and not remove everything.
Also, not mentioned before, but consider using data attribute as its purpose is to keep some additional data.
function Test() {
// this can come from props or be hardcoded depending on your requirements
// If you intend to change it in runtime, consider adding side effect to cleanup previous classes on body
let [classesList] = React.useState(["test1", "test2"]);
let [activeClass, setActiveClass] = React.useState("");
// You can switch actual classes in effect, if you want to
function clickHandler(event) {
let classToToggle = event.target.dataset.customClass;
// we remove all classes from body that are in our list
document.body.classList.remove(...classesList);
if (activeClass === classToToggle) {
setActiveClass("");
} else {
// if class not active - set new one
document.body.classList.add(classToToggle);
setActiveClass(classToToggle);
}
}
return (
<>
{classesList.map((cn) => (
<button key="cn" data-custom-class={cn} onClick={clickHandler}>
Setting {cn} className
</button>
))}
</>
);
}
I have a button and for testing purposes, I want to write to the console the index of an array element. More specifically, I have a button in button.js, and that button is displayed on each array element in the IncomeOutputList array. When clicked on, I want each button to print to the console the index of the corresponding IncomeOutputList array element.
For example, by clicking on the button of the second element shown in the image below, I want the console to display index 1 (the first element is the topmost rectangle, which is a blank array element).
Here is a picture of an array element with the button, the button appears while hovering above the number for each array element:
Currently when the page renders, all of the indices of the array are displayed in console, not sure why.
I hope I made my question clear!
button.js:
import React from 'react';
const Button = ({buttonType, handler}) => (
<>
<div className="item__delete">
<button className={buttonType} onClick={handler}>
<i className="ion-ios-close-outline"></i>
</button>
</div>
</>
)
export default Button;
ValueOutput.js:
import React from 'react';
import Button from '../buttons/Button';
//move item__value element to left when hovering over it, and make delete button appear
const ValueOutput = ({type, value, handleClick}) => {
return (
<>
<div className="right clearfix">
<div className="item__value">{type} {value}</div>
<Button buttonType="item__delete--btn" handler={handleClick}/>
</div>
</>
)
}
export default ValueOutput;
IncomeOutput.js:
import React from 'react';
import ValueOutput from './ValueOutput';
const IncomeOutput = ({ desc, type,id, value, handleButton }) => {
//id = inc-{id}
return (
<>
<div className="item clearfix income" id={id}>
<div className="item__description">{desc}</div>
<ValueOutput
type={type}
value={value}
handleClick={handleButton}
/>
</div>
</>
)
}
export default IncomeOutput;
IncomeOutputList.js:
import React from 'react';
import IncomeOutput from './IncomeOutput';
// list will be list of income objects
const IncomeOutputList = ({ list }) => {
const handler = (i) => {
console.log(i);
console.log('the test');
}
return (
<div className="income__list">
<div className="income__list--title">INCOME</div>
{list.map((item, index) => <IncomeOutput
id={item.id}
value={item.incomeValue}
type={item.budgetType}
desc={item.desc}
handleButton={handler(index)}
/>
)}
</div>
)
}
You are passing handler(index) as your event handler. Since that doesn't return anything you are effectively passing undefined as your handler. You will want to change your handler method to return a function:
const handler = (i) => {
return () => {
console.log(i);
console.log('the test');
};
};
You could also just wrap your call to handler in a function, buttonHandle={() => handler(index)} - This is effectively the same thing.
The problem is that the handler function is executed right away when the code is encountered.
Whenever you have () the function will execute right away when encountered. It is not waiting for the event to fire.
Here is what you can do:
handleButton={() => handler(index)}
I'm new to React, Nodejs and JavaScript so bear with me.
I'm doing some practice with onClick events to change text by clicking some buttons, I have an input type="checkbox" to make the text bold when checked and vise versa, 2 buttons to increase and decrease the text size by 1+ or 1- and a span that shows the current text size (16 is my default), and finally a span with the id="textSpan" that have the text meant to be modified. I also want this buttons, the checkbox and the span with the id="fontSizeSpan" that shows the current font size to be hidden by default and when you click the text it appears on its left.
This is the code so far:
class FontChooser extends React.Component {
constructor(props) {
super(props);
this.state = {hidden: true};
this.checkInput = React.createRef();
this.hide = React.createRef();
}
toggle(){
this.setState({hidden: !this.state.hidden});
this.hide.current
}
makeBold(){
this.setState({bold: !this.state.bold});
this.checkInput.current
}
changeSize(){
this.setState({size: !this.props.size})
for(var i = this.props.size; i <= this.props.max; i++);
}
render() {
return(
<div>
<input type="checkbox" id="boldCheckbox" ref={this.hide} hidden={false} onClick={this.makeBold.bind(this)}/>
<button id="decreaseButton" ref={this.hide} hidden={false}>-</button>
<span id="fontSizeSpan" ref={this.hide} hidden={false}>{this.props.size}</span>
<button id="increaseButton" ref={this.hide} hidden={false} onClick={this.changeSize.bind(this)}>+</button>
<span id="textSpan" ref={this.checkInput} onClick={this.toggle.bind(this)}>{this.props.text}</span>
</div>
);
}
right now their hidden attribute is false so I can see them.Here's the html which is not much:
<div id='container'></div>
<script type="text/jsx">
ReactDOM.render(
<div>
<FontChooser min='4' max='40' size='16' text='You can change me!' bold='false'/>
</div>,
document.getElementById('container'))
;
</script>
So far all I have managed is for the browser console(I'm using Firefox react component addon) to confirm there is a functioning event that doesn't really work, as in when I click the text, the buttons or the input checkbox the props does change to false or true every click but that's about it.
I appreciate it if someone could guide me through this.
NOTE:
just in case nothing is imported, also I setup a local server with Nodejs
Here is an Example of what you want: https://codesandbox.io/s/mystifying-cookies-v7w3l?file=/src/App.js
Basically, I have 4 variables: text, fontWeight, fontSize and showTools.
Each button has its own task and also you can select if show or not.
In React you don't have to care about ids like in older frameworks. You can generate the elements just in the place where you are with the information which you need. So, basically, we have the 4 variables and use them wisely where we want (as styles props, as text and even as a conditional to show components). It's the magic of React and JSX.
In the code I've use hooks, part of the latest definition of React. For that my Components is functional and not a Class. it makes it easier and faster for examples and prototyping.
The tools are show by default just to let you play with it
import React from "react";
import "./styles.css";
export default function App() {
const [text, setText] = React.useState("");
const [boldFont, setBoldFont] = React.useState(false);
const [fontSize, setFontSize] = React.useState(14);
const [showTools, setShowTools] = React.useState(true);
return (
<div className="App">
<div
style={{
fontWeight: boldFont ? "bold" : "normal",
fontSize: `${fontSize}px`
}}
>
<span onClick={() => setShowTools(!showTools)}>
{text || "Text Example"}
</span>
</div>
{showTools && (
<div>
<button onClick={() => setBoldFont(!boldFont)}>Bold</button> |
<button onClick={() => setFontSize(fontSize + 1)}>A+</button>
<button onClick={() => setFontSize(fontSize - 1)}>a-</button>
<input
type="text"
value={text}
onChange={event => {
setText(event.target.value);
}}
/>
</div>
)}
</div>
);
}
I am trying to show or hide a div in Reactjs using the state value in the CSS style option - display and I am using functions with hooks. I have a button and below the button a div. When i click the button i either want to hide or show the contents in the div based on whether it is currently shown or hidden.
This is the basic test code I have
import React, { useState } from "react";
function hide() {
return (
<div>
<Mycomp />
</div>
);
}
function Mycomp() {
const [dp, setDp] = useState("none");
return (
<form>
<button
onClick={() => {
setDp("block");
}}
>
Test
</button>
<div style={{ display: dp }}>Test</div>
</form>
);
}
export default hide;
I then use this hide component in my App.js file. When I click the button the new state is assigned but then the page re-renders and the initial state is loaded again almost immediately. How can I go by ensuring the new state is kept? Eventually I will create a function where if the div display or not based on the previous state.
The issue is that the button is inside a <form>. So any click on that button will submit the form and refresh the page.
Can I make a <button> not submit a form?
You need to add a type="button" to your <button>
import React, { useState } from "react";
function Hide() {
return (
<div>
<Mycomp />
</div>
);
}
function Mycomp() {
const [dp, setDp] = useState(false);
return (
<form>
<button
type="button"
onClick={() => setDp(!dp)}
>
Test
</button>
{dp && <div>Test</div>}
</form>
);
}
export default Hide;
Your code should be something like this, instead of using block and none as style we can use conditional JSX (which is more ideal approach) -:
function Mycomp(){
const[dp, toggleDp] = useState(false);
return(
<form>
<button onClick={()=>{toggleDp(!dp)}}>Test</button>
{dp && <div>Test</div>}
</form>
)
}
export default hide
A better implementation would be to have your state variable TRUE/FALSE value and based on it display the element using a conditional rendering, note e.preventDefault in the button handler to stop the refresh/redirect, here is a working snippet, also a codesandbox:
const { useState, useEffect } = React;
function App() {
return (
<div>
<Mycomp />
</div>
);
}
function Mycomp() {
const [dp, setDp] = useState(true);
return (
<form>
<button
onClick={(e) => {
e.preventDefault();
setDp(!dp);
}}
>
Test
</button>
{dp && <div>Test</div>}
</form>
);
}
ReactDOM.render(<App />, document.getElementById("react-root"));
<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="react-root"></div>
I am really wondering why getElementById is returing null, where as same element I am able to access using get elements by className. It will be great if somebody helps me to understand what is causing this.
Please find below code, its a functional react component where i am passing handleClose function as props, which closes the modal, it just not needed so i have put only Modal component code.
import React, { useRef, useEffect } from 'react'
const A11yModal = ({ handleClose }) => {
const focusClose = useRef(null)
var closeBtn_cls = document.getElementsByClassName("btn-close")
var closeIcon=document.getElementById('btnclose')
useEffect(() => {
focusClose.current.focus();
console.log("using Id",closeIcon);
console.log("using ClassName",closeBtn_cls);
}, [])
function onKeyPressed(e) {
if (e.keyCode === 27) {
handleClose()
}
}
return (
<div className="modal display-block">
<section className="modal-main" role="dialog"
aria-modal="true" onKeyDown={(e) => onKeyPressed(e)}>
<button className="btn-close" id="btnclose" onClick={(e) => handleClose(e)} ref={focusClose}>
X
</button>
<h1 id="modal_title" className="title" >Modal</h1>
<div id="full_description" className="description" aria-describedby="full_description">
<p>Description goes here.</p>
</div>
<button className="close_btn" id="closebtn" onClick={(e) => handleClose(e)}> Close </button>
</section>
</div>
)
}
export default A11yModal
var closeBtn_cls = document.getElementsByClassName("btn-close")
At the moment this line of code finishes running, closeBtn_cls will have nothing in it (assuming this is the first render and there is nothing else with that class name on the page). But closeBtn_cls is a live HTMLCollection. This array-like object has the peculiar property that it will be dynamically changed as elements are added to the DOM. So by the time the useEffect runs, the element has been added to the page and the collection updated.
getElementById does not return an HTMLCollection, so it does not update on the fly.
While that addresses the difference, you should also know that this is not the recommended way to do things in react. In react you should use refs to get a reference to the dom element. You seem to be aware of that, as you're used refs in your example, so i recommend just deleting the code that uses getElementsByClassName and getElementById.
Ok, so the reason that you see elements while using className is that on console the elements are evaluated when you expand objects which in this case is an HTMLCollection, so even though at the initial render there is no element present, after the execution of useEffect you will display the data into the console and by that the HTMLCollection is being initialized and the reference to the array is causes the values to be seen whereas while using the id you are directly returning a single element and there is no element present at the time of initial render.
const { useRef, useEffect } = React;
const A11yModal = ({ handleClose }) => {
const focusClose = useRef(null)
var closeBtn_cls = document.getElementsByClassName("btn-close")
var closeIcon=document.getElementById('btnclose')
console.log('class value', closeBtn_cls[0]);
useEffect(() => {
focusClose.current.focus();
console.log("using Id",closeIcon);
console.log("using ClassName",closeBtn_cls);
}, [])
function onKeyPressed(e) {
if (e.keyCode === 27) {
handleClose()
}
}
return (
<div className="modal display-block">
<section className="modal-main" role="dialog"
aria-modal="true" onKeyDown={(e) => onKeyPressed(e)}>
<button className="btn-close" id="btnclose" onClick={(e) => handleClose(e)} ref={focusClose}>
X
</button>
<h1 id="modal_title" className="title" >Modal</h1>
<div id="full_description" className="description" aria-describedby="full_description">
<p>Description goes here.</p>
</div>
<button className="close_btn" id="closebtn" onClick={(e) => handleClose(e)}> Close </button>
</section>
</div>
)
}
ReactDOM.render(<A11yModal />, document.getElementById('app'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.3/umd/react-dom.production.min.js"></script>
<div id="app" />