I can't send message after subsequential clicks, if i click button at first time it is sending message to server, after that it is not sending messages to server.
import { useEffect, useState, useRef } from "react";
import Header from "../src/Components/Header";
import ChatHistory from "../src/Components/ChatHistory";
import ChatArea from "../src/Components/ChatArea";
function App() {
const [messages, setMessages] = useState([]);
const testValue = { messages, setMessages };
const socket = useRef(null);
const renderCount = useRef(0);
const sendMessage = (msg = "test") => {
if (socket.current) {
socket.current.send(msg);
}
addMessages(msg);
};
const addMessages = (msg) => {
setMessages((prev) => [...prev, msg]);
};
useEffect(() => {
socket.current = new WebSocket("ws://localhost:8001/ws");
socket.current.onmessage = (msg) => {
addMessages(msg);
};
}, []);
useEffect(() => {
return () => {
if (socket.current) {
socket.current.close();
}
};
}, [socket]);
console.log("i am rendering");
return (
<>
<Header />
<ChatHistory chatHistory={messages.current} />
<div>
<button onClick={sendMessage}>Send</button>
</div>
</>
);
}
export default App;
Above mentioned one is my code, While clicking send button at first time, it is triggering message to server, after another subsequential clicks it isn't triggering message to server. Help needed.
Your second useEffect actually closing the connection after the first render, it's unnecessary.
Moreover you don't really need to save your socket instance in a ref, usually you need a single instance:
const socket = new WebSocket("ws://localhost:8001/ws");
function App() {
const [messages, setMessages] = useState([]);
const addMessages = (msg) => {
setMessages((prev) => [...prev, msg]);
};
const sendMessage = (msg = "test") => {
socket.send(msg);
addMessages(msg);
};
// Setup
useEffect(() => {
socket.current.onmessage = addMessages;
}, []);
// Runs on App unmount, means on closing the application
useEffect(() => {
return () => {
socket.close();
};
}, []);
return (
<>
<Header />
<ChatHistory chatHistory={messages.current} />
<div>
<button onClick={sendMessage}>Send</button>
</div>
</>
);
}
useEffect(() => {
return () => {
if (socket.current) {
socket.current.close();
}
};
}, [socket]);
everytime the socket is changing, you close it
try to unmont in the same useEffect that is defining socket
useEffect(() => {
if (!socket.current) {
socket.current = new WebSocket("ws://localhost:8001/ws");
socket.current.onmessage = (msg) => {
addMessages(msg);
};
}
return () => {
if (socket.current) {
socket.current.close();
}
};
}, [socket]);
Note useRef is not optimal for that case, use useState instead
This is working, but i am not sure what i am did wrong.
import { useEffect, useRef, useState } from "react";
import Header from "./Components/Header";
import ChatHistory from "./Components/ChatHistory";
function App() {
const [chatHistory, setChatHistory] = useState([]);
const [isOnline, setIsOnline] = useState(false);
const [textValue, setTextValue] = useState("");
const webSocket = useRef(null);
webSocket.current = new WebSocket("ws://localhost:8001/ws");
useEffect(() => {
setTimeout(() => {
if (webSocket.current.readyState === WebSocket.OPEN) {
setIsOnline(true);
}
if (webSocket.current.readyState === WebSocket.CLOSED) {
setIsOnline(false);
setChatHistory([]);
}
}, 5);
}, [webSocket.current]);
const sendMessage = () => {
if (webSocket.current.readyState === WebSocket.OPEN) {
setChatHistory([...chatHistory, textValue]);
webSocket.current.send(textValue);
}
};
return (
<>
<div className="App">
<Header onLine={isOnline} />
<ChatHistory chatHistory={chatHistory} />
<input
type="text"
onChange={(e) => setTextValue(e.target.value)}
value={textValue}
placeholder="Type Message..."
/>
<button onClick={sendMessage}>Hit</button>
</div>
</>
);
}
export default App;
Related
I am making a website in which I am able to send notifications to all the users but there is a problem, it says
Notification.requestPermission() is not a function
Here is my code:
import React, { useState } from "react";
const Notification = () => {
const [input, setInput] = useState("");
const handleInput = (e) => {
e.preventDefault();
setInput(e.target.value);
};
const sendNotification = () => {
Notification.requestPermission().then((perm) => {
if (perm === "granted") {
new Notification(input, {
body: "Go check it out!",
});
}
});
};
return (
<>
<input type="text" value={input} onChange={handleInput} />
<button onClick={sendNotification}>Send</button>
</>
);
};
export default Notification;
I am using react
Thank You in advance!
I'm trying to implement debounce in a small/test React application.
It's just an application that fetch data from an API and it has a text field for an auto complete.
import React, { useEffect, useState, useMemo } from 'react';
import axios from 'axios';
const API = 'https://jsonplaceholder.typicode.com/posts';
const AutoComplete2 = () => {
const [ text, setText ] = useState("")
const [ posts, setPosts ] = useState([])
useEffect(() => {
async function fetchData() {
const data = await axios.get(API);
if(parseInt(data.status) !== 200) return;
setPosts(data.data)
}
fetchData();
}, [])
const handleTextChange = (event) => setText(event.target.value);
const handleSelectOption = (str) => setText(str);
const showOptions = useMemo(() => {
if(text === '') return;
const showPosts = [...posts].filter((ele) => ele.title.toLowerCase().includes(text.toLowerCase()));
if(showPosts.length === 1) {
setText(showPosts[0].title);
} else {
return (
<div>
{showPosts.map((obj, index) => {
return (
<div key={index} >
<span onClick={() => handleSelectOption(obj.title)} style={{cursor: 'pointer'}}>
{obj.title}
</span>
</div>
)
})}
</div>
)
}
}, [text, posts])
// addding debounce
const debounce = (fn, delay) => {
let timer;
return function() {
let context = this;
let args = arguments;
clearTimeout(timer);
timer = setTimeout(() => {
fn.apply(context, args)
}, delay);
}
}
const newHandleTextChange = ((val) => debounce(handleTextChange(val), 5000));
return (
<div>
<input type="text" value={text} onChange={newHandleTextChange} />
{showOptions}
</div>
)
}
export default AutoComplete2;
The application works, but not the debounce. I add a 5 seconds wait to clearly see if it is working, but every time I change the input text, it calls the function without the delay. Does anyone know why it is happening?
Thanks
A more idiomatic approach to debouncing in React is to use a useEffect hook and store the debounced text as a different stateful variable. You can then run your filter on whatever that variable is.
import React, { useEffect, useState, useMemo } from "react";
import axios from "axios";
const API = "https://jsonplaceholder.typicode.com/posts";
const AutoComplete2 = () => {
const [text, setText] = useState("");
const [debouncedText, setDebouncedText] = useState("");
const [posts, setPosts] = useState([]);
useEffect(() => {
async function fetchData() {
const data = await axios.get(API);
if (parseInt(data.status) !== 200) return;
setPosts(data.data);
}
fetchData();
}, []);
// This will do the debouncing
// "text" will always be current
// "debouncedText" will be debounced
useEffect(() => {
const timeout = setTimeout(() => {
setDebouncedText(text);
}, 5000);
// Cleanup function clears timeout
return () => {
clearTimeout(timeout);
};
}, [text]);
const handleTextChange = (event) => setText(event.target.value);
const handleSelectOption = (str) => setText(str);
const showOptions = useMemo(() => {
if (debouncedText === "") return;
const showPosts = [...posts].filter((ele) =>
ele.title.toLowerCase().includes(debouncedText.toLowerCase())
);
if (showPosts.length === 1) {
setText(showPosts[0].title);
} else {
return (
<div>
{showPosts.map((obj, index) => {
return (
<div key={index}>
<span
onClick={() => handleSelectOption(obj.title)}
style={{ cursor: "pointer" }}
>
{obj.title}
</span>
</div>
);
})}
</div>
);
}
}, [debouncedText, posts]);
return (
<div>
<input type="text" value={text} onChange={handleTextChange} />
{showOptions}
</div>
);
};
export default AutoComplete2;
import { useEffect, useState, useRef } from "react";
import axios from "axios";
import { backend_base_url } from "../constants/external_api";
export default function DebounceControlledInput() {
const [search_category_text, setSearchCategoryText] = useState("");
let debounceSearch = useRef();
useEffect(() => {
const debounce = function (fn, interval) {
let timer;
return function (search_key) {
clearTimeout(timer);
timer = setTimeout(() => {
fn(search_key);
}, interval);
};
};
const getCategories = function (search_key) {
axios
.get(`${backend_base_url}categories/${search_key}`)
.then((response) => {
console.log("API Success");
})
.catch((error) => {});
};
debounceSearch.current = debounce(getCategories, 300);
//use for initial load
//debounceSearch.current('');
}, []);
const searchCategory = (search_key) => {
debounceSearch.current(search_key);
};
return (
<form
className="form-inline col-4"
onSubmit={(e) => {
e.preventDefault();
}}
autoComplete="off"
>
<input
type="text"
placeholder=""
id="search"
value={search_category_text}
onChange={(e) => {
searchCategory(e.target.value);
setSearchCategoryText(e.target.value);
e.preventDefault();
}}
/>
</form>
);
}
Disclaimer: Please don't mark this as duplicate. I've seen similar questions with answers. But none of them is working for me. I'm just learning React.
What I'm trying to achieve is basically infinite scrolling. So that when a user scrolls to the end of the page, more data will load.
I've used scroll eventListener to achieve this. And it is working.
But I'm facing problems with the state of the variables.
First, I've changed the loading state to true. Then fetch data and set the state to false.
Second, when scrolling to the end of the page occurs, I again change the loading state to true. Add 1 with pageNo. Then again fetch data and set the loading state to false.
The problems are:
loading state somehow remains true.
Changing the pageNo state is not working. pageNo always remains to 1.
And actually none of the states are working as expected.
My goal: (Sequential)
Set loading to true.
Fetch 10 posts from API after component initialization.
Set loading to false.
After the user scrolls end of the page, add 1 with pageNo.
Repeat Step 1 to Step 3 until all posts loaded.
After getting an empty response from API set allPostsLoaded to true.
What I've tried:
I've tried adding all the states into dependencyList array of useEffect hook. But then an infinite loop occurs.
I've also tried adding only pageNo and loading state to the array, but same infinite loop occurs.
Source:
import React, { lazy, useState } from 'react';
import { PostSection } from './Home.styles';
import { BlogPost } from '../../models/BlogPost';
import { PostService } from '../../services/PostService';
const defaultPosts: BlogPost[] = [{
Id: 'asdfg',
Content: 'Hi, this is demo content',
Title: 'Demo title',
sections: [],
subTitle: '',
ReadTime: 1,
CreatedDate: new Date()
}];
const defaultPageNo = 1;
const PostCardComponent = lazy(() => import('./../PostCard/PostCard'));
const postService = new PostService();
const Home = (props: any) => {
const [posts, setPosts]: [BlogPost[], (posts: BlogPost[]) => void] = useState(defaultPosts);
const [pageNo, setPageNo] = useState(defaultPageNo);
const [pageSize, setPageSize] = useState(10);
const [loading, setLoading] = useState(false);
const [allPostsLoaded, setAllPostsLoaded] = useState(false);
const [featuredPost, setFeaturedPost]: [BlogPost, (featuredPost: BlogPost) => void] = useState(defaultPosts[0]);
async function getPosts() {
return await postService.getPosts(pageSize, pageNo);
}
async function getFeaturedPost() {
return await postService.getFeaturedPost();
}
function handleScroll(event: any) {
console.log('loading ' + loading);
console.log('allPostsLoaded ' + allPostsLoaded);
var target = event.target.scrollingElement;
if (!loading && !allPostsLoaded && target.scrollTop + target.clientHeight === target.scrollHeight) {
setLoading(true);
setPageNo(pageNo => pageNo + 1);
setTimeout(()=>{
getPosts()
.then(response => {
const newPosts = response.data.data;
setLoading(false);
if (newPosts.length) {
const temp = [ ...posts ];
newPosts.forEach(post => !temp.map(m => m.Id).includes(post.Id) ? temp.push(post) : null);
setPosts(temp);
} else {
setAllPostsLoaded(true);
}
})
}, 1000);
}
}
function init() {
setLoading(true);
Promise.all([getFeaturedPost(), getPosts()])
.then(
responses => {
setLoading(false);
setFeaturedPost(responses[0].data.data);
setPosts(responses[1].data.data);
}
);
}
React.useEffect(() => {
window.addEventListener("scroll", handleScroll);
init();
return () => {
window.removeEventListener("scroll", handleScroll);
};
}, []
);
return (
<PostSection className="px-3 py-5 p-md-5">
<div className="container">
<div className="item mb-5">
{posts.map(post => (
<PostCardComponent
key={post.Id}
Title={post.Title}
intro={post.Content}
Id={post.Id}
ReadTime={post.ReadTime}
CreatedDate={post.CreatedDate}
/>
))}
</div>
</div>
</PostSection>
);
};
export default Home;
Used more effects to handle the change of pageNo, loader and allPostsLoaded state worked for me.
Updated Source:
import React, { lazy, useState } from 'react';
import { Guid } from "guid-typescript";
import { PostSection } from './Home.styles';
import { BlogPost } from '../../models/BlogPost';
import { PostService } from '../../services/PostService';
import { Skeleton } from 'antd';
const defaultPosts: BlogPost[] = [{
Id: '456858568568568',
Content: 'Hi, this is demo content. There could have been much more content.',
Title: 'This is a demo title',
sections: [],
subTitle: '',
ReadTime: 1,
CreatedDate: new Date()
}];
const defaultPageNo = 1;
const defaultPageSize = 10;
const PostCardComponent = lazy(() => import('./../PostCard/PostCard'));
const postService = new PostService();
const Home: React.FC<any> = props => {
const [posts, setPosts]: [BlogPost[], (posts: BlogPost[]) => void] = useState(defaultPosts);
const [pageNo, setPageNo] = useState(defaultPageNo);
const [pageSize, setPageSize] = useState(defaultPageSize);
const [loading, setLoading] = useState(false);
const [allPostsLoaded, setAllPostsLoaded] = useState(false);
const [featuredPost, setFeaturedPost]: [BlogPost, (featuredPost: BlogPost) => void] = useState(defaultPosts[0]);
function getNewGuid() {
return Guid.create().toString();
}
async function getPosts() {
return await postService.getPosts(pageSize, pageNo);
}
async function getFeaturedPost() {
return await postService.getFeaturedPost();
}
function init() {
setLoading(true);
Promise.all([getFeaturedPost(), getPosts()])
.then(
responses => {
setLoading(false);
setFeaturedPost(responses[0].data.data);
setPosts(responses[1].data.data);
}
);
}
React.useEffect(() => {
init();
return;
}, []);
React.useEffect(() => {
if (allPostsLoaded || loading) return;
function handleScroll(event: any) {
var target = event.target.scrollingElement;
if (!loading && !allPostsLoaded && target.scrollTop + target.clientHeight === target.scrollHeight) {
setPageNo(pageNo => pageNo+1);
}
}
window.addEventListener("scroll", handleScroll);
return () => {
window.removeEventListener("scroll", handleScroll);
};
}, [loading, allPostsLoaded]
);
React.useEffect(() => {
if (pageNo > 1) {
setLoading(true);
setTimeout(()=>{
getPosts()
.then(response => {
const newPosts = response.data.data;
setTimeout(()=>{
setLoading(false);
if (newPosts.length) {
const temp = [ ...posts ];
newPosts.forEach(post => !temp.map(m => m.Id).includes(post.Id) ? temp.push(post) : null);
setPosts(temp);
} else {
setAllPostsLoaded(true);
}
}, 1000);
})
}, 1000);
}
}, [pageNo]
);
return (
<PostSection className="px-3 py-5 p-md-5">
<div className="container">
<div className="item mb-5">
{posts.map(post => (
<PostCardComponent
key={post.Id}
Title={post.Title}
intro={post.Content}
Id={post.Id}
ReadTime={post.ReadTime}
CreatedDate={post.CreatedDate}
/>
))}
</div>
</div>
</PostSection>
);
};
export default Home;
So i am using a HOC for general error handling purposes in react like this:
import React, { useState, useEffect } from 'react'
import Modal from '../../UI/Modal/Modal'
const WithErrorHandler = (WrappedComponent, axios) => {
const NewComponent = props => {
console.log('UseState')
const [error, setError] = useState(null)
console.log('runs')
useEffect(() => {
const req = axios.interceptors.request.use(config => {
console.log('request intercepted')
return config
})
const res = axios.interceptors.response.use(null, error => {
setError(error)
return Promise.reject(error)
})
return () => {
axios.interceptors.request.eject(req)
axios.interceptors.response.eject(res)
}
}, [])
return (
<div>
{console.log('render')}
{error ? (
<Modal clickHandler={() => setError(null)}> {error.message}</Modal>
) : null}
<WrappedComponent {...props} />
</div>
)
}
return NewComponent
}
export default WithErrorHandler
The problem i have run into is that i have a component which fires an axios request in it's useEffect().
When i try to wrap this component with my WithErrorHandler the useEffect of the wrapped component fires first then the useEffect of HOC withErrorHandler runs. This causes the axios request to be made faster than the HOC could register the axios interceptors. Any ideas on how to fix this would be aprreciated.
You can define an intermediate state which prevents from rendering wrapped component.
const WithErrorHandler = (WrappedComponent, axios) => {
const NewComponent = (props) => {
const [ready, setReady] = useState(false); // HERE
console.log("UseState");
const [error, setError] = useState(null);
console.log("runs");
useEffect(() => {
const req = axios.interceptors.request.use((config) => {
console.log("request intercepted");
return config;
});
const res = axios.interceptors.response.use(null, (error) => {
setError(error);
return Promise.reject(error);
});
setReady(true); // HERE
return () => {
axios.interceptors.request.eject(req);
axios.interceptors.response.eject(res);
};
}, []);
if (!ready) return null; // HERE
return (
<div>
{console.log("render")}
{error ? (
<Modal clickHandler={() => setError(null)}> {error.message}</Modal>
) : null}
<WrappedComponent {...props} />
</div>
);
};
return NewComponent;
};
What it does is that it makes sure that axios interceptor is initialized and it is good to render wrapped component.
Instead of if (!ready) return null; you can return a more sensible state from your HOC for instance, if (!ready) return <p>Initializing...</p>
You need an extra render for the NewComponent callback to run, adding a conditional rendering on WrappedComponent should do the trick.
Notice that we set isFirstRender on promise success, change it dependenly on your use case.
const WithErrorHandler = (WrappedComponent, axios) => {
const NewComponent = (props) => {
const [isFirstRender, setIsFirstRender] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
if (isFirstRender) {
const req = axios.interceptors.request.use((config) => {
return config;
});
// Check req success
if (req.isSuccess) { setIsFirstRender(false); }
const res = axios.interceptors.response.use(null, (error) => {
setError(error);
return Promise.reject(error);
});
return () => {
axios.interceptors.request.eject(req);
axios.interceptors.response.eject(res);
};
}
}, [isFirstRender]);
return (
<div>
{error ? (
<Modal clickHandler={() => setError(null)}> {error.message}</Modal>
) : null}
{!isFirstRender && <WrappedComponent {...props} />}
</div>
);
};
return NewComponent;
};
I'm trying to set a state, which I fetch from an API in the form of an array.
Tried this in every way possible, doesn't work.
Any ideas on how to fix this?
instance is an axios.create that creates the instance to a localhost django server which has CORS-ALLOW-CROSS-ORIGIN True
import React, { useState } from "react";
import { instance } from "../../stores/instance";
const OneList = () => {
const [one, setOne] = useState([]);
const fetchText = async () => {
const response = await instance.get(`/one/list/`);
setOne(response.data);
};
fetchText();
return (
<>
<div>Hello World.</div>
{one.forEach(o => (
<p>o.text</p>
))}
</>
);
};
export default OneList;
Do it like this,
import React, { useState } from "react";
import { instance } from "../../stores/instance";
const OneList = () => {
const [one, setOne] = useState([]);
const fetchText = async () => {
const response = await instance.get(`/one/list/`);
setOne(response.data);
};
useEffect(() => {
fetchText();
},[ any variable you want it to fetch that again ]);
return (
<>
<div>Hello World.</div>
{one.forEach(o => (
<p>o.text</p>
))}
</>
);
};
export default OneList;
This looks to be a good use case for a useEffect hook. Also, you need to await async functions within useEffect statement. The useEffect hook cannot be an async function in itself. Also, the original implementation would result in an infinite loop. The setState function would trigger a re-render which would then trigger the fetch function to fire off again. Consider the following:
import React, { useState, useEffect } from "react";
import { instance } from "../../stores/instance";
const OneList = () => {
const [one, setOne] = useState([]);
const fetchText = async () => {
const request= await instance.get(`/one/list/`);
request.then( r => setOne(r.data))
};
useEffect(() => {
(async () => {
await fetchText();
})();
}, []);
return (
<>
<div>Hello World.</div>
{one.forEach(o => (
<p>o.text</p>
))}
</>
);
};
export default OneList;
Based on you guys' advice, I came up with the below code which works fine so far.
import React, { useState, useEffect } from "react";
import { instance } from "../../stores/instance";
const OneList = () => {
const [one, setOne] = useState([]);
const [el, setEl] = useState(null);
const fetchText = async () => {
let res = await instance.get("/one/list/");
setOne(res.data);
};
useEffect(() => {
(async () => {
await fetchText();
})();
}, []);
useEffect(() => {
const handleClick = e => {
let newEl = document.createElement("input");
newEl.value = e.target.innerHTML;
newEl.id = e.target.id;
newEl.addEventListener("keypress", e => handleInput(e));
newEl.addEventListener("focusout", e => handleInput(e));
e.target.parentNode.replaceChild(newEl, e.target);
newEl.focus();
};
const handleInput = async e => {
console.log(e.type);
if (
e.target.value !== "" &&
(e.key === "Enter" || e.type === "focusout")
) {
let payload = { text: e.target.value };
try {
e.preventDefault();
let res = await instance.put(`/one/update/${e.target.id}`, payload);
let text = res.data.text;
e.target.value = text;
let newEl = document.createElement("span");
newEl.innerHTML = text;
newEl.addEventListener("click", e => handleClick(e));
newEl.id = e.target.id;
e.target.parentNode.replaceChild(newEl, e.target);
} catch (e) {
console.error(e);
}
}
};
setEl(
one.map(o => (
<p>
<span id={o.id} onClick={e => handleClick(e)}>
{o.text}
</span>
</p>
))
);
}, [one]);
return <>{el}</>;
};
export default OneList;