problem with useEffect and useState in fake chat app - javascript

My program is fake chat App, using useEffect, useState, it will emit a comment per 2 second and display all comments on each lesson. In the first render, it run correctly(emit a comment per 2 second), but in each re-render, it emit 2 comments per 2 second.
I try to console.log(newLessonComments[lessonId - 1].comments.length) after push a comment. And it only push 1 comment to the array.
What is the problem?
import { useState, useEffect } from 'react';
function Content() {
let lessons = [
{
id: 1,
title: 'Bài học 1'
},
{
id: 2,
title: 'Bài học 2'
},
{
id: 3,
title: 'Bài học 3'
}
];
const [lessonId, setLessonId] = useState(1);
const [lessonComment, setLessonComment] = useState(() => {
return lessons.map(lesson => {
return {
id: lesson.id,
comments: []
}
});
})
useEffect(() => {
const handleComment = ({ detail }) => {
setLessonComment(prev => {
const newLessonComments = prev.slice(0);
newLessonComments[lessonId - 1].comments.push(detail);
return newLessonComments;
});
}
window.addEventListener(`lesson${lessonId}`, handleComment);
return () => {
window.removeEventListener(`lesson${lessonId}`, handleComment);
}
}, [lessonId])
return (
<div>
<ul>
{lessons.map(lesson => {
return (
<li
key={lesson.id}
style={{
color: lessonId == lesson.id ? 'red' : '#333',
cursor: 'pointer'
}}
onClick={() => { setLessonId(lesson.id) }}
>
{lesson.title}
</li>
)
})}
</ul>
<div className="comments">
<ul>
{lessonComment[lessonId - 1].comments.map((comment, index) => {
return (
<li
key={index}
>
{comment}
</li>
)
})}
</ul>
</div>
</div>
)
}
export default Content;
function emitComments(id) {
setInterval(() => {
console.log('emit comments')
window.dispatchEvent(
new CustomEvent(`lesson${id}`, {
detail: `Nội dung comments của lesson ${id}`
})
)
}, 2000)
}
emitComments(1);
emitComments(2);
emitComments(3);

Related

TypeError: Cannot read properties of undefined (reading 'url')

I'm working on building a drag and drop card game. I'm not familiar with using the library react-dnd I wonder if this has something to do with it. If I use data already on the file, it works fine, but if I have to fetch the data, it creates This error.
As I said, this usually happens when I use useEffect Somebody has a better idea of how to do this, please let me know.
import React, { useEffect, useState } from 'react';
import Card from './Card';
import { useDrop } from 'react-dnd';
import './Table.css';
const API_Data = [
{
id: 1,
url: 'https://deckofcardsapi.com/static/img/6H.png',
},
{
id: 2,
url: 'https://deckofcardsapi.com/static/img/aceDiamonds.png',
},
{
id: 3,
url: 'https://deckofcardsapi.com/static/img/7C.png',
},
{
id: 4,
url: 'https://deckofcardsapi.com/static/img/6H.png',
},
{
id: 5,
url: 'https://deckofcardsapi.com/static/img/aceDiamonds.png',
},
{
id: 6,
url: 'https://deckofcardsapi.com/static/img/7C.png',
},
];
function Table() {
const [playersCard, setPlayersCard] = useState([]);
const [potA, setPotA] = useState([
{
id: 1,
url: 'https://deckofcardsapi.com/static/img/6H.png',
},
]);
const [potB, setPotB] = useState([]);
/////////////////////////////////////////////////////////////////////////
const [, dropA] = useDrop(() => ({
accept: 'card',
drop: (item) => handleAddToPotA(item.id),
collect: (monitor) => ({
isOver: !!monitor.isOver(),
}),
}));
const handleAddToPotA = (id) => {
const newCard = playersCard.filter((card) => id === card.id);
console.log(`newCard`, newCard);
setPotA((oldCard) => [...oldCard, newCard[0]]);
};
//////////////////////////////////////////////////////////////////////////
const [, dropB] = useDrop(() => ({
accept: 'card',
drop: (item) => handleAddToPotB(item.id),
collect: (monitor) => ({
isOver: !!monitor.isOver(),
}),
}));
const handleAddToPotB = (id) => {
const newCard = playersCard.filter((card) => id === card.id);
setPotB((oldCard) => [...oldCard, newCard[0]]);
console.log(newCard);
};
useEffect(() => {
setPlayersCard(API_Data);
return () => {};
}, []);
//////////////////////////////////////////////////////////////////////////
if (!playersCard) {
return <div></div>;
}
return (
<div className="table-page">
<div className="center-table">
<div className="pot-a" ref={dropA}>
{potA &&
potA.map((card) => {
return <Card url={card?.url} id={card.id} key={card.id} />;
})}
</div>
<div className="pot-b" ref={dropB}>
{potB &&
potB.map((card) => {
return <Card url={card.url} id={card.id} key={card.id} />;
})}
</div>
</div>
<div className="players-card">
{playersCard.map((card) => {
return <Card url={card.url} id={card.id} key={card.id} />;
})}
</div>
</div>
);
}
export default Table;
Because playersCard is not initialized yet.
try to check undefined first
<div className="players-card">
{playersCard.length > 0 && playersCard.map((card) => {
return <Card url={card.url} id={card.id} key={card.id} />;
})}
</div>
A Possible Reason:
Check your function and method names for typos

