How to implement a queue using RxJS? - node.js

I'm designing an API (in NodeJS) which receives a list of user ids, for instance, and access Google Firebase's API to send a notification to those users.
Considering Firebase having a quota limit on how many requests you can concurrently send to, I buffered the Ids and send the requests one by one to Firebase with a delay of two seconds. Here's some simplified sample code for explanation:
app.post('/send-request', (req, res, next) =>{
const userIds = req.body.userIds;
‎const streams = userIds.map((userId) => {
‎ return Observable
‎ .fromPromise(fetch(`firebase url`, {
‎ method: 'POST',
‎ headers: ...,
body: ...
‎ }))
‎ .delay(2000)
‎ .retryWhen(attempts => {
‎ return attempts.zip(Rx.Observable.range(1, 4))
.mergeMap(([error, i]) => {
if (i > 3) {
return Rx.Observable.throw(error);
}
console.log(`Wait ${i} seconds, then retry!`);
return Rx.Observable.timer(i * 1000);
});
‎ });
});
const stream = Observable.merge(...streams);
‎ stream.subscribe();
});
This can handle the case when a single request consists of a lot of users. However, if my API receives similar requests concurrently, it's bound to fail.
Therefore, I want to buffer all these user ids in a queue where this queue can keep receiving more and more user ids an buffer them, and at the same time 'top' the queue by sending requests to Firebase at a steady rate. However, I don't know how to do it with RxJS. Do I have to use an Scheduler? Or is there actually a better solution than using Rx?
Note: I understand Javascript is single-threaded so it's not exactly concurrency, I only used this word so you can get the idea better.

I think I managed to come up with something, the key part is to use Subject to publish values, then use the zip operator to emit value on a regular interval. It'd be even better if I can take the values on demand, but the current solution is already much better than my original method.
const subject = new Rx.Subject();
const stream = subject
.zip(Rx.Observable.interval(3000), function(a, b) { return a; });
stream.subscribe(
(x) => { console.log(`onNext: ${val}`); },
(e) => { console.log(`onError: ${e}`); },
() => { console.log('onCompleted'); });
I used VueJS to build a simple web page for a little demo.
const app = new Vue({
el: '#app',
data: {
subject: undefined,
stream: undefined,
count: 0,
emitHistory: [],
disableBtn: true
},
created() {
console.log('created');
this.subject = new Rx.Subject();
this.stream = this.subject
.zip(Rx.Observable.interval(3000), function(a, b) { return a; })
// .observeOn(Rx.Scheduler.queue); // not working
this.stream.subscribe(
(val) => {
console.log(`onNext: ${val}`);
this.emitHistory.push(val);
if (val === this.count) {
this.disableBtn = false;
} else {
this.disableBtn = true;
}
},
(e) => { console.log(`onError: ${e}`); },
() => { console.log('onCompleted'); });
},
methods: {
clickHandler() {
this.count++;
this.subject.onNext(this.count);
},
clear() {
this.count = 0;
this.emitHistory = [];
}
}
});
/*
Rx.Observable
.fromArray([1,2,3])
.zip(Rx.Observable.interval(500), function(a, b) { return a; })
.subscribe(
function(x) { document.write(x + '<br \>'); },
null,
function() { document.write("complete"); });
*/
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.13/vue.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/rxjs/4.0.7/rx.all.js"></script>
<div id="app">
<button #click="clickHandler()">Click me</button>
<button #click="clear()" v-bind:disabled="disableBtn">Clear</button>
<div>
<h5>Count: {{count}}</h5>
</div>
<div>
<ul>
<li v-for="(item, idx) in emitHistory" v-bind:key="idx">{{item}}</li>
</ul>
</div>
</div>

Related

Testing a React component that uses redux toolkit and RTKQuery

I have been making an app using redux toolkit and RTKQuery, and hit a stumbling block on how to test a component that uses slices:
Component
export const Status = () => {
const selectedKidId = useSelector(getSelectedKidId);
const { selectedKid } = useGetKidsQuery(undefined, {
selectFromResult: ({ data }) => ({
selectedKid: data?.find((kid: KidType) => kid.id === selectedKidId),
}),
});
return (
<section>
<p>
Active:{' '}
{selectedKidId !== null ? selectedKid?.firstName : 'Select a kid'}
</p>
</section>
);
};
Test
test('title renders as expected', () => {
renderWithProviders(<Status />, {
preloadedState: { kids: { selectedKidId: '0' } },
});
expect(screen.getByText(/Monsters!/i)).toBeInTheDocument();
});
As you see I can add a selectedKidId in the preloadedState but the component also uses a generated hook useGetKidsQuery which return a list of kids, I don't know how or if I can add this to preloadedState as its an apiSlice.
How would I get my list of kids data into this test?

NextJS component

