Progress bar with WEB API - reactjs

[First of all I need to tell that I'm a beginner in ReactJS]
There is a web API that I'm using for creating employee data. With that API, I'm sending employee data array.
http://localhost:5000/api/services/app/Employee/AddEmployees
Input json looks a like,
[{
"id": 5495,
"employeeName": "Sarwen",
"department": "Production",
"factoryLocation": "Denmark"
},
{
"id": 5496,
"employeeName": "Kate",
"department": "HR",
"factoryLocation": "Denmark"
}
.
.
.
.
.
.
.]
There are thousands of data in above json.
The web API got all the data at once and then process inside the server. It works totally fine.
public async Task<bool> AddEmployees(InputDto input)
{
//inserting to DB
}
But it took a little time to process. The issue is, the front end user doesn't know the status of that process. (Front end user have no idea, how many records remaining in the process).
I'm developing a React js application for doing above. This is how I send data to API.
async function saveEmployees(props) {
var data = {
Token : usr
EmpData: props//props will get the employee data set
}
const requestOptions = {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data)
};
const response = await fetch('http://localhost:5000/api/services/app/Employee/AddEmployees', requestOptions);
};
I need to show a kind of progress bar or a percentage. I checked for few progress bar options in ReactJS and it needs a percentage for showing the progress bar. Can any expert help me out of this for showing a progress bar regarding my scenario? Need to get current running (real time) record index or whatever progress to the front end.