I want to change this code from class component to functional component in react

I am working on integrating 2 different js files one is in functional component and the other one is in class component. I want the class component file to be converted to functional component. I am new to reactjs and class component. If this can be converted to functional component that would be useful.
import React from "react";
import {Tabs , Button} from 'antd';
import 'antd/dist/antd.css';
const { TabPane } = Tabs;
class Tabbar extends React.Component {
constructor(props) {
super(props)
this.state = {
focusingPaneKey: '',
openingPaneKeys: [],
}
}
openPane = (paneKey) => {
this.setState(({ ...state }) => {
if (!state.openingPaneKeys.includes(paneKey)) {
state.openingPaneKeys = [...state.openingPaneKeys, paneKey]
}
state.focusingPaneKey = paneKey
return state
})
}
closePane = (paneKey) => {
this.setState(({ ...state }) => {
if (paneKey === state.focusingPaneKey) {
const paneKeyIndex = state.openingPaneKeys.indexOf(paneKey)
state.focusingPaneKey = state.openingPaneKeys[paneKeyIndex - 1]
}
state.openingPaneKeys = state.openingPaneKeys.filter((openingPaneKey) => openingPaneKey !== paneKey)
return state
})
}
handleTabsEdit = (key, action) => {
if (action === 'remove') {
this.closePane(key)
}
}
render() {
const { panes } = this.props
const keysOfPane = Object.keys(panes)
return (
<div className="tab-section">
<div style={{ marginBottom: 16 }}>
{keysOfPane.map((key) => (
<Button key={key} onClick={() => this.openPane(key)}>
ADD Tab-{key}
</Button>
))}
</div>
<Tabs
hideAdd
onChange={this.openPane}
activeKey={this.state.focusingPaneKey}
type="editable-card"
onEdit={this.handleTabsEdit}
>
{this.state.openingPaneKeys
.map((key) => panes[key])
.map((pane) => (
<TabPane tab={pane.title} key={pane.key}>
{pane.content}
</TabPane>
))}
</Tabs>
</div>
)
}
}
const panes = {
1: { key: '1', title: 'Tab 1', content: 'Content of Tab Pane 1' },
2: { key: '2', title: 'Tab 2', content: 'Content of Tab Pane 2' },
3: { key: '3', title: 'Tab 3', content: 'Content of Tab Pane 3' },
}
export default Tabbar;
Try this
import React, { useState, useCallback } from "react";
import { Tabs, Button } from 'antd';
import 'antd/dist/antd.css';
const { TabPane } = Tabs;
const Tabbar = (props) => {
const [focusingPaneKey, setFocusingPaneKey] = useState('')
cons[openingPaneKeys, setOpeningPaneKeys] = useState([])
const openPane = (paneKey) => {
setOpeningPaneKeys(oldState => {
if (!oldState.includes(paneKey)) {
return [...oldState, paneKey]
}
return oldState
})
setFocusingPaneKey(paneKey)
}
const closePane = (paneKey) => {
if (paneKey === focusingPaneKey) {
const paneKeyIndex = openingPaneKeys.indexOf(paneKey)
setFocusingPaneKey(openingPaneKeys[paneKeyIndex - 1])
}
setOpeningPaneKeys(openingPaneKeys.filter((openingPaneKey) => openingPaneKey !== paneKey))
}
const handleTabsEdit = useCallback((key, action) => {
if (action === 'remove') {
closePane(key)
}
}, [closePane])
const { panes } = props
const keysOfPane = Object.keys(panes)
return (
<div className="tab-section">
<div style={{ marginBottom: 16 }}>
{keysOfPane.map((key) => (
<Button key={key} onClick={() => openPane(key)}>
ADD Tab-{key}
</Button>
))}
</div>
<Tabs
hideAdd
onChange={openPane}
activeKey={focusingPaneKey}
type="editable-card"
onEdit={handleTabsEdit}
>
{openingPaneKeys
.map((key) => panes[key])
.map((pane) => (
<TabPane tab={pane.title} key={pane.key}>
{pane.content}
</TabPane>
))}
</Tabs>
</div>
)
}
export default Tabbar