I need to reload a remote JSON every 30 seconds. I currently do it this way in reactJS but since moving to NextJS it does not work
The issue is that the following work fine in my current ReactJS website but as soon as I Moved it to NextJS it printing our errors everywhere.
Mainly with the following
fetchTimeout
sessionStorage
export default function MediaControlCard(props) {
const fetchTimeout = (url, ms, { signal, ...options } = {}) => {
const controller = new AbortController();
const promise = fetch(url, { signal: controller.signal, ...options });
if (signal) signal.addEventListener("abort", () => controller.abort());
const timeout = setTimeout(() => controller.abort(), ms);
return promise.finally(() => clearTimeout(timeout));
};
const controller = new AbortController();
const podcast = props.podcast;
const classes = useStyles();
var token = uuidv4();
// alert(sessionStorage['uuid']);
if(!sessionStorage['uuid']){
sessionStorage.setItem("uuid",token);
}
if(!sessionStorage['station']){
sessionStorage.setItem("station","DRN1");
}
if(!sessionStorage['live']){
sessionStorage.setItem("live",true);
}
var icyStream = "https://api.drn1.com.au:9000/station/"+sessionStorage.station+"?uuid="+sessionStorage['uuid'];
var streamurl = icyStream;//window.com_adswizz_synchro_decorateUrl(icyStream);
React.useEffect(() => {
nowplaying();
document.getElementById("player").muted = false;
});
if(podcast){
alert('test');
}
/*if(!sessionStorage.getItem("station")){
sessionStorage.setItem("station","DRN1");
}*/
function nowplaying(){
// alert("hello");
if(sessionStorage.live === true){
document.getElementById("podcast-only").style.display='none';
}
fetchTimeout(`https://api.drn1.com.au:9000/nowplaying/`+sessionStorage.station+`?uuid=`+sessionStorage['uuid'], 3000, { signal: controller.signal })
.then(res => res.json())
.then(
(result) => {
//console.log("testing player"+result.data);
if(sessionStorage.getItem("live") === 'true'){
switch(result.data[0].track.songtype)
{
case "A":
AdSystem(result.data[0]);
break;
case "S":
Song(result.data[0]);
document.getElementById("Now_Playing_Artist").innerHTML = result.data[0].track.artist;
document.getElementById("Now_Playing_Title").innerHTML = result.data[0].track.title;
document.getElementById("Now_Playing_Cover").style.backgroundImage = "url('"+result.data[0].track.imageurl+"')";
break;
default:
Song(result.data[0]);
document.getElementById("Now_Playing_Artist").innerHTML = result.data[0].track.artist;
document.getElementById("Now_Playing_Title").innerHTML = result.data[0].track.title;
document.getElementById("Now_Playing_Cover").style.backgroundImage = "url('"+result.data[0].track.imageurl+"')";
break;
}
fetch(`https://itunes.apple.com/search?term=${result.data[0].track[0].artist}+${result.data[0].track[0].title}&limit=1`)
.then(res => res.json())
.then(
(result) => {
if(result.results[0]){
document.getElementById("buylink").href = result.results[0].collectionViewUrl;
document.getElementById("buynow").style.display = "block";
}
else
{
document.getElementById("buynow").style.display = "none";
}
})
}
})
.then(console.log)
.catch(error => {
console.error(error);
if (error.name === "AbortError") {
// fetch aborted either due to timeout or due to user clicking the cancel button
} else {
// network error or json parsing error
}
});
setTimeout(function(){nowplaying()}, 10000);
}
return (<>
<Card id="nowplayinginfo_card" className={classes.card}>
<CardMedia
id="Now_Playing_Cover"
className={classes.cover}
image="//tvos.adstichr.com/client/resources/images/stations/Indie/DRN1-Logo.png"
title="Live from space album cover"
/>
<div className={classes.details} id="adstichrNP">
<CardContent className={classes.content} id="song">
<Typography variant="subtitle1">
Now Playing
</Typography>
<Typography id="Now_Playing_Title" component="h6" variant="h6">
{props.artist}
</Typography>
<Typography id="Now_Playing_Artist" variant="subtitle1" color="textSecondary">
{props.song}
</Typography>
</CardContent>
<div id="buynow" className={classes.buynow}>
<a id="buylink" target="_blank" href="#Blank"><img alt="buynow" src="https://linkmaker.itunes.apple.com/assets/shared/badges/en-us/music-lrg-1c05919c6feae5d4731d4399cd656cd72e1fadc4b86d4bd7dc93cb8f3227cb40.svg"/></a>
</div>
<div id="podcast-only" className={classes.controls}>
<audio id="player" className={classes.player} controls controlsList="nodownload" autoPlay muted>
<source src={streamurl}
type="audio/mpeg"
/>
</audio>
</div>
</div>
</Card>
<Card className={classes.card} id="adbanner">
<CardContent className={classes.content} id="adstichr">
</CardContent>
</Card>
</>
)
}
How do I ac achieve this with NextJS. I thought anything I put into component with nextjs would just work the same as ReactJS - clearly not.
NextJS has server-side rendering features for your concern. I believe that you should use getStaticProps there is a special property in it called revalidate it will allow you to make requests on every timeout you wish to use. I took an example from official documentation of latest nextjs(version 11.0)
Docs: https://nextjs.org/docs/basic-features/data-fetching#getstaticprops-static-generation
function Blog({ posts }) {
return (
<ul>
{posts.map((post) => (
<li>{post.title}</li>
))}
</ul>
)
}
// This function gets called at build time on server-side.
// It may be called again, on a serverless function, if
// revalidation is enabled and a new request comes in
export async function getStaticProps() {
const res = await fetch('https://.../posts')
const posts = await res.json()
return {
props: {
posts,
},
// Next.js will attempt to re-generate the page:
// - When a request comes in
// - At most once every 10 seconds
revalidate: 10, // In seconds
}
}
export default Blog
You are not showing the errors but I suspect it is related to the server-side rendering feature of next.js.
document is defined only on the browser and since useEffect gets executed only on the browser you are calling nowPlaying inside the useEffect. That is the right thing. However sessionStorage (whatever is the package is) also has to be called on the browser.
You should be always retrieving the data from the storage inside useEffect, before component renders.
Yes, you can't achieve this with proper NextJS. I am using useSWR library, it has some "update" intervals as an option.
You can check it here. ("options" part)
you can use getServerSideProps. Make sure its a page component. getServerSideProps, getStaticProps only works in page component.
function Page({ data }) {
// Render data...
}
// This gets called on every request
export async function getServerSideProps() {
// Fetch data from external API
const res = await fetch(`https://.../data`)
const data = await res.json()
// Pass data to the page via props
return { props: { data } }
}
export default Page

