I have a Chat component which works fine on the happy flow but when I go on another view (meaning I exit the chat component) and come back after that in the chat component, I get duplicates messages.
I placed an console.log into the function which is triggered by the enter event but it displays only once. However, the emit from inside trigger twice I think because on the server side (nodeJs) I get the data twice.
This is my code:
function Chat() {
let [chatInput, setChatInput] = useState('');
let [chatArea, setChatArea] = useState([]);
const handleOnChange = (e) => {
setChatInput(e.target.value)
}
useEffect(() => {
const addTextMessage = (event) => {
if (event.keyCode === 13 && chatInput.value !== '') {
event.preventDefault();
socket.emit('chat', {
message: chatInput.value
});
setChatInput('');
}
}
const chatInput = document.querySelector('.chat-input input');
chatInput.addEventListener("keyup", addTextMessage, false);
socket.on('chat', (data) => {
setChatArea(prevInputState => (
[...prevInputState, <section key={prevInputState.length}>{data.sender}: {data.message}</section>]
))
})
return () => {
chatInput.removeEventListener("keyup", addTextMessage, false);
socket.off('chat');
};
}, []);
return (
<React.Fragment>
<section className="chat-room">
<div className="chat-area">
{chatArea}
</div>
<div className="chat-input">
<input value={chatInput} onChange={handleOnChange} type="text" />
</div>
</section>
</React.Fragment>
);
}
Some issues I'm spotting:
chatInput is a string, It feels weird adding a event listener to a string. It might work.
<input> has a prop called onKeyUp that is probably very useful for you.
This might be much more simple and easier to find the issue if you still get duplicates:
const [userInput, setUserInput] = useState("")
const [chatMessages, setChatMessages] = useState([])
function trySendMessage(event) {
// Send message if last key was ENTER and we got some text
if (event.keyCode === 13 && userInput !== "") {
event.preventDefault();
socket.emit("chat", {
message: chatInput,
})
setChatInput("");
}
}
// This effect only changes the state
// Notice I'm not storing the rendered result in state,
// Instead I handle rendering below in the rendering section
useEffect(() => {
socket.on('chat', (newMessage) => setChatArea([...chatMessages, newMessage])
return () => socket.off('chat')
}, [])
return (
<section>
{
chatMessages.map(message => (
<div key={message.id}>{message.name}: {message.body})</div>
)
}
<input
value={userInput}
onChange={(e) => setUserInput(e.target.value)}
onKeyUp={trySendMessage}
/>
</section>
)
I found the problem. Contrary to what I was thinking, the issue was coming from my server side.
I have placed my nsSocket.on('chat', (data) => { by mistake inside another nsSocket.on(' and after I extracted it outside, the problems were fixed.
Related
My application has a modal window with filters. I would like to add another kind of filter to it. I just don’t know how to implement it in React (perhaps you can help me with the code, recommend links).
The meaning is as follows: I want the filters to have a line in which the user could write some value on his own, press Enter, see the entered result (it is possible to enter several values by which you can filter).
Perhaps my explanation is chaotic, in addition I will provide a screenshot with the desired result:
For UI you can use or create a custom input field like this https://evergreen.segment.com/components/tag-input.
For implementation:
Now you have fields searched by user
Ex-
const list = [2,3,4,56,7,8,12,34,0,1]
const searchedItems = [3,7,8]
const finalList= [];
function searchItems() {
list.forEach(item => {
searchedItems.forEach(ele => {
if(ele===item) finalList.push(item);
})
});
return finalList;
}
console.log(searchItems())
Finally use this array in your search item results.
export function ListItem({ itemValue }) {
return (
<div className="item">
<span className="itemValue">{itemValue}</span>
</div>
);
}
export default function App() {
const [itemList, setListItem] = useState([]);
const [item, setItem] = useState("");
const addValue = (event) => {
setItem(event.target.value);
};
const listenEnter = (event) => {
if (event.key === "Enter" && event.target.value !== "") {
setListItem([...itemList, item]);
setItem("");
}
};
return (
<div className="App">
<input
className="inputElement"
placeholder="Enter Value"
value={item}
onChange={addValue}
onKeyUp={listenEnter}
/>
<div className="itemList">
{itemList.map((item) => (
<ListItem itemValue={item} />
))}
</div>
</div>
);
}
whole code https://codesandbox.io/s/elastic-meadow-h5kbxs
I’m running into an error that I could use some help on
Basically, I have a react app that is executing an HTTP call, receiving an array of data, and saving that into a state variable called ‘tasks’. Each object in that array has a key called ‘completed’. I also have a checkbox on the page called ‘Show All’ that toggles another state variable called showAll. The idea is by default all tasks should be shown however if a user toggles this checkbox, only the incomplete tasks (completed==false) should be shown. I can get all tasks to display but can’t get the conditional render to work based on the checkbox click
Here’s how I’m implementing this. I have the HTTP call executed on the page load using a useEffect hook and available to be called as a function from other change handlers (edits etc.)
Before I call the main return function in a functional component, I’m executing a conditional to check the status of ’ShowAll’ and filter the array if it's false. This is resulting in too many re-render errors. Any suggestions on how to fix it?
See simplified Code Below
const MainPage = () => {
const [tasks, setTasks] = useState([]); //tasks
const [showAll, setShowAll] = useState(true); //this is state for the checkbox (show all or just incomplete)
useEffect( ()=> {
axios.get('api/tasks/')
.then( response => { //this is the chained API call
setTasks(response.data.tasks);
})
.catch(err => {
console.log('error');
})
}, []);
const fetchItems = (cat_id) => {
axios.get('/api/tasks/')
.then( response => {
setTasks(response.data.tasks);
})
.catch(err => {
console.log('error');
})
};
//change the checkbox state
const handleCheckboxChange = (e) => {
setShowAll(!showAll)
console.log('Checkbox: ', showAll)
};
//this part updates the tasks to be filtered down to just the incomplete ones based on the checkbox value
if (showAll === false) {
setTasks(tasks.filter(v => v['completed']===false)); //only show incomplete tasks
}
return (
<div>
<label className="checkb">
<input
name="show_all"
id="show_all"
type="checkbox"
checked={showAll}
onChange={handleCheckboxChange}
/> Show all
</label>
<br/>
{ tasks && tasks.map((task, index) => {
return (
<div key={index} className="task-wrapper flex-wrapper">
<div >
{ task.completed === false ? (
<span> {index +1}. {task.task_description} </span> ) :
(<strike> {index +1}. {task.task_description} </strike>) }
</div>
<div>
<button
onClick={()=> modalClick(task)}
className="btn btn-sm btn-outline-warning">Edit</button>
<span> </span>
</div>
</div>
)
})}
</div>
);
};
export default MainPage;
Thanks
Two things to fix:
Use the checked property on event.target to update the state:
const handleCheckboxChange = ({target: { checked }}) => {
setShowAll(checked)
};
Filter as you want but don't update the state right before returning the JSX as that would trigger a rerender and start an infinite loop:
let filteredTasks = tasks;
if (!showAll) {
filteredTasks = tasks?.filter(v => !v.completed));
}
and in the JSX:
{ tasks && tasks.map should be {filteredTasks?.map(...
use e.target.value and useEffect :
//change the checkbox state
const handleCheckboxChange = (e) => {
setShowAll(e.target.checked)
console.log('Checkbox: ', showAll)
if (!e.target.checked) {
let list =tasks.filter(v => v.completed===false);
setTasks(list ); //only show incomplete tasks
}
};
or
//change the checkbox state
const handleCheckboxChange = (e) => {
setShowAll(e.target.checked)
console.log('Checkbox: ', showAll)
};
useEffect(()=>{
if (showAll === false) {
let list =tasks.filter(v => v.completed===false);
setTasks(list ); //only show incomplete tasks
}
},[showAll])
I have the following component, where review assignments (props.peerReviewAssignmentIds) are loaded for a student's own work (related event is onClick_Submission) or a peer's work to review (related event is onClick_PeerReview ). These events work fine and the related data is loaded successfully. However, there is a problem with updating the content of the child components based on the value of the props.peerReviewAssignmentIds, which I elaborate below.
const AssignmentItem = (props) => {
const assignment = props.assignments[props.currentAssignmentId];
const onClick_Submission = (e) => {
e.preventDefault();
if (!st_showSubmission) {
props.fetchPeerReviewAssignmentForStudent(currentUserId, assignment.activeReviewRoundId);
}
set_showSubmission(!st_showSubmission);
set_isPeerReview(false);
}
const onClick_PeerReview = (e) => {
e.preventDefault();
if (!st_showPeerReviews) {
if (st_submissionContiues === false)
props.fetchPeerReviewAssignmentForReviewer(currentUserId, assignment.activeReviewRoundId);
}
set_showPeerReviews(!st_showPeerReviews);
set_isPeerReview(true);
}
return (
<>
{
st_showSubmission === true && props.peerReviewAssignmentIds.length > 0 &&
<ReviewPhaseInfoForSubmission isPeerReview={false} />
}
</>
)
}
const mapStateToProps = state => ({
peerReviewAssignmentIds: state.peerReviewAssignmentReducer.peerReviewAssignmentIds,
loading_pra: state.peerReviewAssignmentReducer.loading,
error_pra: state.peerReviewAssignmentReducer.error,
})
I will try to explain the problem with an example. When the first time onClick_Submission is triggered, props.peerReviewAssignmentIds[0] is set to 2, and all the sub components are loaded properly. Next, when onClick_PeerReview is triggered, props.peerReviewAssignmentIds[0] is set to 1, which is correct. But, the child components get updated according to the previous value of props.peerReviewAssignmentIds[0], which was 2. If the onClick_PeerReview event is triggered second time, then the child components get updated correctly according to the current value of props.peerReviewAssignmentIds[0], which is 1. Any ideas why this might be happening?
I further explain my code below.
Below is the ReviewPhaseInfoForSubmission component. In this component, based on props.peerReviewAssignmentIds[0] value (which gets updated in the parent component above) etherpadLaterSubmission variable is created with props.createSession_laterSubmission method.
const ReviewPhaseInfoForSubmission = (props) => {
const [st_is_discussFeedback, set_is_discussFeedback] = useState(false);
useEffect(() => {
set_is_discussFeedback(true);
if (props.etherpadLaterSubmission === null) {
props.createSession_laterSubmission(props.peerReviewAssignmentIds[0], discussFeedback.dueDate);
}
}, [])
return (
<div className="p-1">
{
st_is_discussFeedback === true &&
<ProvideOrDiscussFeedback provide0discuss1revise2={1} isPeerReview={props.isPeerReview} />
}
</div>
);
}
const mapStateToProps = (state) => ({
etherpadLaterSubmission: state.etherpadReducer.etherpadLaterSubmission,
loading_ep: state.etherpadReducer.loading,
})
Then, in a child component, ProvideOrDiscussFeedback (see below), the props.etherpadLaterSubmission value is used for display purposes.
const ProvideOrDiscussFeedback = (props) => {
return <div className="p-3 shadow">
{
props.etherpadLaterSubmission &&
<div>
<DisplayingEtherpad etherpadSession={props.etherpadLaterSubmission } />
</div>
}
</div>
}
Turns out the problem was my computer. However, James made some good points about how to isolate the problem and utilizing useCallback and useMemo to optimize.
I am having problems with the performance of my react app. For now I'm excluding the code because I feel there might be some common sense answers.
This is the demo video
Here some pointers
I don't have unnecessary re-renders. Only individual components get rendered when they are hovered.
The animations are confined to a container div of the hovered element so no re-paints happen on the page outside of that container when hovering.
I am not using any heavy code for the hover effect or detection.
I am wondering what else could be cause for performance problems like this. As far as I understand, the number of components shouldn't matter if they are just sitting there, not rerendering.
Here is the code for the card component that is being animated. I wasn't quite sure whats important to show here. The parent component showing all the cards does not re-render.
export default function CardFile(props) {
// Input field
const input = useRef(null)
//Input state
const [inputActive, setInputActive] = useState(false);
const [title, setTitle] = useState(props.file.name)
const [menuActive, setMenuActive] = useState(false)
const [draggable, setDraggable] = useState(true)
const [isDragged, setIsDragged] = useState(false)
// counter > 0 = is hovered
const [dragCounter, setDragCounter] = useState(0)
//_________________ FUNCTIONS _________________//
// Handle file delete
const handleDelete = (e) => {
firebase.firestore().collection('users').doc(props.file.owner).collection('files').doc(props.file.id).delete().then(() => {
console.info('Deleted')
}).catch((err) => console.err(err))
}
// Prevent default if necessary
const preventDefault = (e) => {
e.preventDefault()
e.stopPropagation()
}
// Handle rename
const handleRename = (e) => {
e.stopPropagation()
setMenuActive(false)
setInputActive(true)
}
// Handle change
const handleChange = () => {
setTitle(input.current.value)
}
// Handle focus loss
const handleFocusLoss = (e) => {
e.stopPropagation()
setInputActive(false)
firebase.firestore().collection('users').doc(props.file.owner).collection('files').doc(props.file.id).update({ name: title })
.then(() => {
console.info('Updated title')
}).catch((err) => console.error(err))
}
// Handle title submit
const handleKeyPress = (e) => {
console.log('key')
if (e.code === "Enter") {
e.preventDefault();
setInputActive(false)
firebase.firestore().collection('users').doc(props.file.owner).collection('files').doc(props.file.id).update({ name: title })
.then(() => {
console.info('Submitted title')
}).catch((err) => console.error(err))
}
}
// Set input focus
useEffect(() => {
if (inputActive) {
input.current.focus()
input.current.select()
}
}, [inputActive])
//_____________________________DRAGGING___________________________//
//Handle drag start
const onDragStartFunctions = () => {
props.onDragStart(props.file.id)
setIsDragged(true)
}
// Handle drag enter
const handleDragEnter = (e) => {
// Only set as target if not equal to source
if (!isDragged) {
setDragCounter(dragCounter => dragCounter + 1)
}
}
//Handle drag end
const handleDragEnd = (e) => {
e.preventDefault()
setIsDragged(false)
}
// Handle drag exit
const handleDragLeave = () => {
// Only remove as target if not equal to source
if (!isDragged) {
setDragCounter(dragCounter => dragCounter - 1)
}
}
// Handle drag over
const handleDragOver = (e) => {
e.preventDefault()
}
// Handle drag drop
const onDragDropFunctions = (e) => {
setDragCounter(0)
// Only trigger when target if not equal to source
if (!isDragged) {
props.onDrop({
id: props.file.id,
display_type: 'file'
})
}
}
return (
<div
className={`${styles.card} ${dragCounter !== 0 && styles.is_hovered} ${isDragged && styles.is_dragged}`}
test={console.log('render')}
draggable={draggable}
onDragStart={onDragStartFunctions}
onDragEnter={handleDragEnter}
onDragOver={handleDragOver}
onDragEnd={handleDragEnd}
onDragLeave={handleDragLeave}
onDrop={onDragDropFunctions}
>
<div className={styles.cardInner}>
<div className={styles.videoContainer} onClick={() => props.handleActiveMedia(props.file, 'show')}>
{props.file.thumbnail_url && props.file.type === 'video' &&
<MdPlayCircleFilled className={styles.playButton} />
}
{!props.file.thumbnail_url && props.file.type === 'image' &&
<MdImage className={styles.processingButton} />
}
{!props.file.thumbnail_url && props.file.type === 'video' &&
<FaVideo className={styles.processingButton} />
}
<div className={styles.image} style={props.file.thumbnail_url && { backgroundImage: `url(${props.file.thumbnail_url})` }}></div>
</div>
<div className={styles.body}>
<div className={styles.main}>
{!inputActive ?
<p className={styles.title}>{title}</p>
:
<input
ref={input}
className={styles.titleInput}
type="text"
onKeyPress={handleKeyPress}
onChange={handleChange}
onBlur={handleFocusLoss}
defaultValue={title}
/>
}
</div>
<ToggleContext onClick={() => setMenuActive(prevMenuActive => !prevMenuActive)}>
{
menuActive && <div className={styles.menuBackground} />
}
<Dropdown top small active={menuActive}>
<ButtonLight title={'Rename'} icon={<MdTitle />} onClick={handleRename} />
<ButtonLight title={'Label'} icon={<MdLabel />} onClick={() => props.handleActiveMedia(props.file, 'label')} />
<ButtonLight title={'Share'} icon={<MdShare />} onClick={() => window.alert("Sharing is not yet supported. Stay put.")} />
{/*props.file.type === 'video' && <ButtonLight title={'Split'} icon={<RiScissorsFill />} />*/}
<ButtonLightConfirm
danger
title={'Delete'}
icon={<MdDelete />}
onClick={(e) => preventDefault(e)}
confirmAction={handleDelete}
preventDrag={() => setDraggable(false)}
enableDrag={() => setDraggable(true)}
/>
</Dropdown>
</ToggleContext>
</div>
</div>
</div>
);
}
And here is the css for animating it:
.is_hovered {
box-shadow: 0 0 0 3px var(--blue);
}
.is_hovered > div {
transform: scale(0.9);
box-shadow: 0 0 5px rgba(0, 0, 0, 0.08);
transition: .1s;
}
Edit: Added code
Edit2: Updated sample video to show re-renders
The thing I think you should try first is 'memoizing' all your functions with useCallback. Especially as you're passing some of these functions down to other components, it's possible they are causing unnecessary re-rendering deeper in the DOM.
I don't know if you're familiar with useCallback, but basically it just wraps around your function, and only updates it when specific values change. This allows React to avoid re-creating it on every render and causing components deeper in the DOM to re-render.
You can read the docs here, but the gist of it is that instead of const getA = () => a you would write getA = useCallback(() => a, [a]), and the array contains all the dependencies for the function which cause it to update if changed.
Make sure you use these in your JSX, and avoid arrow functions like onClick={(e) => preventDefault(e)}. The function you have called preventDefault can even live outside the component entirely, since it makes no reference to anything specific to the component.
Try making these updates and see if it makes a difference. Also test without the console.log, since that can also slow things down.
I hate to upload a code snippet with no sandbox, but this particular instance I use firebase so wasn't sure how to make one. Apologies for the verbose code. I'm a beginner React developer and I've been stuck on this state management issue for 2 weeks now, and I tried so many different methods but to no fruit. Please help.
My goal is to click AddLinkButton to make multiple input forms one by one, each input form would be different links, and by clicking Apply Button it would collect all the link values and store it to firebase's firestore. Once the storing is complete, it would display a preview by passing in multiple updated hook values to <UserPreview />.
If I run this particular code below, the key which is supposed to be the value of the link input forms, is null and does not update on onChange.
Please help... much appreciated. Thank you.
EDIT: changed variable name key to keyHook but to no success. Same issue
const AdminCustomizer = () => {
const [username, setUsername] = useState(null);
const [linkForm, setlinkForm] = useState([]);
const [spotlightLabel, setSpotlightLabel] = useState('');
const [spotlightLink, setSpotlightLink] = useState('');
const [refresh, setRefresh] = useState(false);
const [keyHook, setKeyHook] = useState(null);
const [startCollect, setStartCollect] = useState(false);
const linkRef = useRef();
const userInfo = {username, linkRef, spotlightLabel, spotlightLink, pfpURL, refresh};
// on initial load, load database to page
if (!username) {
firebase.getAuth().onAuthStateChanged(user => {
if (user) {
setUsername(user.displayName);
firebase.getUserInfo(user.displayName).then(result => {
setSpotlightLabel(result.spotlightLabel);
setSpotlightLink(result.spotlightLink);
linkRef.current = result.links;
if (result.links) {
Object.values(result.links).forEach(link => {
AddLinks(link);
});
}
})
}
});
}
//on refresh (when clicking apply changes button) reload page values with updated database
useEffect(() => {
if (refresh) {
firebase.getAuth().onAuthStateChanged(user => {
if (user) {
firebase.getUserInfo(user.displayName).then(result => {
linkRef.current = result.links;
Object.values(result.links).forEach(link => {
AddLinks(link);
});
})
setRefresh(false);
}
});
}
}, [refresh])
// adding AddLink button will add a new input form
// adding AddLink with firebase database value will add a new input form with values loaded
const AddLinks = url => {
const hooks = { refresh, startCollect, keyHook, setKeyHook };
if (url) setKeyHook(url);
setlinkForm([ ...linkForm, <AddLink key={keyHook} keyHook={keyHook} hooks={hooks} /> ]);
}
// add link input form
const AddLink = props => {
const handleChange = e => setKeyHook(e.target.value);
return (
<form noValidate autoComplete="off">
<br />
<Link label="Social" onChange={handleChange} value={props.keyHook} />
</form>
)
}
// when apply changes is clicked, collect input values from all link input forms
if (startCollect) {
linkForm.forEach(form => {
linkRef.current = {
...linkRef.current,
link: form.keyHook,
}
});
firebase.addLinksToUser({ spotlightLabel, spotlightLink, linkRef }).then(() => {
//force refresh to update userInfo for UserPreview
setStartCollect(false);
setRefresh(true);
});
}
return (
<>
<LinkBox>
<ApplyButton onClick={() => setStartCollect(true)}>Apply Changes</ApplyButton>
<Link label="Website Title" onChange={e => setSpotlightLabel(e.target.value)} value={spotlightLabel} />
<Link label="Website URL" onChange={e => setSpotlightLink(e.target.value)} value={spotlightLink}/>
<AddLinkButton onClick={() => AddLinks(null)} />
<div>{linkForm ? linkForm.map(child => child) : null}</div>
</LinkBox>
<div>
<PhoneOutline>
<UserPreview userInfo={userInfo}/>
</PhoneOutline>
</div>
</>
);
}
export default AdminCustomizer;
In AddLink, the key is a restricted keyword and doesn't get propagated as props. Try a different prop name instead of key.
See this link
Try:
<AddLink key={keyHook} keyHook={keyHook} hooks={hooks} />