React: How to Pass State on Each Mapped Array Items?

I rendered a list of buttons using Array.map method in a function component. When I tried to pass state to each mapped array items, the rendered results changed all array items at once, instead of one by one.
Here is my code. Am I doing something wrong? Sorry if the question has been solved in other thread or I used the wrong method. This is my first React project and I am still learning. It would be very appreciated if someone could advise. Thank you!
import React, { useState } from "react"
export default function Comp() {
const [isActive, setActive] = useState(false)
const clickHandler = () => {
setActive(!isActive)
console.log(isActive)
}
const data = [
{ id: 1, name: "Alice" },
{ id: 2, name: "Bob" },
{ id: 3, name: "Charlie" },
]
const renderList = items => {
return items.map(item => (
<li key={item.id}>
<button onClick={clickHandler}>
{item.name} {isActive ? "active" : "not active"}
</button>
</li>
))
}
return (
<ul>{renderList(data)}</ul>
)
}
Put the individual item into a different component so that each has its own active state:
export default function Comp() {
const data = [
{ id: 1, name: "Alice" },
{ id: 2, name: "Bob" },
{ id: 3, name: "Charlie" },
]
const renderList = items => (
items.map(item => <Item key={item.id} name={item.name} />)
);
return (
<ul>{renderList(data)}</ul>
)
}
const Item = ({ name }) => {
const [isActive, setActive] = useState(false);
const clickHandler = () => {
setActive(!isActive);
};
return (
<li>
<button onClick={clickHandler}>
{name} {isActive ? "active" : "not active"}
</button>
</li>
);
};
You need to set the active-id in handling the click-event. That will in-turn render active/non-active conditionally:
Notice the flow (1) > (2) > (3)
function Comp() {
const [activeId, setActiveId] = React.useState(null);
const clickHandler = (item) => {
setActiveId(item.id) // (2) click-handler will set the active id
}
const data = [
{ id: 1, name: "Alice" },
{ id: 2, name: "Bob" },
{ id: 3, name: "Charlie" },
]
const renderList = items => {
return items.map(item => (
<li key={item.id}>
<button onClick={() => clickHandler(item)}> // (1) passing the clicked-item so that we can set the active-id
{item.name} {item.id === activeId ?
"active" : "not active" // (3) conditionally render
}
</button>
</li>
))
}
return (
<ul>{renderList(data)}</ul>
)
}
Good Luck...

React-Spring: Invalid Hooks Call

