Nested component does not render properly in Svelte/Sapper - javascript

I have three files inside a slug. I use slug parameters as directory name.
The problem I am having is everything except the each loop in taglist. For some reason it does not receive the prop tagList. Any help would be appreciated.
index.svelte
<script context="module">
export function preload({ params }, { user }) {
let [id, slug] = [params.id, params.slug];
return { id, slug };
}
</script>
<script>
import Editor from "../../../_components/Editor.svelte";
import Topics from "./Topics.svelte";
import { stores } from "#sapper/app";
export let id;
export let slug;
const { session } = stores();
</script>
<svelte:head>
<title />
</svelte:head>
<div class="editor-page">
<div class="container page">
<div class="row">
<div class="col-md-8 offset-md-2 col-xs-12">
<Topics {id} {slug} />
{#if $session.user}
<Editor />
{/if}
</div>
</div>
</div>
</div>
Topics.svelte
<script>
import { onMount } from "svelte";
import * as api from "api.js";
import "bytemd/dist/index.min.css";
import TagList from "../../../_components/_TagList.svelte";
export let id;
export let slug;
let topics = [];
let title = "";
let tagList = [];
let value = "";
let Viewer = null;
onMount(async () => {
const bytemd = await import("bytemd");
Viewer = bytemd.Viewer;
const response = await api.get(
`t/${id}/${slug}`,
localStorage.getItem("jwt")
);
console.log(response);
if (response.topic) {
topics = response.topic;
title = response.title;
value = topics[0].description;
for(let i= 0; i < response.tags.length; i++) {
tagList.push(response.tags[i]);
}
}
});
</script>
<div>
<h3>{title}</h3>
<hr/>
<svelte:component this={Viewer} {value} />
<TagList {tagList} />
</div>
_TagList.svelte
<script>
export let tagList;
console.log(tagList);
</script>
<ul>
{#each tagList as tag}
<p>hello</p>
<li>{tag.name}</li>
{/each}
</ul>

In Svelte, updates are only triggered with an assignment.
In your case that means that when the component is rendered it will render an empty taglist (tagList = []).
Now in onMount you do taglist.push, but as said earlier, this doesn't trigger an update (remember that this function is called after the component has mounted) because it is not an assignment.
There are four ways to fix it in your case:
after the for loop you do tagList = tagList, this is an assignment and will trigger the update.
instead of doing the for loop use a mapping tagList = response.tags.map(tag => tag)
instead of doing the for loop you spread the tags into the taglist tagList = [...response.tags]
considering you don't do anything with the tags anyway, and tagList is empty and you don't seem to have any other way to update, just assign the tags to it directly tagList = response.tags
Of course your code might be simplified, if you actually do something with each tag before adding it to the list case 3 and 4 are not good options, for that scenario I would use the map option

Related

Div id is not defined in a function using react.js

import React from "react";
import "./profile.css";
const Notifications = () => {
function changeText() {
themebox.textContent =
"Nice";
}
function changeText2() {
themebox.textContent =
"Fair";
}
function changeText3() {
themebox.textContent = "Aggressive";
}
function changeText4() {
themebox.textContent =
"Threatening";
}
return (
<div className="notification-container">
<h3>Notifications</h3>
<div className="notif-picker">
<p className="Selected" onClick={changeText}>
Nice😘
</p>
<p onClick={changeText2}>Fair🕊</p>
<p onClick={changeText3}> Aggressive😈</p>
<p onClick={changeText4}>Threatening🤬</p>
</div>
<div className="theme-show-box">
<div className="theme-box" id="themebox"></div>
</div>
</div>
);
};
export default Notifications;
When i click on one of p tags it shows the text that i put in a function which is displayed in the div with classname "theme-box" and id "themebox". Everything seems to work fine, but i get an error in react saying themebox is not defined. Any idea how i can solve that error? :)
There's no variable named themebox in your javascript. Try this out instead, using react to manage state and functions to change the state on click.
// Get a hook function
const {useState} = React;
const Notifications = () => {
const [displayedText, setDisplayedText] = useState("");
const niceText = () => setDisplayedText("Nice");
const fairText = () => setDisplayedText("Fair");
const aggressiveText = () => setDisplayedText("Aggressive");
const threateningText = () => setDisplayedText("Threatening");
return (
<div className="notification-container">
<h3>Notifications</h3>
<div className="notif-picker">
<p className="Selected" onClick={niceText}>
Nice😘
</p>
<p onClick={fairText}>Fair🕊</p>
<p onClick={aggressiveText}> Aggressive😈</p>
<p onClick={threateningText}>Threatening🤬</p>
</div>
<div className="theme-show-box">
<div className="theme-box" id="themebox">{displayedText}</div>
</div>
</div>
);
};
// Render it
ReactDOM.createRoot(
document.getElementById("root")
).render(
<Notifications />
);
<div id="root"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/18.1.0/umd/react.development.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/18.1.0/umd/react-dom.development.js"></script>
React has it's own way of updating the DOM so it's generally a bad idea to try and manipulate the DOM directly. You should be using and updating state, and then using that state in the JSX.
By doing this you only really need one function which destructures the text content from the clicked element, and sets the state with that text. When the state changes that changed value is reflected in the JSX.
The (new) React documentation site has a good rundown on "how to think in React".
const { useState } = React;
function Example() {
// Initialise the state to an empty string
const [ text, setText ] = useState('');
// When any of the paragraphs is clicked
// destructure the textContent from that element
// and then set the state's new value. `e` is the
// event, and `target` is the element that fired the
// event ie. a paragraph element
function handleClick(e) {
const { textContent } = e.target;
setText(textContent);
}
return (
<div className="notification-container">
<h3>Notifications</h3>
<div className="notif-picker">
<p onClick={handleClick}>Nice 😘</p>
<p onClick={handleClick}>Fair 🕊</p>
<p onClick={handleClick}>Aggressive 😈</p>
<p onClick={handleClick}>Threatening 🤬</p>
</div>
<div className="theme-show-box">
<div className="theme-box">{text}</div>
</div>
</div>
);
}
ReactDOM.render(
<Example />,
document.getElementById('react')
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.2/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.2/umd/react-dom.production.min.js"></script>
<div id="react"></div>

enabling and disabling div element with javascript in Svelte

The following Svelte file produces a tree in which elements can be clicked.
Current behaviour
The problem I have is that when I click on the div-element, the element is focused, but the previous elements are not focused.
Expected behaviour
Only one div is active at the same time.
Trials
I've tried several re-writings of this code, but it seems like I need to keep track of some kind of history, to undo focus.
<script lang="ts">
import type { FrontendFile } from '$lib/front';
export let content: FrontendFile;
export let history: FrontendFile[];
let text: string;
function focusUnfocus() {
for (let i=0; i++; i < history.length) {
history[i].status.focused = false;
}
content.status.focused = !content.status.focused;
history.push(content);
if (content.status.focused) {
text = 'font-black';
} else {
text = '';
}
}
</script>
<div class="{text}" on:click={focusUnfocus}>
{content.name}
</div>
...
{#each content.children as sub}
<svelte:self bind:history bind:content={sub} />
{/each}
...
I solved it with the following
<script lang="ts">
import type { FrontendFile } from '$lib/front';
export let content: FrontendFile;
export let lastFocused: FrontendFile;
function focusUnfocus() {
if (lastFocused) {
lastFocused.status.focused = false;
}
content.status.focused = true;
lastFocused = content;
}
let text: string = '';
$: if (content.status.focused) {
text = 'font-black';
} else {
text = '';
}
</script>
<div class=" {text}" on:click={focusUnfocus}>
{content.name}
</div>
{#each content.children as sub}
<svelte:self bind:lastFocused bind:content={sub} />
{/each}
I switched to 1-object history and changed the binding in the top level index.svelte file as follows.
<script lang="ts">
export let lastFocused;
</script>
...
{#each filesystems as system}
<RenderTree {lastFocused} bind:content={system}
{/each}
...

ReactJS removing item from array removes all other elements

Following my app.js
import Form from "./Containers/Form";
import { useState, useCallback } from "react";
import List from "./Containers/List";
function App() {
let [items,setItems] = useState([]);
const handleAdd = useCallback((item)=>{
let newItems=[...items,item];
setItems(newItems)
},[items])
const onEdit=useCallback((odlv,newv)=>{
let newItems=items.map((item)=>{
if(item===odlv){
return newv;
}
return item
});
setItems(newItems)
},[items]);
const onDelete=(odlv)=>{
console.log("old",odlv)
let newItems=items.filter((item)=>{
return (item!==odlv)
});
setItems(newItems)
}
return ( <div className = "container" >
<Form onAdd={handleAdd}/>
<List items={[...items]} onEdit={onEdit} onDelete={onDelete} />
</div >
);
}
export default App;
And my list item.js
import InputBox from "./InputBox"
import { useMemo, useState, useCallback } from "react"
export default function Item(props){
let [edit,setEdit]=useState(false)
let [taskDetail,setTaskDetail]=useState(props.item)
let [error,setError]=useState('')
let handleSave=useCallback(()=>{
if(taskDetail==''){
setError('Please Enter Task Details')
}else{
if(taskDetail!==props.item)
{
setError('')
props.onEdit(props.item,taskDetail)
setEdit(false)
}
}
},[edit,taskDetail,error])
const handleTaskUpdate=useCallback((e)=>{
setTaskDetail(e.target.value)
},[])
let [titleDisplay,editButton,deleteButton]=useMemo(()=>{
if(edit){
return [<InputBox value={taskDetail} error={error} onChange={handleTaskUpdate}
/>,<button onClick={handleSave} className='btn btn-success '>Save</button>,<button onClick={()=>{setEdit(false)}} className='ms-1 btn btn-danger'>Cancel</button>]
}
return [<h5>{props.item}</h5>,<button onClick={()=>setEdit(true)} className='btn btn-secondary '>Edit</button>,<button className='ms-1 btn btn-danger' onClick={()=>{props.onDelete(taskDetail)}}>Delete</button>];
},[edit,taskDetail,error])
return<li className='list-group-item'>
<div className='row'>
<div className='col-md-6'>{titleDisplay}</div>
<div className='col-md-6'>
{editButton}{deleteButton}
</div>
</div>
</li>
}
When I click on delete, it works fine for last node deletion but when middle node is deleted it removed all other node expect the first one
like try adding
A, B, C, D
remove D will work fine
but remove B will remove C and D
What's going wrong?
the problem occurs because useMemo is missing dependencies. In this case props.item, props.onDelete, setEdit
If you don't pass these required arguments as dependencies useMemo will no recalculate what is inside the callback and will continue using the old momoized values, thus props.items was the same variable containing the same values even if you removed some of them.
When you delete one item and this variable is part of the dependencies, react knows that it's changed and useMemo is recalculated.

How do I output my tex from an array to the screen dynamically?

Hello I am new to React and building a quote generator. I want to pull out one quote at a time from my array and show it on the screen, however I can only seem to output each quote to the console.
I have:
1.Created an on click handler and function so that when the user clicks my quote array is targeted.
2. In this function I have created a variable to hold my random array index
3. I have console.logged the array index to see if every time the user clicks it the quote appears.
Component and function and click handler, as you can see the Quote Component should return the quote from the array in my opinion but nothing happens:
class Card extends Component {
state = {
quotes: ['"A dream doesn\'t become reality through magic; it takes sweat, determination and hard work."','"You GOT this!"','"To be or not to be that is the question"'];
changeQuoteHandler = (event) => {
const quotes = [...this.state.quotes];
const arrayIndex = quotes[Math.floor(Math.random() * quotes.length)]
console.log(arrayIndex);
this.setState({
quotes: quotes
})
};
render(){
return (
<div className="Card">
<div>
<h2>Random Quote Generator</h2>
<Quote className="QuoteStyle" quote={this.state.quotes.arrayIndex}/>
</div>
<div className="Flex">
<div>
<NewQuoteButton onClick={this.changeQuoteHandler}/>
</div>
</div>
</div>
)
}
};
export default Card;
Quote Componenet :
import React from 'react';
const Quote = (props) => {
return(
<p>{props.quote}</p>
)
};
export default Quot
I would like to print one quote at a time to the screen on click.
You are so close. You can store the arrayIndex that you generate in the state and use it to display the quote. The code would look like something below
class Card extends Component {
state = {
quotes: ['"A dream doesn\'t become reality through magic; it takes sweat, determination and hard work."','"You GOT this!"','"To be or not to be that is the question"'],
selectedIndex: 0,
}
changeQuoteHandler = (event) => {
const quotes = [...this.state.quotes];
const arrayIndex = Math.floor((Math.random() * 10) % quotes.length);
this.setState({
quotes: quotes,
selectedIndex: arrayIndex,
});
};
render(){
return (
<div className="Card">
<div>
<h2>Random Quote Generator</h2>
<Quote className="QuoteStyle" quote={this.state.quotes[this.state.selectedIndex]}/>
</div>
<div className="Flex">
<div>
<NewQuoteButton onClick={this.changeQuoteHandler}/>
</div>
</div>
</div>
)
}
};
export default Card;

Map function on object containing mutliple arrays

I have a props object, with a user comment array, and the userId array inside. Originally i only had the user comment array, and so i used the map function to style each comment individually. Now that i have two arrays inside my props object, is there a way to use the map function to style both the users comment and his id at the same time? Here is my attempt at it but it doesnt work:
import React from 'react'
import faker from 'faker'
const UserComment = (props)=> {
var commentData = props.map(props=> {
return(<StyleComment comment = {props.comment} key = {props.comment} author = {props.userIds} />)})
return(null)//commentData)
}
const StyleComment = (props) => {
// get time of comment
return(
<div className = 'comment'>
<a href="/" className= "avatar">
<img alt ="avatar" src= {faker.image.avatar()}/>
</a>
<div className = 'name'>
<a href ="/" className = "author">
{props.author}
</a>
<span className="metadata"> Today at 1.00pm </span>
<div className= 'content'>
<div className = 'text'>{props.comment}</div>
</div>
</div>
</div>
)
}
Here is the parent where the props are defined:
<UserComment comment = {this.state.usersComment} userIds = {this.props.userIds}/>
and here is a console.log of an example output for the props object:
You need to pass complete object to UserComment component,
<UserComment comment={this.state.usersComment} />
Then you can iterate like this,
const UserComment = (props)=> {
console.log(props.comment);
return props.comment.comment.map((comment,index) => {
return <StyleComment key={comment} comment={comment} author={props.comment.userIds[index]}/>
});
}
Demo
Note: Current array iteration and mapping is based on index, but you must have some relation between comment and userIds array to correctly map the data.

Categories