I'm making a project using rabbitMQ and react.
I did connect rabbitMQ server and my react app, and I finally success get some data from server.
I can print those data in console (like using console.log()), however I cannot display data in html.
I tried to use async/await but it didn't work.
function ServerTest() {
var a:any;
const client = createClient("admin", "1111");
const queue = v4();
client.onConnect = function () {
console.log("connected to Stomp");
subscribe(client, 'admin', queue, (payload: {}) => {
a = payload;
a = a.UserInfo.userId
console.log(a)
})
publish(client, 'api.user.info', 'admin', queue, {})
}
client.activate();
return (<div>a: {a}</div>)
}
React does not listen for changes happening in any random variable, be it variable a, so the UI will not be updated automatically. To get the desired functionality, useState hook can be used here.
For that, import the useState function.
import {useState} from 'react'
Inside the function, use it like this
function ServerTest() {
const [ valueA, setValueA ] = useState();
An initial value for valueA can also be passed
const [ valueA, setValueA ] = useState(initialState);
valueA will hold the current state, so use it in the return statement
return (<div>a: {valueA}</div>)
Now, when data from the server is available, use setValueA to update the value of valueA
subscribe(client, 'admin', queue, (payload: {}) => {
setValueA(payload.UserInfo.userId);
By doing this, react will automatically update the UI to match the current state of 'valueA'
If this is in your react component, you should use state to show the value from the api.
import React, { useState } from 'react';
const MyComponent = () => {
const [aState, setA] = useState();
const callApi = () => {
//get values from API here
setA(apiValue);
}
return (
<div>{aState}</div>
)
In your code, return the a value you get from the api and then pass it into setA(a). When the aState value changes, the component will automatically rerender and the result will be shown on the page.
check out this article: https://www.robinwieruch.de/react-hooks-fetch-data
It explains how to fetch data and display it with React
Related
I am trying to send an id to the next page when the user navigates.
I have a homepage where I am fetching an Array of data and using .map to display it in a kind of card-based UI.
Now, when the user clicks the card, they will be navigated to the next page display details about that card.
Suppose the home page is here - localhost:3000
And user clicks the card with an id of 234
They will be navigated to the next page as :
The next page is as - localhost:3000/user/234
Now here I want to display information about that card with an id of 234. FOr that I do need to make a fetch request as such fetch(https://userdatabase/234)
The above 234 is a dynamic id for sure, How can I let this fetch request know to change this id every time a new card has been clicked? Or in other words, How this page "knows" the id of the card ?
Right now, I', using a workaround as :
When the user is at the second page, the URL will be like this localhost:3000/user/386
Get this id in NextJS using useRouter as :
import {useRouter} from 'next/router'
`const router = useRouter()`
fetch(`localhost:3000/user/${router?.query?.user})
I understand taking id from URL and making a fresh quest is not ideal at all and this is causing stale caching issue on the second page.
How do I resolve this in a better way?
Thanks so much for reading.
you need to make a dynamic route : Next.js Docs
for your case make a file pages/user/[id].js
1. Client side
access id with this snippet :
import { useRouter } from 'next/router'
const Component = () => {
const router = useRouter()
const { id } = router.query
return <p>{id}</p>
}
export default Component
2. Server side
you can use it any of the data fetching functions
Snippet for SSR :
export async function getServerSideProps({ params }) {
const { id } = params
// fetch data from database with id
return {
props: {}, // will be passed to the page component as props
}
}
More on what gets passed to data fetching functions as Context : Context Parameter
Add id to the dependencies array of useEffect().
Something along these lines:
import { useState, useEffect } from "react";
import { useRouter } from 'next/router';
function Page() {
const router = useRouter();
const [page, changePage] = useState();
// `query` can only be fully parsed client-side
// so `isReady` flag is needed
const { query, isReady } = router;
const { id } = query;
// also need to parse the query value to undefined or string
const parsedID = id === undefined
? undefined
: Array.isArray(id)
? id[0]
: id;
useEffect(() => {
// refuse to run the effect if query is not ready
// or ID is undefined
if (!isReady || !parsedID ) {
return;
}
// this looks ugly
// but pure promise syntax is even uglier
// and `useEffect()` doesn't accept async functions
(async () => {
// errors are assumed to be handled in the `_app` component
// so no error-handling logic here
const response = await fetch(`localhost:3000/user/${parsedID}`);
const newPage = await response.json();
changePage(newPage);
})()
}, [isReady, parsedID]);
return (
<>
{!page
// show loading placeholder until the page is fetched
? <div>Loading...</div>
// pass the `page` value to whatever component you need
: ...
}
</>
)
}
export default Page;
I am new to React so please forgive me if this issue is entry-level to you.
Basically, I want to do the thing as the title goes,
here I have a function designed for this purpose:
function saveAndUpdate() {
// data processing and wrap up the finalized data, lets call it newData
useEffect(() => {
async function loadData() {
try {
await axios.put(API/updateEntry,{objToUpdate: newData}).then(() => {
const { loading, error, data } = axios.get(API/dataForTheTable)
callback(data);
});
} catch (error) {
console.error(error);
}
}
loadData();
}, []);
}
callback is a function in parent component in order to update the state related to the data immediately after the data is updated.
const handleCallback = (data) => {
setInfo(data);
}
And by running this, I get the classic error which I still do not fully understand:
Uncaught Error: Invalid hook call. Hooks can only be called inside of the body of a function component. This could happen for one of the following reasons:
You might have mismatching versions of React and the renderer (such as React DOM)
You might be breaking the Rules of Hooks
You might have more than one copy of React in the same app
Any help will be appreciated, thank you!
Seems that what you're doing is creating a hook (saveAndUpdate) and trying to access to It somewhere (for example, inside another hook or function).
Althought, a hook to update data (with axios.put, where you need to pass an argument; the data for the update) is not a good approach, as an UseEffect hook will run when the component is mounted, not when the user clicks on a button or whatever. For example, it would be better if you use this for gathering data (get request) from your backend when the component mounts.
Anyway this is a way you could do what you want to achieve, using the useCallback. I've created a button that fires the function and pass some data (just you can see the expected behaviour):
In the saveAndUpdate hook (name it as useSaveAndUpdate, just a convention for hooks names):
import { useEffect, useState } from "react";
export default function useSaveAndUpdate(dataUpdate) {
const [data, setData] = useState(null);
const [updating, setUpdating] = useState(false);
useEffect(() => {
if (updating) {
const req = function () {
// the put request (dataUpdate will be the data we use for the request)...
// for this i'm just going to set data to the argument we've passed
// the idea is to setData to the returned data from the server
setData(dataUpdate);
setUpdating(false);
// setting updating to false, if there's a re-render it won't do the request again
};
req();
}
}, [updating, dataUpdate]);
return [data, updating, setUpdating];
}
In the parent component:
import { useState, useCallback } from "react";
import useSaveAndUpdate from './useSaveAndUpdate'
const parent = () => {
const [updateTarget,setUpdateTarget = useState(null)
const [data,updating,setUpdating] = useSaveAndUpdate(updateTarget)
const updateFunction = useCallback((updateData) => {
setUpdating(true)
//
// ^ Setting updating to true will fire the request inside useSaveAndUpdate when re-render
//
setUpdateTarget(updateData)
// Setting the data for the update (this data will be used in the put request)
},[setUpdating,setUpdateTarget])
return (
<h3>{loading?'loading...':'not loading'}</h3>
<h3>{data?data.name:'data is null'}</h3>
{/*data will be updated when button clicked, so if it's a table just do data.map()...*/}
<button onClick={() => updateFunction({name:'jhon'})}>Click me to update</button>
)
}
So at first render, It will outpout that It's not loading and data is null.
When click It'll say loading for a sec (the time the request lasts), and the data will be displayed ('jhon').
A small codesandbox so you can see how it works.
This question already has answers here:
How to re-render a custom hook after initial render
(3 answers)
Closed 1 year ago.
I have custom hook named useIsUserSubscribed that checks to see a specific user is subscribed. It returns true if the user is subscribed and false if the user is not subscribed...
import { useState, useEffect } from "react";
import { useSelector } from "react-redux";
import { checkSubscription } from "../services";
// this hook checks if the current user is subscribed to a particular user(publisherId)
function useIsUserSubscribed(publisherId) {
const [userIsSubscribed, setUserIsSubscribed] = useState(null);
const currentUserId = useSelector((state) => state.auth.user?.id);
useEffect(() => {
if (!currentUserId || !publisherId) return;
async function fetchCheckSubscriptionData() {
try {
const res = await checkSubscription(publisherId);
setUserIsSubscribed(true);
} catch (err) {
setUserIsSubscribed(false);
}
}
fetchCheckSubscriptionData();
}, [publisherId, currentUserId]);
return userIsSubscribed;
}
export default useIsUserSubscribed;
...I have a button using this hook that renders text conditionally based on the boolean returned from useIsUserSubscribed...
import React, { useEffect, useState } from "react";
import { add, remove } from "../../services";
import useIsUserSubscribed from "../../hooks/useIsUserSubscribed";
const SubscribeUnsubscribeBtn = ({profilePageUserId}) => {
const userIsSubscribed = useIsUserSubscribed(profilePageUserId);
const onClick = async () => {
if (userIsSubscribed) {
// this is an API Call to the backend
await removeSubscription(profilePageUserId);
} else {
// this is an API Call to the backend
await addSubscription(profilePageUserId);
}
// HOW CAN I RERENDER THE HOOK HERE!!!!?
}
return (
<button type="button" className="sub-edit-unsub-btn bsc-button" onClick={onClick}>
{userIsSubscribed ? 'Subscribed' : 'Unsubscribed'}
</button>
);
}
After onClick I would like to rerender my the useIsUserSubscribed hook So that my button text toggles. Can this be done? Should I use a different approach?
SubscribeUnsubscribeBtn has a dependency on useIsUserSubscribed, but useIsUserSubscribed don't depend on anything from SubscribeUnsubscribeBtn.
Instead, useIsUserSubscribed is keeping a local state. You have a couple of choices here:
Move the state regarding whetehr user is subscribed or not one level up, since you are using Redux, perhaps in Redux.
Communicate to useIsUserSubscribed that you need to change its internal state.
For 1)
const [userIsSubscribed, setUserIsSubscribed] = useState(null);
move this state to Redux store and use it with useSelector.
For 2), return an array of value and callback from the hook, instead of just the value. It will allow you to communicate from component back into the hook.
In useIsUserSubscribed,
return [userIsSubscribed, setUserIsSubscribed];
Then in onClick, you can call setUserIsSubscribed(false), changing the hook's internal state, and re-rendering your component.
import React, {useState, useEffect} from 'react';
import socketIO from 'socket.io-client';
import Container from 'react-bootstrap/Container';
function Sock() {
const [textData, setTextData] = useState([]);
useEffect(() => {
const socket = socketIO('http://127.0.0.1:5009');
socket.on('chat message', (text) => {
setTextData([
textData.push(text)
]);
console.log(textData);
});
},[]);
return (
<Container>
<h1>Socket</h1>
{textData.map((text) => <li>{text}</li>)}
</Container>
);
}
export default Sock;
With help I have managed to display something on the UI but it currently only displays the array count and not the object inside the array. I am fairly new to React hooks any help or suggestions would be greatly appreciated.
There are few ways to take care of stale closure but for your simple case, you can use the functional update syntax to get the previous text data, and return a new instance of array (do not mutate existing state, using push).
useEffect(() => {
const socket = socketIO("http://127.0.0.1:5009");
socket.on("chat message", text => {
setTextData(previousTextData => [...previousTextData, text]);
});
}, []);
Using the callback approach, you don't need to put textData in the useEffect's dependency array.
You have to join your data (text) with existing data (textData), try with:
setTextData([...textData, text]);
I am using useEffect hook and getting a list of users data with fetch call using function getStoreUsers which dispatches an action on response and stores shopUsers(which is an array) inside the redux store.
In array dependency, I am writing [shopUsers]. I don't know why it is causing infinite rendering.
Here is how I am using useEffect hook:
useEffect(() => {
const { getStoreUsers, shopUsers } = props;
setLoading(true);
getStoreUsers().then(() => {
setLoading(false);
}).catch(() => {
setLoading(false);
});
}, [shopUsers]);
I want to re-render component only when data inside shopUsers array changes.
If I write shopUsers.length inside array dependency. It stops to re-render.
But, let's suppose I have have a page which opens up when the user clicks on a userList and updates user data on next page. After the update I want the user to go back to the same component which is not unmounted previously. So, In this case array length remains the same, but data inside in of array index is updated. So shopUsers.length won't work in that case.
You can make a custom hook to do what you want:
In this example, we replace the last element in the array, and see the output in the console.
import React, { useState, useEffect, useRef } from "react";
import ReactDOM from "react-dom";
import { isEqual } from "lodash";
const usePrevious = value => {
const ref = useRef();
useEffect(() => {
ref.current = value;
});
return ref.current;
};
const App = () => {
const [arr, setArr] = useState([2, 4, 5]);
const prevArr = usePrevious(arr);
useEffect(() => {
if (!isEqual(arr, prevArr)) {
console.log(`array changed from ${prevArr} to ${arr}`);
}
}, [prevArr]);
const change = () => {
const temp = [...arr];
temp.pop();
temp.push(6);
setArr(temp);
};
return (
<button onClick={change}>change last array element</button>
)
};
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
Live example here.
Your effect is triggered based on the "shopUsers" prop, which itself triggers a redux action that updates the "shopUsers" prop and thats why it keeps infinitely firing.
I think what you want to optimize is the rendering of your component itself, since you're already using redux, I'm assuming your props/state are immutable, so you can use React.memo to re-render your component only when one of its props change.
Also you should define your state/props variable outside of your hooks since they're used in the scope of the entire function like so.
In your case, if you pass an empty array as a second param to memo, then it will only fire on ComponentDidMount, if you pass null/undefined or dont pass anything, it will be fired on ComponentDidMount + ComponentDidUpdate, if you want to optimise it that even when props change/component updates the hook doesn't fire unless a specific variable changes then you can add some variable as your second argument
React.memo(function(props){
const [isLoading, setLoading] = useState(false);
const { getStoreUsers, shopUsers } = props;
useEffect(() => {
setLoading(true);
getStoreUsers().then(() => {
setLoading(false);
}).catch((err) => {
setLoading(false);
});
}, []);
...
})