I am trying to learn React-Spring. One of the codes provided in its documentation throws an error when I run it. Any idea what possibly is wrong? How to solve it?
The code I'm trying to run is-
const TextContent = (props) => {
const [items] = useState([
{ id: '0', title: 'Text1' },
{ id: '1', title: 'Text2' },
{ id: '2', title: 'Text1' }
])
const [index, setIndex] = useState(0);
const transitions = useTransition(items[index], index => index.id,
{
from: { opacity: 0 },
enter: { opacity: 1 },
leave: { opacity: 0 },
config: { tension: 220, friction: 120 }
}
)
useEffect(() => {
const interval = setInterval(() => {
setIndex((state) => (state + 1) % items.length);
}, 4000)
return () => clearInterval(interval);
}, []);
{
transitions.map(({ item, props, key }) => (
<animated.div
key={key}
style={{ ...props, position: 'absolute' }}
>
<p>
{item.title}
</p>
</animated.div>
))
}
}
export default TextContent;
Add a return statement to your functional component
const TextContent = (props) => {
const [items] = useState([
{ id: '0', title: 'Text1' },
{ id: '1', title: 'Text2' },
{ id: '2', title: 'Text1' }
])
const [index, setIndex] = useState(0);
const transitions = useTransition(items[index], index => index.id,
{
from: { opacity: 0 },
enter: { opacity: 1 },
leave: { opacity: 0 },
config: { tension: 220, friction: 120 }
}
)
useEffect(() => {
const interval = setInterval(() => {
setIndex((state) => (state + 1) % items.length);
}, 4000)
return () => clearInterval(interval);
}, []);
return (
<div>
{
transitions.map(({ item, props, key }) => (
<animated.div
key={key}
style={{ ...props, position: 'absolute' }}
>
<p>{item.title}</p>
</animated.div>
))
}
</div>
)
}
export default TextContent;
Here is a codesandbox where I got it working
In addition to Al Duncanson answer: My problem was in exporting React Fragment instead of actual tag:
return (
<>
{ /* springs.map() */ }
</>
)
Hook started working after I changed it to
return (
<div>
{ /* springs.map() */ }
</div>
)
In my NextJs app, I got the same issue. In my case, I think it was a cache-related issue. Run the project after removing the ".next" folder fixed the issue. I hope removing the build folder in React will do the same.
And there are two similar functions ("useSpring" and "useSprings"). Make sure to pick the right one for your use case.

Change active state in a list using useState

Component
Here is an example of code. What I want is to toggle the active state ( true / false) for each individual list item when I clicked it. I don't want to change them all at once.
Any help with this? Thanks in advance.
import React, { useState } from "react";
const App = () => {
const [active, setActive] = useState({});
const items = [
{ name: 'Item 1' },
{ name: 'Item 2' },
{ name: 'Item 3' }
];
handleClick = (index) => {
...
}
const list = items.map( (item, index) => {
return(
<li
key={index}
onClick={() => handleClick(index)}
className={active ? "active" : null}
>
{item.name}
</li>
)
})
return {
<>
<ul>{list}</ul>
</>
}
}
export default App;
First option - Introducing isActive property for each elements
I would introduce in the items an active property for each item and manipulate them based on the clicked item. Which needs to be added to useState where you can update the isActive property with .map().
Similarly like the following:
const [items, setItems] = useState([
{ name: 'Item 1', isActive: true },
{ name: 'Item 2', isActive: false },
{ name: 'Item 3', isActive: false }
]);
handleClick = (index) => {
setItems(prevItems =>
prevItems.map((e, i) => ({...e, isActive: i === index}))
);
}
const list = items.map( (item, index) => {
return(
<li
key={index}
onClick={() => handleClick(index)}
className={item.isActive ? 'active' : null}
>
{item.name}
</li>
)
})
return {
<>
<ul>{list}</ul>
</>
}
Second option - store the index for the clicked active element
Storing the index for the clicked element helps you the identify in .map() which one is the active one. So with a simple check with i === index you can add active class to the <li> element.
You can create a state for index with useState as the following:
const [index, setIndex] = useState(0);
const items = [
{ name: 'Item 1' },
{ name: 'Item 2' },
{ name: 'Item 3' }
];
handleClick = (i) => {
setIndex(i);
}
const list = items.map( (item, i) => {
return(
<li
key={i}
onClick={() => handleClick(i)}
className={i === index ? 'active' : null}
>
{item.name}
</li>
)
})
return {
<>
<ul>{list}</ul>
</>
}
I hope this helps!
Added comments inline.
const App = () => {
// active is active item, initially '' means, nothing selected.
const [active, setActive] = useState("");
const items = [{ name: "Item 1" }, { name: "Item 2" }, { name: "Item 3" }];
const list = items.map(({ name }) => (
<li
key={name /* avoid using index for key */}
onClick={() => setActive(name)}
className={active === name ? "active" : ""}
>
{name}
</li>
));
// return the Element, not {}
return (
<>
<ul>{list}</ul>
</>
);
};
export default App;

Categories