You have a few options, which one works best depends on the back end implementation.
Approach 1: Split data in parts, send multiple requests
Unless the data needs to all be submitted at the same time, this is probably the easiest and best option. As your code already loads the data in memory in JS, there's not much code needed to turn it into a loop. Then you can just update the progress bar every time a part finishes.
If you use state, you can just pass the setter of the variable holding the done percentage as an argument to your async function, it's always the same function.
async function uploadBatched (setProgress) {
for (let i = 0; i < items.length; i+=batchSize) {
const chunk = items.slice(i, i + batchSize);
await saveEmployees(chunk);
setProgress(100 * i / items.length )
}
}
The code in the snippet below is a bit different/mangled because SO snippets can't have React and async together :( I did a hack with .then() that should have the same behavior as an async loop.
const {useState, useEffect} = React;
// Generate example data.
const data = Array.from(Array(1000).keys());
const batchSize = 100;
function sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
function saveEmployees(items) {
return sleep(10 * items.length);
};
function uploadBatched(items, setProgress) {
let previous;
console.log(items);
for (let i = 0; i < items.length; i+=batchSize) {
// Workaround code, please read the async version of uploadBatched instead.
// I also cheat and add batchSize, to avoid needing a complex fix for the last batch.
const doBatch = () => {
setProgress(100 * (i+batchSize) / items.length )
const chunk = items.slice(i, i + batchSize);
console.log();
return saveEmployees(chunk);
}
if (previous) {
previous = previous.then(doBatch)
} else {
previous = doBatch();
}
}
}
function App() {
const [progress, setProgress] = useState(0);
return <div>
<button onClick={()=>{uploadBatched(data,setProgress)}}>Upload</button>
<div className="progress-container">
<div style={{width: `${progress}%`}} className="progress">{progress}%</div>
</div>
</div>
}
const root = document.getElementById('root');
ReactDOM.render(<App/>, root);
.progress-container {
border: 1px solid black;
position: relative;
width: 240px;
height: 42px;
}
.progress {
height: 100%;
background: lightgreen;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/18.2.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/18.2.0/umd/react-dom.production.min.js"></script>
<div id="root"></div>
Approach 2: Server side jobs + pulling status
You could send a request and create an asynchronous job to process the data, instead of letting the request wait for a response. The server just acknowledges it has received the request and will asynchronously process it.
Then you can poll a server endpoint that gives the current processing status and set the scrollbar size with it.
From the front end POV this is not harder than the first approach, but the back end infrastructure needs to be there.

Related

how to turn off buffering in react-player?

when I try to play the next video it does not start and I guess the problem is buffering.
P.S my url is video.m3u8 files
It works fine, but when i change url nothing happens, i would like to know how can i stop current video and load a new one whe, url changes ?
here's my rewind function
const showVideo = async () => {
sessionStorage.setItem("sPlayerLinkId", params.id);
const body = new FormData();
const mac = window.TvipStb.getMainMacAddress();
body.append("link_id", params.id);
body.append("mac", mac);
let response = await fetch(getVideo, {
method: "POST",
body: body,
});
let data = await response.json();
if (data.error) {
openDialog("crush");
return 0;
}
if (_isMounted.current) setVideoLink(data.response.url); };
var goToNext = function () {
playerRef.current.seekTo(0, "seconds");
setVideoLink(null);
if (playerInfo.next_id) {
params.id = playerInfo.next_id;
showVideo();
} else navigate(-1);};
<ReactPlayer
url={videoLink}
playing={isPlaying}
ref={playerRef}
key={params.id}
onProgress={() => {
current();
}}
config={{
file: {
forceHLS: true,
},
}}
/>
I would suggest you build your own player from scratch using just react and a style library.
I had similar issues using react-player and I had to resort to building my own custom player in which I could now ensure that buffering is handled the way I expect it to.
I handled buffering using the progress event as follows
const onProgress = () => {
if (!element.buffered) return;
const bufferedEnd = element.buffered.end(element.buffered.length - 1);
const duration = element.duration;
if (bufferRef && duration > 0) {
bufferRef.current!.style.width = (bufferedEnd / duration) * 100 + "%";
}
};
element.buffered represents a collection of buffered time ranges.
element.buffered.end(element.buffered.length - 1) gets the time at the end of the buffer range. With this value, I was able to compute the current buffer range and update the buffer progress accordingly.
I ended up writing an article that would help others learn to build an easily customizable player from scratch using just React and any style library (in this case charkra UI was used).

Querying persisted React WYSIWYG data from MongoDB

thanks in advance!
In summary, I am using React's WYSIWYG rich text editor, and saving the text written in the editor to a MongoDB, data is sent to a server which does the insertion. My issue is that I am unable, after following recommended code, to retrieve the stored data back successfully to display it on my page. This is for a prospective blog post site.
Below I've provided all relevant code:
My Component which sends the data to the server to insert it into MongoDB, (not in order, only relevant code):
<Editor
editorState={editorState}
onEditorStateChange={handleEditorChange}
wrapperClassName="wrapper-class"
editorClassName="editor-class"
toolbarClassName="toolbar-class"
/>
const Practice = () => {
const [editorState, setEditorState] = useState(
() => EditorState.createEmpty(),
);
const [convertedContent, setConvertedContent] = useState(null);
const handleEditorChange = (state) => {
setEditorState(state);
convertContentToRaw();
}
const convertContentToRaw = () => {
const contentState = editorState.getCurrentContent();
setEditorState(editorState: {convertToRaw(contentState)});
}
const stateToSend = JSON.stringify(editorState);
try {
const response = await axios.post('http://localhost:8080/api/insert', {
content: stateToSend
})
} catch(error) {
}
In MongoDB, I've initialized 1 column for storing the WYSIWYG data, I've initialized as an empty JS object:
const wysiwygtest = new mongoose.Schema({
content: {
type: {}
}
});
As a result, my data is inserted into MongoDB as such, with everything desired clearly in data type such as RGBA etc. correct me if I'm wrong but I believe Mongo uses BSON, a form of binary based JSON, so this looks doable for retrieval:
Lastly, the code which is not working correctly, the retrieval. For this, I have no interest just yet in placing the data back into the text editor. Rather, I'd like to display it on the page like a typical blog post. However, I'm unable to even log to the console as of yet.
I am parsing the data back to JSON using JSON.parse, converting JSON to JS object using createFromRaw and using EdiorState (even though I don't have the text editor in this component but this seems to be needed to convert the data fully..) to convert fully:
useEffect( async () => {
try {
const response = await axios.get('http://localhost:8080/api/query', {
_id: '60da9673b996f54d507dbfc5'
});
const content = response;
if(content) {
const convertedContent =
EditorState.createWithContent(convertFromRaw(JSON.parse(content)));
console.log('convertedContent - ', convertedContent);
}
console.log('response - ', content);
} catch(error) {
console.log('error!', error);
}
}, [])
My result for the past day and last night has been the following:
"SyntaxError: Unexpected token o in JSON at position 1" and so I'm unsure what I'm doing wrong in the data retrieval, and possibly even the insertion.
Any ideas? Thanks again!
Edit: For more reference, here is what the data looks like when output to the console without a JSON.stringify, this is the full tree of data. I can see all of the relevant data is there, but how do I convert this data and display it into a div or paragraph tag, for example?
More or less figured this out, see my solution below given the aforementioned implementation:
Firstly, I think my biggest mistake was using JSON.parse(); I did away with this with success. My guess as to why this does not work (even though I inserted into MongoDB as JSON) is because we ultimately need the draft-js.Editor Object to convert the data from the DB into an object type it can understand, in order to subsequently convert into HTML successfully, with all properties.
Below is the code with captions/descriptions:
Retrieve data (in useEffect before React component is rendered:
useEffect( async () => {
console.log('useeffect');
try {
const response = await axios.get('http://localhost:8080/api/query', {
_id: '60da9673b996f54d507dbfc5' //hard-coded id from DB for testing
});
const content = response.data; //get JSON data from MongoDB
if(content) {
const rawContent = convertFromRaw(content); //convert from JSON to contentstate understood by DraftJS, for EditorState obj to use
setEditorState(EditorState.createWithContent(rawContent)); //create EditorState based on JSON data from DB and set into component state
let currentContentAsHTML = draftToHtml(convertToRaw(editorState.getCurrentContent())); //create object which converts contentstate understood by DraftJS into a regular vanilla JS object, then take THAT and convert into HTML with "draftToHtml" function. Save that into our 2nd state titled "convertedContent" to be displayed on page for blog post
setConvertedContent(currentContentAsHTML);
}
} catch(error) {
console.log('error retrieving!', error);
} },[convertedContent]) //ensure dependency with with convertedContent state, DB/server calls take time...
In component render, return HTML which sets the innerHTML in the DOM using/passing the convertedContent state which we converted to proper HTML format in step 1.
return (
<div className="blog-container" dangerouslySetInnerHTML={createMarkup(convertedContent)}></div>
</div>
);
In step 2, we called a function entitled, "createMarkup"; here is that method. It essentially returns HTML object using the HTML converted data originally from our database. This is a bit vulnerable it terms of malicious users being able to intercept that HTML in the DOM, however, so we use a method, "purify" from "DOMPurify" class from 'isomorphic-dompurify" library. I'm using this instead of regular DOMPurify because I am using Next JS and NEXT runs on the server side as well, and DOMPurify only expects client side:
const createMarkup = (html) => {
return {
__html: DOMPurify.sanitize(html)
}
}

Redux how I should handle update with API call

I have an application that triggers many update and I would like to know more about the best way to update the app properly.
In my app, I have 5 slots to fill with books (can be managed by drag and drop). When the app launches, the filled book for the user are loaded and are stored in the state.
Problem : when I update a book, like if I switch the position of 2 books in my list, I must do some operations to say "this book belongs here now and the other one belongs here now, switch!"
I feel like I'm doing some tedious actions because if I just return the whole data (get, after updating) from my API call and call the "load" function (as I do when I launch the app) I will not have to handle the update of the operation.
Plus, it could create bug If I'm loading correctly, but not updating correctly (if I miss position of a book for example)
The benefit I see in a functional update is that I only update the 2 books I need, instead of reload all of them again and again.
What way would be better? Should I get rid of those updates functions and just reload the data entirely? I think there could be also some libraries that cache it to only re-render modified books
Thanks you
Without code it is difficult to fully understand the problem but getting the data from the server has 2 advantages.
You are sure the ui shows the data as it is on the server
Your client code does not need to contain the logic of what needs to happen, the server has this logic. When the logic is refactored in some way they don't go out of sync.
Because of this I usually choose to get the data as is on the server.
One problem with fetching data based on user interaction is that fetching is async so the following can happen:
User does action A, request made for A, user Does action B, request made for B, B request resolves and UI is set to result of request B, request made for A resolves and UI is set to result of A.
So the order the user does the actions does not guarantee the order in which the requests are resolved.
To solve this you can use a helper that resolves only if it was last requested, in the example above when A request resolves the UI does not need to be set with anything because it has already been replaced with another request.
In the example below you can type search value, when the value is 1 character long it'll take 2 seconds to resolve so when you type ab the ab request will resolve before the a request. but because the function making the request is wrapped with the last helper when a resolves it'll will be rejected because it has been replaced with the newer request ab.
//constant to reject with when request is replaced with a
// more recent request
const REPLACED = {
message: 'replaced by more recent request',
};
//helper to resolve only last requested promise
const last = (fn) => {
const check = {};
return (...args) => {
const current = {};
check.current = current;
return Promise.resolve()
.then(() => fn(...args))
.then((result) => {
//see if current request is last request
if (check.current === current) {
return result;
}
//was not last request so reject
return Promise.reject(REPLACED);
});
};
};
const later = (howLong, value) =>
new Promise((resolve) =>
setTimeout(() => resolve(value), howLong)
);
const request = (value) =>
later(value.length === 1 ? 2000 : 10, value).then(
(result) => {
console.log('request resolved:', result);
return result;
}
);
const lastRequest = last(request);
const App = () => {
const [search, setSearch] = React.useState('');
const [result, setResult] = React.useState('');
React.useEffect(() => {
//if you use request instead of lastRequest here
// you see it will break, UI is updated as requests
// resolve without checking if it was the last request
lastRequest(search)
.then((result) => setResult(`result:${result}`))
.catch((err) => {
console.log(
'rejected with:',
err,
'for search:',
search
);
if (err !== REPLACED) {
//if the reject reason is not caused because request was
// replaced by a newer then reject this promise
return Promise.reject(err);
}
});
}, [search]);
return (
<div>
<label>
search
<input
type="text"
value={search}
onChange={(e) => setSearch(e.target.value)}
></input>
</label>
<div>{result}</div>
</div>
);
};
ReactDOM.render(<App />, document.getElementById('root'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>
<div id="root"></div>

Perform Asynchronous Decorations in DraftJS?

I'm trying to perform real-time Named Entity Recognition highlighting in a WYSIWYG editor, which requires me to make a request to my back-end in between each keystroke.
After spending about a week on ProseMirror I gave up on it and decided to try DraftJS. I have searched the repository and docs and haven't found any asynchronous examples using Decorations. (There are some examples with Entities, but they seem like a bad fit for my problem.)
Here is the stripped down Codepen of what I'd like to solve.
It boils down to me wanting to do something like this:
const handleStrategy = (contentBlock, callback, contentState) => {
const text = contentBlock.getText();
let matchArr, start;
while ((matchArr = properNouns.exec(text)) !== null) {
start = matchArr.index;
setTimeout(() => {
// THROWS ERROR: Cannot read property '0' of null
callback(start, start + matchArr[0].length);
}, 200) // to simulate API request
}
};
I expected it to asynchronously call the callback once the timeout resolved but instead matchArr is empty, which just confuses me.
Any help is appreciated!
ok, one possible solution, a example, simple version (may not be 100% solid) :
write a function take editor's string, send it to server, and resolve the data get from server, you need to figure out send the whole editor string or just one word
getServerResult = data => new Promise((resolve, reject) => {
...
fetch(link, {
method: 'POST',
headers: {
...
},
// figure what to send here
body: this.state.editorState.getCurrentContent().getPlainText(),
})
.then(res => resolve(res))
.catch(reject);
});
determine when to call the getServerResult function(i.e when to send string to server and get entity data), from what I understand from your comment, when user hit spacebar key, send the word before to server, this can done by draftjs Key Bindings or react SyntheticEvent. You will need to handle case what if user hit spacebar many times continuously.
function myKeyBindingFn(e: SyntheticKeyboardEvent): string {
if (e.keyCode === 32) {
return 'send-server';
}
return getDefaultKeyBinding(e);
}
async handleKeyCommand(command: string): DraftHandleValue {
if (command === 'send-server') {
// you need to manually add a space char to the editorState
// and get result from server
...
// entity data get from server
const result = await getServerResult()
return 'handled';
}
return 'not-handled';
}
add entity data get from server to specific word using ContentState.createEntity()
async handleKeyCommand(command: string): DraftHandleValue {
if (command === 'send-server') {
// you need to manually add a space char to the editorState
// and get result from server
...
// entity data get from server
const result = await getServerResult()
const newContentState = ContentState.createEntity(
type: 'string',
mutability: ...
data: result
)
const entityKey = contentStateWithEntity.getLastCreatedEntityKey();
// you need to figure out the selectionState, selectionState mean add
// the entity data to where
const contentStateWithEntity = Modifier.applyEntity(
newContentState,
selectionState,
entityKey
);
// create a new EditorState and use this.setState()
const newEditorState = EditorState.push(
...
contentState: contentStateWithEntity
)
this.setState({
editorState: newEditorState
})
return 'handled';
}
return 'not-handled';
}
create different decorators find words with specific entity data, and return different style or whatever you need to return
...
const compositeDecorator = new CompositeDecorator([
strategy: findSubjStrategy,
component: HandleSubjSpan,
])
function findSubjStrategy(contentBlock, callback, contentState) {
// search whole editor content find words with subj entity data
// if the word's entity data === 'Subj'
// pass the start index & end index of the word to callback
...
if(...) {
...
callback(startIndex, endIndex);
}
}
// this function handle what if findSubjStrategy() find any word with subj
// entity data
const HandleSubjSpan = (props) => {
// if the word with subj entity data, it font color become red
return <span {...props} style={{ color: 'red' }}>{props.children}</span>;
};

Displaying array-responses in the form of a Nested menus using React

I'm fairly new to React and stuck regarding a very minor problem. I wrote an UI that essentially calls a service that returns the responses in the form of an array. Now, I need those responses from the service to be displayed in the form of a nested menu. For e. g., one of my axios calls returns a response of [1,2,3,4] and the other axios call returns [1.1,1.2,1.3,..]. I want these responses to be aligned in the form of
1
1.1
1.2
1.3
2
2.1
etc.,
i. e. the UI should show 1,2,3,4 .. and when the user clicks on 1, then 1.1,1.2 etc. should be displayed.
I'm using React, material-ui's components and redux for this.
I have a function to do the above mentioned.. but I'm not sure if I'm doing it right.
handleMenuData() {
var applist = this.props.menuData;
var appNames = [];
var moduleNames = [];
applist.forEach(app => {
app.moduleNames.forEach(module => {
try {
return axios.get(
'service url' + app.name + '/' + module,
);
} catch (error) {
console.error(error);
}
});
appNames.push({
name: app.name,
moduleNames: moduleNames,
});
moduleNames = [];
});
this.setState({
appNames: appNames,
});
}
and in my state,
this.state = {
appList: [],
appNames: [],
moduleNames: [],
};
app names are 1,2,3 and module names are 1.1,1.2 and I was thinking of using ListItemText component from material UI.
I think what you are doing is incorrect. axios.get is an asynchronous function and you are not waiting for the response to come from the server. That's why you get all arrays as empty. Try calling a recursive function like this.
const getModuleNames = (i) => {
axios.get('service url' + applist[i].name + '/' + module)
.then((response) => {
if(i < applist.length){
applist[i].moduleNames = response.data;
appNames.push({
name: applist[i].name,
moduleNames: applist[i].moduleNames
});
getModuleNames(i++);
}
else {
// code you want to execute after adding data to appNames
}
}).catch((err) => {
// error handling
});
}
getModuleNames(0);

Resources