ReactJS removing item from array removes all other elements - javascript

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.

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>

Rendering elements from an array of objects cause them to flicker on the screen and disappear

What is the problem?
I have a functional component that should render a list of player stats based on what team the user selects. The functionality of getting the data works and when I console log the state array using a useEffect I get an array with data inside it, but when I try to render the objects in html so you can see them on the screen sometimes they flicker on and then disappear, sometimes nothing happens at all.
What I've tried
I've tried using both a state array and just an ordinary variable array to see if that makes any difference. I've tried using .forEach and just a for loop to see if that would work. I've messed around with how I store the data and just trying to use a simple array instead of an object nothing so far has managed to get it rendered. As a note it is clear that the component does render as the div (className = Player-Stats) that contains the .map function is visible when inspected.
Thanks for any help and suggestions, I've spent days on this one functionality because the NHL api stores it's data super weirdly and you need to do all kinds of things to get the data you want. I didn't want to spam this question with tons of my code so if you need anything else like the parent components please ask and I can provide them.
Code Snippets
Landing Page
import { useState } from 'react';
import '../CSS/LandingPage.css';
import Instruction from './Instruction';
import LeagueLeaders from './LeagueLeaders';
import NavBar from './NavBar';
import TeamSelector from './TeamSelector';
import TeamStandings from './TeamStandings';
function LandingPage() {
const [teamSelected, setTeamSelected] = useState(false);
const [listOfTeams, setListOfTeams] = useState([]);
return (
<div className = 'Landing-Page-Container'>
<NavBar/>
<div className = 'Stats-Standings-Container'>
<div className = 'Team-Select-Container'>
<TeamSelector toggleStats = {setTeamSelected} setListTeams = {setListOfTeams}/>
</div>
<div className = 'Stats-Container'>
<LeagueLeaders showStats = {teamSelected} getListTeams = {listOfTeams} />
</div>
<div className = 'Standings-Container'>
<TeamStandings/>
</div>
</div>
</div>
);
}
export default LandingPage;
LeagueLeaders code
import { useState } from 'react';
import {FaChevronLeft, FaChevronRight} from 'react-icons/fa';
import '../CSS/LeagueLeaders.css';
import Instruction from './Instruction';
import LeaderStats from './LeaderStats.js';
function LeagueLeaders({showStats, getListTeams}){
var title = ['Skaters', 'Goalies', 'Defencemen'];
var [titleNo, setTitleNo] = useState(0);
var goalieOptions = ['GAA', 'SV%', 'SHUTOUTS'];
var nonGoalieOptions = ['POINTS', 'GOALS', 'ASSISTS'];
function selectPosition(task){
if(task === '+' && titleNo <2){
setTitleNo(titleNo+1);
}else if (task === '+' && titleNo == 2){
setTitleNo(0);
}else if(task === '-' && titleNo >0){
setTitleNo(titleNo-1);
}else{
setTitleNo(2);
}
}
return(
<div className = 'Leaders-Container'>
<div className = 'Leaders-Title'>
<FaChevronLeft className = 'toggleArrow' size = {24} color = 'white' onClick={() => selectPosition('-')}/>
<h1>{title[titleNo]}</h1>
<FaChevronRight className = 'toggleArrow' size = {24} color = 'white' onClick={() => selectPosition('+')}/>
</div>
<div className = 'Leaders-Selection-Container'>
<div className = 'Stat-Select-1'>
<p>{titleNo == 1 ? goalieOptions[0]: nonGoalieOptions[0]}</p>
</div>
<div className = 'Stat-Select-2'>
<p>{titleNo == 1 ? goalieOptions[1]: nonGoalieOptions[1]}</p>
</div>
<div className = 'Stat-Select-3'>
<p>{titleNo == 1 ? goalieOptions[2]: nonGoalieOptions[2]}</p>
</div>
</div>
<div className = 'Leaders-Stats-Container'>
{showStats ? <LeaderStats playerPos = {titleNo} teams = {getListTeams}/> : <Instruction/>}
</div>
</div>
);
}
export default LeagueLeaders;
TeamSelector component code
import '../CSS/TeamSelector.css';
import { useEffect, useState } from "react";
import teamDetail from "../Assets/teamDetail";
function TeamSelector( {toggleStats, setListTeams}) {
const [listOfTeams, setListOfTeams] = useState([]);
const [listOfURL, setListOfURL] = useState([]);
const [selectedTeams, setSelectedTeams] = useState([]);
useEffect(()=>{
console.log(selectedTeams);
setListTeams(selectedTeams);
}, [selectedTeams])
function handleClick(e){
const selectedTeamsCopy = [...selectedTeams];
if(selectedTeams.includes(e.currentTarget.id)){
if(selectedTeams.length <= 1){
toggleStats(false);
selectedTeamsCopy.splice(selectedTeamsCopy.indexOf(e.currentTarget.id, 1), 1);
setSelectedTeams(selectedTeamsCopy);
}else{
selectedTeamsCopy.splice(selectedTeamsCopy.indexOf(e.currentTarget.id, 1), 1);
setSelectedTeams(selectedTeamsCopy);
}
}else {
if(selectedTeams.length === 0){
toggleStats(true);
selectedTeamsCopy.push(e.currentTarget.id);
setSelectedTeams(selectedTeamsCopy);
}else{
selectedTeamsCopy.push(e.currentTarget.id);
setSelectedTeams(selectedTeamsCopy);
}
}
if(e.target.style.opacity === '1'){
e.target.style.opacity = '25%';
}else {
e.target.style.opacity = '100%';
}
}
return (
<div className = 'Team-Logo-Container'>
{teamDetail.map((Teams)=>(
<div>
<img onClick={(e) => handleClick(e)} key = {Teams.ID} id = {Teams.ID} alt = {Teams.Name +' Logo'} src = {Teams.URL} className = 'logo'/>
</div>
))}
</div>
);
}
export default TeamSelector;
Array of objects layout
[{ ID: "8480003", Name: "Jesper Boqvist", Points: "1", … }, { ID: "8475193", Name: "Tomas Tatar", Points: "10", … }, etc. etc.]
Rendering the array
return(
<div className = 'Player-Stats'>
{triggerStats ? listOfStats.map((d)=>{
return <p className = 'Stats' key={d.ID}>{d.ID}</p>}
) : <p className = 'Stats'> Sorry theres no available data</p>}
</div>
)
LeaderStats component script (Where the rendering issue is)
import { useEffect, useState, useRef } from "react";
import '../CSS/LeaderStats.css';
function LeaderStats({playerPos, teams}){
const isInitialMount = useRef(true);
const [listOfStats, setListOfStats] = useState([]);
const [triggerStats, setTriggerStats] = useState(false);
//If it's not the first render and the listOfStats state has changed it will render the stats of the players
useEffect(()=>{
if(!isInitialMount.current){
console.log(listOfStats);
setTriggerStats(true);
}
}, [listOfStats])
//When teams prop changes, run the function to get player data from API
useEffect(()=>{
if (isInitialMount.current) {
isInitialMount.current = false;
}else{
if(teams.length !== 0){
getPlayerIDs(teams);
}else{
setTriggerStats(false);
}
}
},[teams])
//This function runs all the axios calls and gathers data from multiple endpoints and saves it to an array
function getPlayerIDs(teamID){
const axios = require('axios');
var playerList=[];
var tempObj;
teamID.forEach(d =>
axios.get(`https://statsapi.web.nhl.com/api/v1/teams/${parseInt(d)}/roster`).then(res => {
//If user has selected the forward position filter roster by that player position and get the stats and save to temp array.
if(playerPos === 0){
res.data.roster.filter(obj => obj.position.type === 'Forward').map(e=>
axios.get(`https://statsapi.web.nhl.com/api/v1/people/${e.person.id}/stats?stats=statsSingleSeason&season=20212022`).then(res =>{
if(typeof res.data.stats[0].splits[0] !== 'undefined'){
if(playerPos !== 1 ){
tempObj = { ID: `${e.person.id}`, Name: `${e.person.fullName}` ,Points: `${res.data.stats[0].splits[0].stat.points}`, Goals: `${res.data.stats[0].splits[0].stat.goals}`, Assists: `${res.data.stats[0].splits[0].stat.assists}`};
playerList.push(tempObj);
}
}
})
);
}
//If user has selected the goalie position filter roster by that player position and get the stats and save to temp array.
else if(playerPos === 1){
res.data.roster.filter(obj => obj.position.type === 'Goalie').map(e=>
axios.get(`https://statsapi.web.nhl.com/api/v1/people/${e.person.id}/stats?stats=statsSingleSeason&season=20212022`).then(res =>{
if(typeof res.data.stats[0].splits[0] !== 'undefined'){
if(playerPos !== 1 ){
tempObj = { ID: `${e.person.id}`, Name: `${e.person.fullName}`, GAA: `${res.data.stats[0].splits[0].stat.goalAgainstAverage}`, SavePercentage: `${res.data.stats[0].splits[0].stat.savePercentage}`, Shutouts: `${res.data.stats[0].splits[0].stat.shutouts}`};
playerList.push(tempObj);
}
}
})
);
}
//If user has selected the defense position filter roster by that player position and get the stats and save to temp array.
else if(playerPos === 2){
res.data.roster.filter(obj => obj.position.type === 'Defenseman').map(e=>
axios.get(`https://statsapi.web.nhl.com/api/v1/people/${e.person.id}/stats?stats=statsSingleSeason&season=20212022`).then(res =>{
if(typeof res.data.stats[0].splits[0] !== 'undefined'){
if(playerPos !== 1 ){
tempObj = { ID: `${e.person.id}`, Name: `${e.person.fullName}` ,Points: `${res.data.stats[0].splits[0].stat.points}`, Goals: `${res.data.stats[0].splits[0].stat.goals}`, Assists: `${res.data.stats[0].splits[0].stat.assists}`};
playerList.push(tempObj);
}
}
})
);
}
})
);
//Set the state to the temp array that will be used to render the stats
setListOfStats(playerList);
}
return(
<div className = 'Player-Stats'>
{triggerStats ? listOfStats.map((d)=>{
return <p className = 'Stats' key={d.ID}>{d.ID}</p>}
) : <p className = 'Stats'> Sorry theres no available data</p>}
</div>
)
}
export default LeaderStats;
I have managed to figure out the issue so I'll post it here for those of you who in the future might find yourself in the same position as me without being able to find an answer. Turns out the way I was setting setListOfStats(playerList); made the state change without me realising it and by the time it got to rendering the .map there was nothing actually there as console.log takes a snapshot of the state at the time of the console.log. The solution (which I had experimented with before a few times but couldn't get working) was to remove the temprorary array of playerList completely and change the following code playerList.push(tempObj); to setListOfStats(listOfStats => [...listOfStats, tempObj]) thise line of code essentially sets the list of stats as I did before but it captures the previous state as well. This comes up with another issue of when you want to remove some data from the list but that's not related to this question. Hopefully someone can make use of this answer in the future.

Nested component does not render properly in Svelte/Sapper

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

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