I have a component called NAV this component is static in the application, in that component I handle the socket.on listener to store the notification information, if there is a path that does not contain the socket it works normal, but when I put the socket in a component Inside of a route in react, the socket that was inside the NAV component stops working, I swear I must repeat the socket.on code of the NAV component so that the information can arrive again, this is a bad practice because the code should not be repeated so much This happens to me with 5 components in different routes, but I don't know how to prevent this from happening
I tried to make a component that covers all the components inside the routes and manages the sockets information there but it didn't work for me.
THIS IS THE CODE I HAVE IN THE SOCKET ON THE SERVER
socket.on("received event", async (id, productID) => {
if (id || productID) {
let product;
if (productID !== undefined)
product = await Product.findById(productID);
const user = getUser(id ? id : product.owner);
if (user.length > 0)
user.map((user) => io.to(user.socketId).emit("received event"));
}
});
THIS IS THE CODE THAT I HAVE IN THE NAV BUT I HAVE TO REPEAT IT IN EACH ROUTE THAT INTEGRATES THE SOCKET
useEffect(() => {
socket.on("received event", async () => {
await searchNotifications();
});
return () => socket.off();
});
THIS IS THE MAIN APP COMPONENT
function App() {
return (
<LoadingZone>
<Nav /> // HERE IS THE "SOCKET" THE ABOVE CODE THAT IS REPEATED
<Routes>
<Route path="/" element={<Home />}/>
<Route path="/report" element={<PrivateRoute><Report /></PrivateRoute>}/>
<Route path="/messages" element={<PrivateRoute><Messages /></PrivateRoute>}/>
<Route path="/notifications" element={<PrivateRoute><Notifications /></PrivateRoute>}/>
<Route path="/help/*" element={<Help />} />
<Route path="/post/information/*" element={<Post />} /> // THE SOCKET THAT IS IN THE NAV DOES NOT AFFECT IT
</Routes>
<Footer />
<PopupWindow/>
</LoadingZone>
);
}
export default App;
It's supposed to keep taking the socket from the NAV since it doesn't depend on any route but it doesn't, I have the main socket in a separate file where I can access the different components. But this is my problem
This is the nav component
import { socket } from "../api";
function Nav() {
const dispatch = useDispatch();
const searchNotifications = useCallback(async () => {
const briefNotifications = await getNotifications(cookies.get("id"));
const currentNotification = [];
let count = 0;
for (let i = 0; i < 3; i++) {
if (briefNotifications[i] !== undefined)
currentNotification.push(briefNotifications[i]);
}
for (let i = 0; i < briefNotifications.length; i++) {
if (!briefNotifications[i].view) count += 1;
}
dispatch(set(count));
dispatch(changeNotifications(currentNotification));
}, [dispatch]);
useEffect(() => {
socket.on("received event", async () => { // This works until I go to the Post route
await searchNotifications();
});
return () => socket.off();
});
Return <Content/>
}
This is the Post component
function Post() {
... // Code that has nothing to do with the problem
Return <Content/>
}
It's supposed to keep taking the socket from the NAV since it doesn't depend on any route but it doesn't
You can create global hooks to handle repeated useEffect in many components. For example like this:
export const useSocket = () => {
const [data, setData] = useState();
useEffect(() => {
socket.on("received event", async () => {
const res = await searchNotifications();
setData(res);
});
return () => socket.off();
});
return data;
}
And in any component that needs to use socket:
import { useSocket } from 'yourJsFile';
const AnyComponent = props => {
const data = useSocket();
}
Not a lot of context provided for what exactly isn't working, but it appears the useEffect hook is missing a dependency array. Without it the component(s) are constantly opening/closing (subscribing/unsubscribing) the socket connection.
Since it doesn't appear there are any external dependencies try adding an empty dependency array so the effect is run once when the component mounts to connect the socket event, and unsubscribed from the event when the component unmounts.
Example:
useEffect(() => {
socket.on("received event", async () => {
await searchNotifications();
});
return () => socket.off();
}, []); // <-- add empty dependency array
Related
I am new to React and following a tutorial on Youtube. I am attempting to make an API call to return a cart object, after receiving that object I would like that object to be passed to a child component where an ID property will be pulled from the cart to generate a checkout token. The issue is that when the API call is made to get the cart it will not consistently wait before passing the object to the child component through props. Sometimes it's the object, sometimes just the name of the object, sometimes an empty object. I'll write out some partial code here to demonstrate
App Component
const [cart, setCart] = useState();
const fetchCart = async() => {
setCart(await commerce.cart.retrieve();
}
useEffect(() => {
fetchCart();
},[]);
<routerstuff...>
<Route Path="/checkout" element={<Checkout cart={cart} />}>
Checkout component
const Checkout = { cart } => {
const generateToken = async () => {
commerce.checkout.generateToken(cart.id, { type = 'cart'});
useEffect(() => {
generateToken();
}, [cart]);
I need to share some data I have collected with useState from one component file to another. I am building a blog using Material UI that displays all posts on the home page. When a specific post is clicked I want to redirect the user to a single post page where they can read the entire thing. I have searched StackOverflow and the internet and can't seem to find a straight forward answer to the problem. I think context API might be overkill. And I am unsure how to pass props from one component to another when I am not calling the function in the other component file.
This is part of the home page component. This code stores the post id that I need to access from the SinglePost component. When I click a post it correctly console.logs the id:
export default function HomeCard() {
const [latestPosts, setLatestPost] = useState([]);
useEffect(() => {
fetch('http://localhost:8000/posts')
.then ((res) => {
return res.json();
})
.then ((data) => {
setLatestPost(data.reverse());
});
}, []);
const navigate = useNavigate();
const storePostId = (singlePostId) => {
navigate('/single-post')
console.log(singlePostId);
}
A little further down in the file:
<CardActionArea
onClick={ () =>
storePostId(latestPost.id)
}>
Within my SinglePost component:
const SinglePost = () => {
useEffect(() => {
fetch(`http://localhost:8000/posts/${singlePostId}`)
.then ((res) => {
return res.json();
})
.then ((data) => {
return (data);
});
}, []);
You can pass the prob as a query parameter.
const storePostId = (singlePostId) => {
navigate(`/single-post/${singlePostId}`)
}
and in SinglePost component read the query params using your project's router (ie: react router dom)
Edit:
You're using useNavigate from react router. I'm assuming you've had the routes set up
<Routes>
<Route path="/single-post" element={<SinglePost />} />
</Routes>
I'd recommend setting it like this
<Routes>
<Route path="/single-post/:singlePostId" element={<SinglePost />} />
</Routes>
And in your SinglePost
const SinglePost = () => {
let { singlePostId } = useParams(); //It will read it from query params
useEffect(() => {
fetch(`http://localhost:8000/posts/${singlePostId}`)
.then ((res) => {
return res.json();
})
.then ((data) => {
return (data);
});
}, [singlePostId]);
This is my code. By using useCallback and useMemo the initial empty re render is not controlled. Initially empty data gets rendered ,after routing the same product get replaced with the selected product.
Routing component
<Header/>
<Routes>
<Route path="/" element={<ProductListing/>} />
<Route exact path="/product/:productId" element={<ProductDetail/>} />
<Route>404 Not Found!</Route>
</Routes>
<Link to={`/product/${id}`}>`
...
const product = useSelector((state) => state.product);
const { productId } = useParams();
console.log(productId);
const { title, image, price, category ,description} = product;
const dispatch = useDispatch();
console.log(product);
const fetchProductDetail = async (id) => {
const response = await axios
.get(`https://fakestoreapi.com/products/${id}`)
.catch((err) => {
console.log("Err: ", err);
});
dispatch(selectedProducts(response.data));
};
useEffect(() => {
if (productId && productId !== "") fetchProductDetail(productId);
return () => {
dispatch(removeSelectedProducts());
};
}, [productId]);
please resolve the issue
I have an incomprehensible problem.
I have 2 React components Signin and Chat, connection to the socket should only occur in second component Chat.
const url = 'http://localhost:4000';
const socket = openSocket(url);
const Chat = () => {
useEffect(() => {
subscribeToChanges();
}, []);
const subscribeToChanges = () => {
socket.on('getAllMessages', message => {
const {userMessage, userId} = message;
setState(prevState => ({
...state,
messages: [...prevState.messages, {userMessage, userId}]
}));
});
};
return (
<div>
</div>
);
};
export default Chat;
But when I update both components in the browser, I see at my backend
logs and they show the same connection to the socket in both.
But as I understand it, this should happen only when i update a `Chat
Maybe this is somehow influenced by the routes, or i don't understand sockets enough
<Switch>
<Route exact path="/">
<SignIn/>
</Route>
<Route exact path="/chat">
<Chat/>
</Route>
</Switch>
You must create your socket instance inside of the component and not in the file otherwise the instance will created when you import the file and not when you render it
Also you must clean ip your socket instance when you unmount the component
const url = 'http://localhost:4000';
const Chat = () => {
const socket = useRef(null);
useEffect(() => {
socket.current = openSocket(url); // create socket here and pass it on
subscribeToChanges();
return () => {
// close socket on unmount
socket.current.close();
}
}, []);
const subscribeToChanges = () => {
socket.current.on('getAllMessages', message => {
const {userMessage, userId} = message;
setState(prevState => ({
...state,
messages: [...prevState.messages, {userMessage, userId}]
}));
});
};
return (
<div>
</div>
);
};
export default Chat;
I am getting data, using useEffect Hook from sockets. Every time I get the response, one particular component rerenders, but I don't pass any socket data to that component.
Code:
const App = () => {
const isMounted = React.useRef(false);
const [getSocketData, setSocketData] = useState([]);
useEffect(() => {
console.log('[App.js] Mounted!');
isMounted.current = true;
const socket = socketIOClient(process.env.REACT_APP_BOARD);
socket.on('ServersInfo', (data) => {
if (isMounted.current) {
setSocketData([...data]);
}
});
socket.on('connect_error', () => {
if (isMounted.current) {
setSocketData([]);
}
console.log('Connection error!');
});
return (() => {
isMounted.current = false;
socket.disconnect();
console.log('[App.js] unmounted');
});
}, []);
const routes = (
<Switch>
<Route path={['/', '/users/:id']} exact component={() => <MainPage axiosInstance={axiosInstance} />} />
<Route path='/servers' render={(spProps) => <ServerPing {...spProps} />} />
<Route render={(nfProps) => <NotFoundComponent {...nfProps} />} />
{/* <Redirect to='/' /> */}
</Switch>
);
return (
<div className="App">
<Layout>
<Suspense fallback={<p>Loading...</p>}>
{routes}
</Suspense>
</Layout>
</div>
);
};
export default App;
What my Components look like:
- App.js
- Layout (doesn't rerender) (which has 3 children) - MainPage (rerenders infinitely), ServerPing (doesn't rerender), NotFoundComponent (doesn't rerender)
The question is: why MainPage Component rerenders infinitely?
I mean MainPage Component and its children unmount and mount again, when socket data fetches which is weird behaviour.
MainPageComponent:
const MainPage = ({ axiosInstance, ...props }) => {
const isMounted = React.useRef(false);
const [loadingPage, setLoadingPage] = useState(true);
const [usernames, setUsernames] = useState([]);
const [currentDay] = useState(new Date().getDay());
useEffect(() => {
isMounted.current = true;
console.log('[MainPage.js] Mounted!');
getUsers();
return () => {
console.log('[MainPage.js] Unmounted!');
isMounted.current = false;
};
}, []);
const getUsers = async () => {
try {
const res = await axiosInstance.get('/users');
const newData = await res.data;
const newArray = [];
newData.map(user => (
newArray.push({id: user._id, flag: user.flag, text: user.name, value: user.name.toLowerCase()})
));
if (isMounted.current) {
setUsernames(newArray);
setLoadingPage(false);
}
} catch {
if (isMounted.current) {
setLoadingPage(false);
}
}
};
return...
Problem is that you are using component prop instead of render prop to render the MainPage which will remount the component on any re-render of App component if there is a callback given to a component prop.
Below code should fix the issue
<Route
path={['/', '/users/:id']}
exact
render={(props) => <MainPage {...props} axiosInstance={axiosInstance} />}
/>
According to the react-router-dom docs
When you use component (instead of render or children, below) the
router uses React.createElement to create a new React element from the
given component. That means if you provide an inline function to the
component prop, you would create a new component every render. This
results in the existing component unmounting and the new component
mounting instead of just updating the existing component. When using
an inline function for inline rendering, use the render or the
children prop (below).