React 17.0.1 basic onChange is not updating values into state [duplicate]

I am trying to learn hooks and the useState method has made me confused. I am assigning an initial value to a state in the form of an array. The set method in useState is not working for me, both with and without the spread syntax.
I have made an API on another PC that I am calling and fetching the data which I want to set into the state.
Here is my code:
<div id="root"></div>
<script type="text/babel" defer>
// import React, { useState, useEffect } from "react";
// import ReactDOM from "react-dom";
const { useState, useEffect } = React; // web-browser variant
const StateSelector = () => {
const initialValue = [
{
category: "",
photo: "",
description: "",
id: 0,
name: "",
rating: 0
}
];
const [movies, setMovies] = useState(initialValue);
useEffect(() => {
(async function() {
try {
// const response = await fetch("http://192.168.1.164:5000/movies/display");
// const json = await response.json();
// const result = json.data.result;
const result = [
{
category: "cat1",
description: "desc1",
id: "1546514491119",
name: "randomname2",
photo: null,
rating: "3"
},
{
category: "cat2",
description: "desc1",
id: "1546837819818",
name: "randomname1",
rating: "5"
}
];
console.log("result =", result);
setMovies(result);
console.log("movies =", movies);
} catch (e) {
console.error(e);
}
})();
}, []);
return <p>hello</p>;
};
const rootElement = document.getElementById("root");
ReactDOM.render(<StateSelector />, rootElement);
</script>
<script src="https://unpkg.com/#babel/standalone#7/babel.min.js"></script>
<script src="https://unpkg.com/react#17/umd/react.production.min.js"></script>
<script src="https://unpkg.com/react-dom#17/umd/react-dom.production.min.js"></script>
Neither setMovies(result) nor setMovies(...result) works.
I expect the result variable to be pushed into the movies array.
Much like .setState() in class components created by extending React.Component or React.PureComponent, the state update using the updater provided by useState hook is also asynchronous, and will not be reflected immediately.
Also, the main issue here is not just the asynchronous nature but the fact that state values are used by functions based on their current closures, and state updates will reflect in the next re-render by which the existing closures are not affected, but new ones are created. Now in the current state, the values within hooks are obtained by existing closures, and when a re-render happens, the closures are updated based on whether the function is recreated again or not.
Even if you add a setTimeout the function, though the timeout will run after some time by which the re-render would have happened, the setTimeout will still use the value from its previous closure and not the updated one.
setMovies(result);
console.log(movies) // movies here will not be updated
If you want to perform an action on state update, you need to use the useEffect hook, much like using componentDidUpdate in class components since the setter returned by useState doesn't have a callback pattern
useEffect(() => {
// action on update of movies
}, [movies]);
As far as the syntax to update state is concerned, setMovies(result) will replace the previous movies value in the state with those available from the async request.
However, if you want to merge the response with the previously existing values, you must use the callback syntax of state updation along with the correct use of spread syntax like
setMovies(prevMovies => ([...prevMovies, ...result]));
Additional details to the previous answer:
While React's setState is asynchronous (both classes and hooks), and it's tempting to use that fact to explain the observed behavior, it is not the reason why it happens.
TLDR: The reason is a closure scope around an immutable const value.
Solutions:
read the value in render function (not inside nested functions):
useEffect(() => { setMovies(result) }, [])
console.log(movies)
add the variable into dependencies (and use the react-hooks/exhaustive-deps eslint rule):
useEffect(() => { setMovies(result) }, [])
useEffect(() => { console.log(movies) }, [movies])
use a temporary variable:
useEffect(() => {
const newMovies = result
console.log(newMovies)
setMovies(newMovies)
}, [])
use a mutable reference (if we don't need a state and only want to remember the value - updating a ref doesn't trigger re-render):
const moviesRef = useRef(initialValue)
useEffect(() => {
moviesRef.current = result
console.log(moviesRef.current)
}, [])
Explanation why it happens:
If async was the only reason, it would be possible to await setState().
However, both props and state are assumed to be unchanging during 1 render.
Treat this.state as if it were immutable.
With hooks, this assumption is enhanced by using constant values with the const keyword:
const [state, setState] = useState('initial')
The value might be different between 2 renders, but remains a constant inside the render itself and inside any closures (functions that live longer even after render is finished, e.g. useEffect, event handlers, inside any Promise or setTimeout).
Consider following fake, but synchronous, React-like implementation:
// sync implementation:
let internalState
let renderAgain
const setState = (updateFn) => {
internalState = updateFn(internalState)
renderAgain()
}
const useState = (defaultState) => {
if (!internalState) {
internalState = defaultState
}
return [internalState, setState]
}
const render = (component, node) => {
const {html, handleClick} = component()
node.innerHTML = html
renderAgain = () => render(component, node)
return handleClick
}
// test:
const MyComponent = () => {
const [x, setX] = useState(1)
console.log('in render:', x) // ✅
const handleClick = () => {
setX(current => current + 1)
console.log('in handler/effect/Promise/setTimeout:', x) // ❌ NOT updated
}
return {
html: `<button>${x}</button>`,
handleClick
}
}
const triggerClick = render(MyComponent, document.getElementById('root'))
triggerClick()
triggerClick()
triggerClick()
<div id="root"></div>
I know that there are already very good answers. But I want to give another idea how to solve the same issue, and access the latest 'movie' state, using my module react-useStateRef.
As you understand by using React state you can render the page every time the state change. But by using React ref, you can always get the latest values.
So the module react-useStateRef let you use state's and ref's together. It's backward compatible with React.useState, so you can just replace the import statement
const { useEffect } = React
import { useState } from 'react-usestateref'
const [movies, setMovies] = useState(initialValue);
useEffect(() => {
(async function() {
try {
const result = [
{
id: "1546514491119",
},
];
console.log("result =", result);
setMovies(result);
console.log("movies =", movies.current); // will give you the latest results
} catch (e) {
console.error(e);
}
})();
}, []);
More information:
react-usestsateref
I just finished a rewrite with useReducer, following #kentcdobs article (ref below) which really gave me a solid result that suffers not one bit from these closure problems.
See: https://kentcdodds.com/blog/how-to-use-react-context-effectively
I condensed his readable boilerplate to my preferred level of DRYness -- reading his sandbox implementation will show you how it actually works.
import React from 'react'
// ref: https://kentcdodds.com/blog/how-to-use-react-context-effectively
const ApplicationDispatch = React.createContext()
const ApplicationContext = React.createContext()
function stateReducer(state, action) {
if (state.hasOwnProperty(action.type)) {
return { ...state, [action.type]: state[action.type] = action.newValue };
}
throw new Error(`Unhandled action type: ${action.type}`);
}
const initialState = {
keyCode: '',
testCode: '',
testMode: false,
phoneNumber: '',
resultCode: null,
mobileInfo: '',
configName: '',
appConfig: {},
};
function DispatchProvider({ children }) {
const [state, dispatch] = React.useReducer(stateReducer, initialState);
return (
<ApplicationDispatch.Provider value={dispatch}>
<ApplicationContext.Provider value={state}>
{children}
</ApplicationContext.Provider>
</ApplicationDispatch.Provider>
)
}
function useDispatchable(stateName) {
const context = React.useContext(ApplicationContext);
const dispatch = React.useContext(ApplicationDispatch);
return [context[stateName], newValue => dispatch({ type: stateName, newValue })];
}
function useKeyCode() { return useDispatchable('keyCode'); }
function useTestCode() { return useDispatchable('testCode'); }
function useTestMode() { return useDispatchable('testMode'); }
function usePhoneNumber() { return useDispatchable('phoneNumber'); }
function useResultCode() { return useDispatchable('resultCode'); }
function useMobileInfo() { return useDispatchable('mobileInfo'); }
function useConfigName() { return useDispatchable('configName'); }
function useAppConfig() { return useDispatchable('appConfig'); }
export {
DispatchProvider,
useKeyCode,
useTestCode,
useTestMode,
usePhoneNumber,
useResultCode,
useMobileInfo,
useConfigName,
useAppConfig,
}
With a usage similar to this:
import { useHistory } from "react-router-dom";
// https://react-bootstrap.github.io/components/alerts
import { Container, Row } from 'react-bootstrap';
import { useAppConfig, useKeyCode, usePhoneNumber } from '../../ApplicationDispatchProvider';
import { ControlSet } from '../../components/control-set';
import { keypadClass } from '../../utils/style-utils';
import { MaskedEntry } from '../../components/masked-entry';
import { Messaging } from '../../components/messaging';
import { SimpleKeypad, HandleKeyPress, ALT_ID } from '../../components/simple-keypad';
export const AltIdPage = () => {
const history = useHistory();
const [keyCode, setKeyCode] = useKeyCode();
const [phoneNumber, setPhoneNumber] = usePhoneNumber();
const [appConfig, setAppConfig] = useAppConfig();
const keyPressed = btn => {
const maxLen = appConfig.phoneNumberEntry.entryLen;
const newValue = HandleKeyPress(btn, phoneNumber).slice(0, maxLen);
setPhoneNumber(newValue);
}
const doSubmit = () => {
history.push('s');
}
const disableBtns = phoneNumber.length < appConfig.phoneNumberEntry.entryLen;
return (
<Container fluid className="text-center">
<Row>
<Messaging {...{ msgColors: appConfig.pageColors, msgLines: appConfig.entryMsgs.altIdMsgs }} />
</Row>
<Row>
<MaskedEntry {...{ ...appConfig.phoneNumberEntry, entryColors: appConfig.pageColors, entryLine: phoneNumber }} />
</Row>
<Row>
<SimpleKeypad {...{ keyboardName: ALT_ID, themeName: appConfig.keyTheme, keyPressed, styleClass: keypadClass }} />
</Row>
<Row>
<ControlSet {...{ btnColors: appConfig.buttonColors, disabled: disableBtns, btns: [{ text: 'Submit', click: doSubmit }] }} />
</Row>
</Container>
);
};
AltIdPage.propTypes = {};
Now everything persists smoothly everywhere across all my pages
React's useEffect has its own state/lifecycle. It's related to mutation of state, and it will not update the state until the effect is destroyed.
Just pass a single argument in parameters state or leave it a black array and it will work perfectly.
React.useEffect(() => {
console.log("effect");
(async () => {
try {
let result = await fetch("/query/countries");
const res = await result.json();
let result1 = await fetch("/query/projects");
const res1 = await result1.json();
let result11 = await fetch("/query/regions");
const res11 = await result11.json();
setData({
countries: res,
projects: res1,
regions: res11
});
} catch {}
})(data)
}, [setData])
# or use this
useEffect(() => {
(async () => {
try {
await Promise.all([
fetch("/query/countries").then((response) => response.json()),
fetch("/query/projects").then((response) => response.json()),
fetch("/query/regions").then((response) => response.json())
]).then(([country, project, region]) => {
// console.log(country, project, region);
setData({
countries: country,
projects: project,
regions: region
});
})
} catch {
console.log("data fetch error")
}
})()
}, [setData]);
Alternatively, you can try React.useRef() for instant change in the React hook.
const movies = React.useRef(null);
useEffect(() => {
movies.current='values';
console.log(movies.current)
}, [])
The closure is not the only reason.
Based on the source code of useState (simplified below). Seems to me the value is never assigned right away.
What happens is that an update action is queued when you invoke setValue. And after the schedule kicks in and only when you get to the next render, these update action then is applied to that state.
Which means even we don't have closure issue, react version of useState is not going to give you the new value right away. The new value doesn't even exist until next render.
function useState(initialState) {
let hook;
...
let baseState = hook.memoizedState;
if (hook.queue.pending) {
let firstUpdate = hook.queue.pending.next;
do {
const action = firstUpdate.action;
baseState = action(baseState); // setValue HERE
firstUpdate = firstUpdate.next;
} while (firstUpdate !== hook.queue.pending);
hook.queue.pending = null;
}
hook.memoizedState = baseState;
return [baseState, dispatchAction.bind(null, hook.queue)];
}
function dispatchAction(queue, action) {
const update = {
action,
next: null
};
if (queue.pending === null) {
update.next = update;
} else {
update.next = queue.pending.next;
queue.pending.next = update;
}
queue.pending = update;
isMount = false;
workInProgressHook = fiber.memoizedState;
schedule();
}
There's also an article explaining the above in the similar way, https://dev.to/adamklein/we-don-t-know-how-react-state-hook-works-1lp8
I too was stuck with the same problem. As other answers above have clarified the error here, which is that useState is asynchronous and you are trying to use the value just after setState. It is not updating on the console.log() part because of the asynchronous nature of setState, it lets your further code to execute, while the value updating happens on the background. Thus you are getting the previous value. When the setState is completed on the background it will update the value and you will have access to that value on the next render.
If anyone is interested to understand this in detail. Here is a really good Conference talk on the topic.
https://www.youtube.com/watch?v=8aGhZQkoFbQ
I found this to be good. Instead of defining state (approach 1) as, example,
const initialValue = 1;
const [state,setState] = useState(initialValue)
Try this approach (approach 2),
const [state = initialValue,setState] = useState()
This resolved the rerender issue without using useEffect since we are not concerned with its internal closure approach with this case.
P.S.: If you are concerned with using old state for any use case then useState with useEffect needs to be used since it will need to have that state, so approach 1 shall be used in this situation.
If we have to update state only, then a better way can be if we use the push method to do so.
Here is my code. I want to store URLs from Firebase in state.
const [imageUrl, setImageUrl] = useState([]);
const [reload, setReload] = useState(0);
useEffect(() => {
if (reload === 4) {
downloadUrl1();
}
}, [reload]);
const downloadUrl = async () => {
setImages([]);
try {
for (let i = 0; i < images.length; i++) {
let url = await storage().ref(urls[i].path).getDownloadURL();
imageUrl.push(url);
setImageUrl([...imageUrl]);
console.log(url, 'check', urls.length, 'length', imageUrl.length);
}
}
catch (e) {
console.log(e);
}
};
const handleSubmit = async () => {
setReload(4);
await downloadUrl();
console.log(imageUrl);
console.log('post submitted');
};
This code works to put URLs in state as an array. This might also work for you.
With custom hooks from my library, you can wait for the state values to update:
useAsyncWatcher(...values):watcherFn(peekPrevValue: boolean)=>Promise - is a promise wrapper around useEffect that can wait for updates and return a new value and possibly a previous one if the optional peekPrevValue argument is set to true.
(Live Demo)
import React, { useState, useEffect, useCallback } from "react";
import { useAsyncWatcher } from "use-async-effect2";
function TestComponent(props) {
const [counter, setCounter] = useState(0);
const [text, setText] = useState("");
const textWatcher = useAsyncWatcher(text);
useEffect(() => {
setText(`Counter: ${counter}`);
}, [counter]);
const inc = useCallback(() => {
(async () => {
await new Promise((resolve) => setTimeout(resolve, 1000));
setCounter((counter) => counter + 1);
const updatedText = await textWatcher();
console.log(updatedText);
})();
}, []);
return (
<div className="component">
<div className="caption">useAsyncEffect demo</div>
<div>{counter}</div>
<button onClick={inc}>Inc counter</button>
</div>
);
}
export default TestComponent;
useAsyncDeepState is a deep state implementation (similar to this.setState (patchObject)) whose setter can return a promise synchronized with the internal effect. If the setter is called with no arguments, it does not change the state values, but simply subscribes to state updates. In this case, you can get the state value from anywhere inside your component, since function closures are no longer a hindrance.
(Live Demo)
import React, { useCallback, useEffect } from "react";
import { useAsyncDeepState } from "use-async-effect2";
function TestComponent(props) {
const [state, setState] = useAsyncDeepState({
counter: 0,
computedCounter: 0
});
useEffect(() => {
setState(({ counter }) => ({
computedCounter: counter * 2
}));
}, [state.counter]);
const inc = useCallback(() => {
(async () => {
await new Promise((resolve) => setTimeout(resolve, 1000));
await setState(({ counter }) => ({ counter: counter + 1 }));
console.log("computedCounter=", state.computedCounter);
})();
});
return (
<div className="component">
<div className="caption">useAsyncDeepState demo</div>
<div>state.counter : {state.counter}</div>
<div>state.computedCounter : {state.computedCounter}</div>
<button onClick={() => inc()}>Inc counter</button>
</div>
);
}
var [state,setState]=useState(defaultValue)
useEffect(()=>{
var updatedState
setState(currentState=>{ // Do not change the state by get the updated state
updateState=currentState
return currentState
})
alert(updateState) // the current state.
})
Without any addtional NPM package
//...
const BackendPageListing = () => {
const [ myData, setMyData] = useState( {
id: 1,
content: "abc"
})
const myFunction = ( x ) => {
setPagenateInfo({
...myData,
content: x
})
console.log(myData) // not reflecting change immediately
let myDataNew = {...myData, content: x };
console.log(myDataNew) // Reflecting change immediately
}
return (
<>
<button onClick={()=>{ myFunction("New Content")} }>Update MyData</button>
</>
)
Not saying to do this, but it isn't hard to do what the OP asked without useEffect.
Use a promise to resolve the new state in the body of the setter function:
const getState = <T>(
setState: React.Dispatch<React.SetStateAction<T>>
): Promise<T> => {
return new Promise((resolve) => {
setState((currentState: T) => {
resolve(currentState);
return currentState;
});
});
};
And this is how you use it (example shows the comparison between count and outOfSyncCount/syncCount in the UI rendering):
const App: React.FC = () => {
const [count, setCount] = useState(0);
const [outOfSyncCount, setOutOfSyncCount] = useState(0);
const [syncCount, setSyncCount] = useState(0);
const handleOnClick = async () => {
setCount(count + 1);
// Doesn't work
setOutOfSyncCount(count);
// Works
const newCount = await getState(setCount);
setSyncCount(newCount);
};
return (
<>
<h2>Count = {count}</h2>
<h2>Synced count = {syncCount}</h2>
<h2>Out of sync count = {outOfSyncCount}</h2>
<button onClick={handleOnClick}>Increment</button>
</>
);
};
Use the Background Timer library. It solved my problem.
const timeoutId = BackgroundTimer.setTimeout(() => {
// This will be executed once after 1 seconds
// even when the application is the background
console.log('tac');
}, 1000);
// replace
return <p>hello</p>;
// with
return <p>{JSON.stringify(movies)}</p>;
Now you should see, that your code actually does work. What does not work is the console.log(movies). This is because movies points to the old state. If you move your console.log(movies) outside of useEffect, right above the return, you will see the updated movies object.

How to fetch (Express) data ONLY once the (React) form-submitted data has been successfully received and served?

I'm currently building a league of legends (a MOBA or multiplayer online battle arena game) search-based web app that essentially allows the user to search for their summoner's name and obtain general information regarding their search input. (The data is provided by the game's own third-party api)
I've been able to successfully retrieve the form data and perform the intended backend processes, however, upon the client's initial render, my results-listing component is already trying to fetch the nonexistent processed data.
How do I prevent the server request from firing until the server has actually successfully served the data?
(abridged single-component client example)
the summoner data endpoint is set to http://localhost:3001/api/summoner
server does not contain any additional routes
const App = () => {
const [summName, setSummName] = useState('');
const summonerFormData = new FormData();
// let data;
const findSummoner = () => {
summonerFormData.set('summonerName', summName);
}
// problem here
const data = axios.get('http://localhost:3001/api/summoner');
// axios.get('http://localhost:3001/api/summoner')
// .then(res => {
// data = res;
// });
return (
<div>
<form
method="POST"
action="http://localhost:3001/api/summoner"
onSubmit={findSummoner}
>
<input
value={summName}
name="summName"
onChange={e => setSummName(e.target.value)}
/>
<button type="submit">submit</button>
</form>
{data !== undefined &&
<div className="results">
data.map(match => {
<div>
<p>{match.kills}</p>
<p>{match.deaths}</p>
<p>{match.assists}</p>
</div>
})
</div>
}
</div>
)
}
Here's the Repo for some more context, but please don't hesitate to ask if you need more information or have any questions at all!
I really appreciate any help I can get!
Thanks!
Edits:
I've also tried using the useEffect hook considering the lifecycle point I'm trying to fetch would be componentDidMount, but wasn't quite sure what the solution was. Doing more research atm!
Close, but no cigar. Request gets stuck at 'pending'.
let data;
const fetchData = () => {
axios.get('http://localhost:3001/api/summoner');
};
useEffect(() => {
if (summName !== '') {
fetchData();
}
}, summName);
I tried putting the axios request within an async function and awaiting on the request to respond, and it seems to be working, however, the server is still receiving undefined when the client starts, which then is attempting to be fetched, never allowing the promise to be fulfilled.
const fetchData = async () => {
await axios
.get('http://localhost:3001/api/summoner')
.then(res => {
data = res;
})
.catch(() => {
console.log('error');
});
};
useEffect(() => {
fetchData();
}, [])
So I took the advice and recommendations from #imjared and #HS and I'm literally so close..
I just have one more problem... My data-mapping component is trying to map non-existent data before actually receiving it, giving me an error that it's unable to map match of undefined..
const [modalStatus, setModalStatus] = useState(false);
const [loading, setLoading] = useState(false);
const [data, setData] = useState({ hits: [] });
const [summName, setSummName] = useState('');
const [summQuery, setSummQuery] = useState('');
const summonerFormData = new FormData();
const prepareResults = async () => {
await setSummQuery(summName);
};
const findSummoner = async () => {
setLoading(true);
setModalStatus(false);
await summonerFormData.set('summonerName', summQuery);
};
useEffect(() => {
const fetchData = async () => {
if (summQuery) {
setData({ hits: [] });
console.log('fetching');
await axios
.get('http://localhost:3001/api/summoner')
.then(res => {
setData(res.data);
setLoading(false);
setModalStatus(true);
return data;
})
.catch(error => {
console.log(error);
});
}
};
fetchData();
}, [summQuery]);
SUCCESS! Thank you guys! Here's what ended up working for me:
const findSummoner = async () => {
setSummQuery(summName);
};
useEffect(() => {
setData({ hits: [] });
summonerFormData.set('summonerName', summQuery);
const fetchData = async () => {
setModalStatus(false);
setLoading(true);
if (summQuery !== '') {
setLoading(true);
console.log('fetching');
await axios
.get('/api/summoner')
.then(res => {
setData({
hits: res.data,
});
setError(false);
setLoading(false);
setModalStatus(true);
return data;
})
.catch(() => {
setError(true);
console.log('error');
});
}
};
if (summQuery !== '') {
fetchData();
}
}, [summQuery]);
This flow will help you design better -
1. User - input
2. Hit - search
3. Set loading in state - true,
5. Set data in state - empty
6. Call api
7. Get data
8. Then, set data in state
6. Set loading in state - false
Along the side in the render/return -
1. if loading in the state - indicate loading.
2. if loading done( false ) and data is not empty - show data.
3. if loading done and data is empty - indicate 'not-found'.
Coming to the initial render part - the axios.get() calls the api, which should only be initiated once the form is submitted in the case. Therefore, that logic should be moved inside the event-handler.

How to render the React component with dynamic data realtime from socket.io high efficiency

My front-end page is made by React + Flux, which sends the script data to back-end nodejs server.
The script data is an Array which contains the linux shell arguments (more than 100000). When to back-end received, it will execute the linux shell command.
Just an example:
cat ~/testfile1
cat ~/testfile2
.
.
.
(100000 times ...etc)
When the backend finished one of the linux shell commands, I can save the readed content to result data. Therefore, socket.io will emit the result data to the front-end.
I want to get the result data from my webpage in real time, so I have done some stuff in my project below.
My React component code:
import React from 'react';
import AppActions from '../../../actions/app-actions';
import SocketStore from '../../../stores/socket-store';
import ResultStore from '../../../stores/result-store';
function getSocket () {
return SocketStore.getSocket();
}
function getResult () {
return ResultStore.getResultItem();
}
class ListResultItem extends React.Component {
constructor () {
super();
}
render () {
return <li>
{this.props.result.get('name')} {this.props.result.get('txt')}
</li>;
}
}
class ShowResult extends React.Component {
constructor () {
super();
this.state = {
socket: getSocket(),
result: getResult()
};
}
componentWillMount () {
ResultStore.addChangeListener(this._onChange.bind(this));
}
_onChange () {
this.setState({
result: getResult()
});
}
render () {
return <div>
<ol>
{this.state.result.map(function(item, index) {
return <ListResultItem key={index} result={item} />;
})}
</ol>
</div>;
}
componentDidMount () {
this.state.socket.on('result', function (data) {
AppActions.addResult(data);
});
}
}
My Flux store (ResultStore) code:
import AppConstants from '../constants/app-constants.js';
import { dispatch, register } from '../dispatchers/app-dispatcher.js';
import { EventEmitter } from 'events';
import Immutable from 'immutable';
const CHANGE_EVENT = 'changeResult';
let _resultItem = Immutable.List();
const _addResult = (result) => {
let immObj = Immutable.fromJS(result);
_resultItem = _resultItem.push(immObj);
}
const _clearResult = () => {
_resultItem = _resultItem.clear();
}
const ResultStore = Object.assign(EventEmitter.prototype, {
emitChange (){
this.emit( CHANGE_EVENT );
},
addChangeListener (callback) {
this.on(CHANGE_EVENT, callback);
},
removeChangeListener (callback) {
this.removeListener(CHANGE_EVENT, callback);
},
getResultItem () {
return _resultItem;
},
dispatcherIndex: register(function (action) {
switch (action.actionType) {
case AppConstants.ADD_RESULT:
_addResult(action.result);
break;
case AppConstants.CLEAR_RESULT:
_clearResult();
break;
}
ResultStore.emitChange();
})
});
However, the page will become very slow after rendering more than 1000 datas. How to enhance the performance for rendering? I need to execute the linux script persistently more than 3 days. Any solutions? Thanks~
Is there any need to render all the data on screen? If not then there are a few ways to deal with cutting down the amount of visible data.
Filter / Search
You can provide a search/filter component that complements the list and creates a predicate function that can be used to determine whether each item should or should not be rendered.
<PredicateList>
<Search />
<Filter />
{this.state.result
.filter(predicate)
.map(function(item, index) {
return <ListResultItem key={index} result={item} />;
})
}
</PredicateList>
Lazy Load
Load the items only when they are asked for. You can work out whether item is needed by keeping track of whether it would be onscreen, or whether the mouse was over it.
var Lazy = React.createClass({
getInitialState: function() {
return { loaded: false };
},
load: function() {
this.setState({ loaded: true });
},
render: function() {
var loaded = this.state.loaded,
component = this.props.children,
lazyContainer = <div onMouseEnter={this.load} />;
return loaded ?
component
lazyContainer;
}
});
Then simply wrap your data items inside these Lazy wrappers to have them render when they are requested.
<Lazy>
<ListResultItem key={index} result={item} />
</Lazy>
This ensures that only data needed by the user is seen. You could also improve the load trigger to work for more complex scenarios, such as when the component has been onscreen for more then 2 seconds.
Pagination
Finally, the last and most tried and tested approach is pagination. Choose a limit for a number of data items that can be shown in one go, then allow users to navigate through the data set in chunks.
var Paginate = React.createClass({
getDefaultProps: function() {
return { items: [], perPage: 100 };
},
getInitialState: function() {
return { page: 0 };
},
next: function() {
this.setState({ page: this.state.page + 1});
},
prev: function() {
this.setState({ page: this.state.page - 1});
},
render: function() {
var perPage = this.props.perPage,
currentPage = this.state.page,
itemCount = this.props.items.length;
var start = currentPage * perPage,
end = Math.min(itemCount, start + perPage);
var selectedItems = this.props.items.slice(start, end);
return (
<div className='pagination'>
{selectedItems.map(function(item, index) {
<ListResultItem key={index} result={item} />
})}
<a onClick={this.prev}>Previous {{this.state.perPage}} items</a>
<a onClick={this.next}>Next {{this.state.perPage}} items</a>
</div>
);
}
});
These are just very rough examples of implementations for managing the rendering of large amounts of data in efficient ways, but hopefully they will make enough sense for you to implement your own solution.

Resources