How to dynamically change style on conditionally rendered element in Svelte - javascript

Testing out SvelteKit 1.0 with a basic todo app but I'm unable to make the text strikethrough conditionally. When the user clicks the checkbox on the left of the TodoItem the text should change style to become strikethrough. I logged out the values for completed and textDecoration whenever they change and so I'm fairly confident the data is being passed correctly, the style just isn't changing for some reason.
I tried following the example laid out in the docs here as closely as possible but it's still not working. Here is the code
+page.svelte (Home)
<script lang="ts">
import SearchBar from '$components/SearchBar.svelte';
import TodoItem from '$components/TodoItem.svelte';
let todos = [
{
id: 0,
text: 'first task',
completed: false
}
];
const addTodos = (taskText: string) => {
if (!taskText) return;
todos = [
...todos,
{
id: todos.length,
text: taskText,
completed: false
}
];
console.log(todos);
};
const checkTodo = (id: number) => {
todos[id].completed = !todos[id].completed;
};
</script>
<h1>to-do app</h1>
<SearchBar onAdd={(str) => addTodos(str)} />
{#each todos as todo}
<TodoItem
text={todo.text}
bind:completed={todo.completed}
onCheck={() => checkTodo(todo.id)}
onDelete={() => console.log(`Delete ` + todo.id)}
/>
{/each}
TodoItem.svelte
<script lang="ts">
export let text: string;
export let onDelete: () => void;
export let onCheck: () => void;
export let completed: boolean;
$: textDecoration = completed ? 'line-through' : 'none';
//console log when value changes
$: textDecoration, console.log(textDecoration);
$: completed, console.log(completed);
</script>
{#if text}
<div class="todoContainer">
<input
on:click={() => {
onCheck();
}}
id={`todo-checkbox-` + text}
type="checkbox"
/>
<div style:textDecoration style:textAlign="left">
{text}
</div>
<div on:click={onDelete} on:keypress={onDelete}>x</div>
</div>
{/if}
<style>
.todoContainer {
display: flex;
justify-content: space-between;
align-items: center;
width: 100%;
height: 50px;
border: 1px solid black;
border-radius: 5px;
margin: 10px 0;
}
</style>

Use dash instead of camelCase with the style:directive (an example seems to be missing in the docs and tutorial)
style:text-decoration={textDecoration}

Related

Updating State via onClick, in a Component

I'm new to StackOverflow and looking forward to contributing back to the community!
My first question, I am trying to make some squares change color on the screeen, after an onClick event. I'm nearly there, but I keep getting an error when I try to update the state, which then should updates the color. Please could you let me know what I'm doing wrong?
App.js
import React from "react"
import boxes from "./boxes"
import Box from "./Box"
export default function App() {
const [squares, setSquares] = React.useState(boxes)
function changeOn() {
console.log(squares)//just checking I'm getting the full object
setSquares({
id: 1, on: false //this was previously [...prev], on: !squares.on
})
}
const squaresElement = squares.map(props => (
<Box key={props.id} on={props.on} onClick={changeOn} />
))
return (
<main>
{squaresElement}
</main>
)
}
Box.js
import React from "react"
export default function Box (props) {
const styles= props.on ? {backgroundColor: "#222222"} : {backgroundColor: "none"}
return (
<div className="box" style={styles} onClick={props.onClick}></div>
)
}
Boxes.js
export default [
{
id: 1,
on: true
},
{
id: 2,
on: false
},
{
id: 3,
on: true
},
{
id: 4,
on: true
},
{
id: 5,
on: false
},
{
id: 6,
on: false
},
]
I hope somebody can easily spot what's wrong here?
I was expecting to see the color of the top left box change to a different color, after a click.
There are two issues:
setSquares needs the whole array, so you need to give it a new squares array
The styling back to None does not work always. better to give it the white color again
Please find the codesandbox
export default function App() {
const [squares, setSquares] = React.useState(boxes);
function changeOn(id) {
setSquares(
squares.map((square) => {
return { ...square, on: id === square.id ? !square.on : square.on };
})
);
}
const squaresElement = squares.map((props) => (
<Box key={props.id} on={props.on} onClick={() => changeOn(props.id)} />
));
return <main>{squaresElement}</main>;
}
And in Box.js
const styles = props.on
? { backgroundColor: "#222222" }
: { backgroundColor: "#fff" };
You're calling setSquares and passing it a single object instead of an array.
On the next render squares.map(...) blows up because squares is the object, and the object doesn't have a map method.
// after this call squares is just this one object
setSquares({
id: 1, on: false
})
Here's a possible implementation that pushes the on/off responsibility into the box component itself.
// generates a list of items (faking your boxes.js)
const boxes = Array.from({length: 9}, (_, id) => ({ id }));
// container element to render the list
function Boxen ({ items }) {
return (
<div className="container">
{items.map((item, idx) => (
<Box item={item} key={idx} />
))}
</div>
)
}
// component for a single box that can toggle its own on/off state
function Box ({item}) {
const [active, setActive] = React.useState();
return (
<div onClick={() => setActive(!active)} className={active ? 'active' : ''}>{item.id}</div>
)
}
ReactDOM.render(<Boxen items={boxes}/>, document.getElementById('root'));
.container {
display: grid;
grid-template-columns: repeat(3, 100px);
grid-template-rows: repeat(3, 100px);
gap: 1em;
}
.container > * {
display: flex;
justify-content: center;
align-items: center;
background: skyblue;
}
.container > .active {
background: slateblue;
color: white;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.14.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.14.0/umd/react-dom.production.min.js"></script>
<div id="root"></div>

How to select only one element from several elements in React (React,typescript)

In this project, React, TypeScript and ant design are used. In a part of the project, only one box out of three boxes should be selected. I have used "useState" and toggle, but when I select one, they are all selected together when only one should be selected. I am using React version 18.2.0
I would be grateful if you could guide me.
allBox{
display: flex;
align-items: center;
justify-content: space-between;
width: 700px
}
.box{
width: 34px;
height: 34px;
border: 3px solid yellow;
background: green;
color:blue;
}
.box.active{
border: 3px solid black;
background: red;
}
<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>
import React, { useState } from "react";
function MyBox() {
const boxes = [
{
id: 1,
type: "1",
},
{
id: 2,
type: "2",
},
{
id: 3,
type: "3",
},
];
const [boxSelect, setBoxSelect] = useState(false);
const handleSelect = () => {
setBoxSelect(!boxSelect);
};
return (
<div>
<div className='allBox'>
{boxes.map((box) => {
return (
<div className={`${box} ${boxSelect && 'active'}`} key={box.id} onClick={handleSelect}>
<p>{box.type}</p>
</div>
);
})}
</div>
</div>
);
}
export default MyBox;
It's because all your boxes share the same const [boxSelect, setBoxSelect] = useState(false); so if you click on one of them, they will all be selected.
You have two solutions:
create a children component with only:
function mySubBox(box) {
const [boxSelect, setBoxSelect] = useState(false);
const handleSelect = () => {
setBoxSelect(!boxSelect);
};
return (
<div className={`${box} ${boxSelect && 'active'}`} key={box.id} onClick={handleSelect}>
<p>{box.type}</p>
</div>
);
}
And the parent loops through it:
{boxes.map((box, i) => <mySubBox box={box} key={i}/>)}
another solution would be to keep only one component and store the selected boxes in an array: if I click on the 2nd box, my selected array is [2], and you add/remove the item inside the array.

How can align my elements on the left side of the page?

I am a new programmer and I recently started following a music-player tutorial, and am experiencing a couple of styling issues with it.
Project Overview:
As previously described, it is a music-player project made with create-react-app. The objective is to click the image of the song of your choosing, and for the song to be played for you.
The Problem:
Currently they are in a column, aligned in the center. How can I line the images of the songs on the left side of the page? I tried doing a couple of things on my css file named Turkish.css(shown below) like:
align-items: flex-start
justify-items: flex-start;
However they did not do anything.
The Code:
Turkish.js (the music-player file):
import React, { Component,useRef, setStatus, status } from 'react';
import './Turkish.css';
import turk1 from "./music/turk1.mp3";
import turk2 from "./music/turk2.mp3"
import turk3 from "./music/turk3.mp3"
import turk4 from "./music/turk4.mp3"
export default function Turkish() {
const data = [
{ imgSrc: 'turk1.png', audioSrc: turk1 },
{ imgSrc: 'turk2.png', audioSrc: turk3 },
{ imgSrc: 'turk3.png', audioSrc: turk4 },
{ imgSrc: 'turk4.png', audioSrc: turk2 },
];
return (
<div className='Turkish'>
<ol>
{data.map(({ imgSrc, audioSrc }) => (
<MediaComponent imgSrc={imgSrc} audioSrc={audioSrc} />
))}
</ol>
</div>
);
}
const MediaComponent = ({ imgSrc, audioSrc }) => {
const audioRef = useRef(null);
const toggleAudio = () =>
audioRef.current === null
? console.log("Audio component is not loaded yet.")
: audioRef.current.paused
? audioRef.current.play()
: audioRef.current.pause();
return (
<ol>
<img src={imgSrc} onClick={toggleAudio} />
<audio
ref={audioRef}
src={audioSrc}
onLoad={() => setStatus({ ...status, isLoaded: true })}
onPlay={() => setStatus({ ...status, isPlaying: true })}
onPause={() => setStatus({ ...status, isPlaying: false })}
onError={() => setStatus({ ...status, error: true })}
/>
</ol>
);
};
Turkish.css
.Turkish ol {
cursor: grab;
border: solid;
border-color: #303030;
border-width: 0.01px ;
border-left: transparent;
border-right: transparent;
border-bottom: transparent;
}
Let me know what you think!
Thank You,
-Zpo

React Styled Component doesn't show correct output

I've changed my style in StyledButton tag but it doesn't reflect on webpage. Can you help what is wrong in this code
import React, { Component } from 'react';
import './App.css';
//import Radium, {StyleRoot} from 'radium';
import Person from './Person/Person';
import { render } from 'react-dom';
import styled from 'styled-components'
const StyledButton = styled.button `
background-color: ${props => props.alt ? 'red' : 'green'}; //Here I have define these property which is not reflecting in output
color: white;
font: inherit;
border: 1px solid blue;
padding: 8px;
cursor:pointer;
&:hover:{
background-color:${props => props.alt ? 'salmon' : 'green'}; //Hover Function is also not working
color:black;
}`;
class App extends Component {
state ={
persons : [
{id:'12', name: 'Max', age: 28},
{id:'21', name: 'Manu', age:29},
{id:'33', name: 'Nikhil', age:23}
]};
nameChangeHandler = (event, id) => {
const personIndex = this.state.persons.findIndex(p=>{
return p.id===id;
});
const person = {
... this.state.persons[personIndex]
};
person.name = event.target.value;
const persons =[...this.state.persons];
persons[personIndex]=person;
this.setState({ persons: persons
});
}
deletePersonHandler = (personIndex) =>{
//const persons = this.state.persons.slice();
//const persons = this.state.persons
const persons = [... this.state.persons];
persons.splice(personIndex,1);
this.setState({persons: persons})
}
togglePersonsHandler = ()=> {
const doesShow = this.state.showPersons;
this.setState({showPersons: !doesShow});
}
render()
{
let persons = null;
if(this.state.showPersons)
{
persons = (
<div>
{ this.state.persons.map((person, index) =>{
return <Person
click = { () => this.deletePersonHandler(index) }
name={person.name}
age={person.age}
key= {person.id}
changed={(event)=> this.nameChangeHandler(event, person.id)}/>
})}
</div>
);
//StyledButton.backgroundColor= 'red';
}
let classes = []
if(this.state.persons.length<=2)
{
classes.push('red');
}
if(this.state.persons.length<=1)
{
classes.push('bold');
}
return (
<div className = "App">
<h1>Hi there this is my first react application</h1>
<p className= {classes.join(' ')}>Hi this is really working</p>
<StyledButton
alt ={ this.state.showPersons }
onClick = {this.togglePersonsHandler}>Toggle Persons</StyledButton>
{ persons }
</div>
);
}
}
export default App;
Code just toggle the names and ages when user click on button and delete the names when click on the paragraph and adding certain classes these are all works fine.
I'm using styled component package on toggle button and it is not working properly, I don't why Please let me know if you understand
You've defined alt to be a transient prop, i.e. $alt, but you don't pass that prop to the StyledButton.
You've also a typo in your hover selector, there's a trailing colon (:): &:hover: should be :hover (the leading parent node selector & is also unnecessary).
const StyledButton = styled.button `
background-color: ${props => props.$alt ? 'red' : 'green'};
color: white;
font: inherit;
border: 1px solid blue;
padding: 8px;
cursor: pointer;
:hover {
background-color:${props => props.$alt ? 'salmon' : 'green'};
color:black;
}
`;
...
<StyledButton
$alt={this.state.showPersons} // <-- pass transient prop
onClick={this.togglePersonsHandler}
>
Toggle Persons
</StyledButton>
This was introduced in v5.1. If you didn't intend to declare a transient prop or you aren't on v5.1 or newer, then simply remove the $ and use the alt prop.
const StyledButton = styled.button `
background-color: ${props => props.alt ? 'red' : 'green'};
color: white;
font: inherit;
border: 1px solid blue;
padding: 8px;
cursor: pointer;
:hover {
background-color:${props => props.alt ? 'salmon' : 'green'};
color:black;
}
}`;
...
<StyledButton
alt={this.state.showPersons} // <-- use alt prop
onClick={this.togglePersonsHandler}
>
Toggle Persons
</StyledButton>
Demo

Strikethrough a Paragraph in React.js via onClick?

I'm pretty much ready to rip my hair out. So my final project in my Javascript class is an experimental thing with learning React.js, where you do a basic todo list. I got all that done and working, and I can have it add things properly. But my final hurdle is making it so that onclicking the printed paragraph from the button will cause them to give the printed paragraphs the strikethrough property, which can be undone by clicking on it again.
I've looked up everywhere, I've tried other examples from here, and nothing I can think of gets the strikethrough to take place. I tried a basic Javascript function that would do what I wanted if this was an HTML/non-react file, but it breaks the react page when I try to plop it in. So I spent a stupidly long time on a tutorial trying to figure out what to do, and I maybe figured out the step in the right direction? But I still can't get anything to happen and I don't know how to establish an onclick to this.
import React from 'react';
import './App.css';
class App extends React.Component {
setCurrentToDoItem = (toDoItem) => {
console.log("toDoItem", toDoItem);
this.setState({
currentToDoItem: toDoItem
});
};
saveToDoListItem = (toDoItem) => {
this.setState({
toDoList: [...this.state.toDoList,
toDoItem]
});
};
constructor(props) {
super(props);
this.state = {
currentToDoItem: null,
toDoList: [],
strikeThrough: []
};
}
render() {
return (
<div>
<h1>To Do List</h1>
<label>To Do Item: </label>
<input
onChange={(event) => this.setCurrentToDoItem(event.target.value)}>
</input>
<button onClick={() => this.saveToDoListItem(this.state.currentToDoItem)}>
<p>Add Item</p>
</button>
<p>{this.state.currentToDoItem}</p>
<div>
<p>To Do Items</p>
{
this.state.toDoList.map((item, index) => <p key={index}>{item} </p>)
}
</div>
</div>
);
}
}
export default App;
This is my App.js code. As you can see, everything else should work fine, but I have no clue how to add a strikethrough effect to what would result from the this.state.toDoList.map((item, index) => <p key={index}>{item} </p>) bit like I would with a function in normal javascript. How do I make the printed lines strikethrough via onclick, and then how do I undo that by clicking on it again? (I assume with a second onclick) I really just need to know how to get a working strikethrough with this, as everything else is pretty much working.
One of the most comfortable ways to do that is as advised in comments. A really quick way to implement this is to toggle class list. In the code bellow, I added a function crossLine which toggles class name "crossed-line" and adds event listener on mapped to-dos (in render function). Then in your App.css add a line
.crossed-line {
text-decoration: line-through;
}
Here's your edited component code.
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
currentToDoItem: null,
toDoList: [],
strikeThrough: []
};
}
setCurrentToDoItem = toDoItem => {
this.setState({
currentToDoItem: toDoItem
});
};
saveToDoListItem = toDoItem => {
this.setState({
toDoList: [...this.state.toDoList, toDoItem]
});
};
crossLine = event => {
const element = event.target;
element.classList.toggle("crossed-line");
};
render() {
return (
<div>
<h1>To Do List</h1>
<label>To Do Item: </label>
<input
onChange={event =>
this.setCurrentToDoItem(event.target.value)
}
/>
<button
onClick={() =>
this.saveToDoListItem(this.state.currentToDoItem)
}
>
<p>Add Item</p>
</button>
<p>{this.state.currentToDoItem}</p>
<div>
<p>To Do Items</p>
{this.state.toDoList.map((item, index) => {
return (
<p onClick={this.crossLine} key={index}>
{item}{" "}
</p>
);
})}
</div>
</div>
);
}
}
As commented, you will have to keep a handle click and add class to add strikethrough using CSS.
For this I have updated your JSX to:
<p onClick={ () => this.handleClick(index) } className={ item.isComplete ? 'completed' : '' } key={index}>{item.value} </p>
and the signature of toDoItem from string to an object:
{
value: string;
isComplete: boolean
}
and based on this flag, I'm adding class.
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
currentToDoItem: null,
toDoList: [],
strikeThrough: []
};
this.setCurrentToDoItem = this.setCurrentToDoItem.bind(this);
this.saveToDoListItem = this.saveToDoListItem.bind(this);
this.handleClick = this.handleClick.bind(this);
}
setCurrentToDoItem(toDoItem) {
this.setState({
currentToDoItem: toDoItem
});
}
saveToDoListItem(toDoItem) {
this.setState({
toDoList: [...this.state.toDoList, {
value: toDoItem,
isComplete: false
}]
});
}
handleClick(index) {
const {
toDoList
} = this.state;
toDoList[index].isComplete = !toDoList[index].isComplete;
this.setState({
toDoList
});
}
render() {
return (
<div>
<h1>To Do List</h1>
<label>To Do Item: </label>
<input
onChange={(event) => this.setCurrentToDoItem(event.target.value)}>
</input>
<button onClick={() => this.saveToDoListItem(this.state.currentToDoItem)}>
<p>Add Item</p>
</button>
<p>{this.state.currentToDoItem}</p>
<div>
<p>To Do Items</p>
{
this.state.toDoList.map((item, index) =>
<p onClick={ () => this.handleClick(index) } className={ item.isComplete ? 'completed' : '' } key={index}>{item.value} </p>)
}
</div>
</div>
);
}
}
ReactDOM.render( < App / > , document.querySelector("#app"))
body {
background: #20262E;
padding: 20px;
}
#app {
background: #fff;
border-radius: 4px;
padding: 20px;
transition: all 0.2s;
}
.completed {
text-decoration: line-through;
}
<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="app"></div>
Check out this solution https://codesandbox.io/s/crazy-kare-go2vf
I have modified your code to achieve the required functionality.
This code does exactly what you want.
Update : Created a TODO fiddler code using React Hooks for modern code approach.
const initialState = {
items: [
{ text: "Learn JavaScript", done: false },
{ text: "Learn React", done: false },
{ text: "Play around in JSFiddle", done: true },
{ text: "Build something awesome", done: true }
]
};
function appReducer(state, action) {
switch (action.type) {
case 'ITEM_STATUS_CHANGE':{
let affected = state.items.slice();
affected[action.index].done = !affected[action.index].done;
return Object.assign({}, state, { items: affected });
}
case 'ADD_ITEM_TO_LIST':{
let affected = state.items.slice();
affected.push({ text: action.data, done : false})
return Object.assign({}, state, { items: affected });
}
default:
throw new Error();
}
}
function TodoApp(props){
const [state, dispatch] = React.useReducer(appReducer, initialState);
return (
<div>
<h2>Todos:
<input type="text" id="todoTextItem"/>
<button
onClick={()=>{
dispatch({
type: 'ADD_ITEM_TO_LIST',
data: window.todoTextItem.value
})
}}
>Add Item</button>
</h2>
<ol>
{state.items.map((item, index) => (
<li key={index}>
<label>
<input
type="checkbox"
checked={item.done}
onChange={()=>{
dispatch({
type: 'ITEM_STATUS_CHANGE',
index: index,
})
}}
/>
<span className={item.done ? "done" : ""}>{item.text}</span>
</label>
</li>
))}
</ol>
</div>
);
}
ReactDOM.render(<TodoApp />, document.querySelector("#app"))
and in CSS
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;
}
.done {
color: rgba(0, 0, 0, 0.3);
text-decoration: line-through;
}
input {
margin-right: 5px;
}
Explanation:
Basically i am creating a list with done boolean flag which is false by default, which helps to identify if the TODO items added to the list is finished or not using reducers. With that logic class .done is toggled. You can change the code according to your need by segregating TODO list from done list items while setting state
This is a Unit testable code by creating Jest snapshot. Never to manipulate DOM directly, which will defeat the purpose of React's snapshot testing.
Old fiddle code using Class Component.
Use this to compare and learn modern hooks concepts from class based.

Categories