Updating a dynamic TD cell in React with promised data - javascript

I'm working on building on a dynamic table view for data in React. I'm currently getting the data back through a promise, and I'm trying to update a specific TD with the data returned by said promise. However attempting to do so with jQuery gives me an "unrecognized expression" error. I've read that you shouldn't be using jQuery with React anyways, but I can't seem to wrap my head around how to construct my view correctly (I assume I should be creating a child component, but I'm unsure how to update it with promised data). Here's my current code attempting to accomplish this. Thanks for any help!
getThingField(thing, key) {
const self = this;
var user = gun;
if(typeof(thing[key]) === 'object') { //Field requires a lookup of data
var cellKey = thing._['#'] + self.props.linkedFields[key]
cellKey = cellKey.replace(/\s/g, '');
var jGet = '#' + cellKey;
self.gunGetListProp(user, thing[key]['#'], self.props.linkedFields[key]).then(e=> {
//this is my promise that returns my data in 'e'
if(e.length == 1) {
self.updateTD(jGet, e[0]);
}
else {
//I expect an array of length 1 so I'm skipping this for now
}
});
return <td key={cellKey}></td>; //To ensure the cell always renders
}
else { //This is for fields that don't require a lookup and works properly
return (
<td key={thing._['#'] + key}>{thing[key]}</td>
)
}
}
updateTD(cellKey, val) {
$(cellKey).html(val);
}

The reason you read that you should not use jQuery with React is that they operate on two fundamentally different paradigms. jQuery manipulates the DOM directly, while React responds to changes in state, then manipulates the DOM as needed.
The code that you submitted should be changed so that when the promise returns with your data, you call this.setState (assuming you are within a component class). The class will re-render the table based on the new state. The promise should not be called inside the render() function because that is a pure function. Instead, you can call the promise in a lifecycle method such as componentDidMount().
I would review the state and lifecycle documentation here.

Related

Having promise response inside Vue template interpolation

I'm using Vue 2 and the composition API. My current component receives a prop from a parent and I render different data based on it, on the screen - the prop is called "result" and is of type Object, containing multiple info. I receive one "result" at a time, but multiple will be rendered - you can think of my "result" as a google search result - which means that the page will have multiple results.
The issue I have is that for one of the info inside "result", I need to call an asynchronous method and display its result, which is what I cannot accomplish.
Currently this is what I have:
<div>
{{ getBoardName(props.result.boardReference) }}
</div>
Method inside the script:
async function getBoardName(boardReference) {
var result = await entriesApi.getBoardName({
boardReference: boardReference,
});
return result;
}
It displays "[object Promise]", although if I console.log(result) right before returning it, it's just what I need, so it seems to me as if the interpolation doesn't actually wait for the promise result.
I've also tried using "then" inside the interpolation:
{{
getBoardName(props.result.boardReference).then((value) => {
return value;
})
}}
I was thinking about using a computed property but I am not sure how that would work, as the response I need from the method has to be connected to each result individually.
Please ask for further clarifications if needed.
As you thought, the interpolation does not actually wait for the result so this is why you have a Promise object.
Since you are using the composition API, what you can actually do is to use a reactive state (using the ref() function if you are waiting for a primitive type, which seems to be the case here, or the reactive() function for objects) and update the state within the onMounted() lifecycle hook.
setup(props, context) {
const boardGameName = ref("");
onMounted(async () => {
boardGameName.value = await getBoardName(props.result.boardReference);
});
async function getBoardName(boardReference) {
var result = await entriesApi.getBoardName({
boardReference: boardReference,
});
return result;
}
return {
boardGameName,
};
}
Since you are dealing with async data, you could add a loading state so you can show a spinner or something else until the data is available.
If your board reference changes over time, you could use a watcher to update your board game name.
Good luck with your project!

Conditional Rendering of Arrays in React: For vs Map

I'm new to React and building a calendar application. While playing around with state to try understand it better, I noticed that my 'remove booking' function required a state update for it to work, while my 'add booking' function worked perfectly without state.
Remove bookings: requires state to work
const [timeslots, setTimeslots] = useState(slots);
const removeBookings = (bookingid) => {
let newSlots = [...timeslots];
delete newSlots[bookingid].bookedWith;
setTimeslots(newSlots);
}
Add bookings: does not require state to work
const addBookings = (slotid, tutorName) => {
timeslots[slotid].bookedWith = tutorName;
}
I think that this is because of how my timeslot components are rendered. Each slot is rendered from an item of an array through .map(), as most tutorials online suggest is the best way to render components from an array.
timeslots.map(slot => {
if (!slot.bookedWith) {
return <EmptyTimeslot [...props / logic] />
} else {
return <BookedTimeslot [...props / logic]/>
}
})
So, with each EmptyTimeslot, the data for a BookedTimeslot is available as well. That's why state is not required for my add bookings function (emptyTimeslot -> bookedTimeslot). However, removing a booking (bookedTimeslot -> emptyTimeslot) requires a rerender of the slots, since the code cannot 'flow upwards'.
There are a lot of slots that have to be rendered each time. My question is therefore, instead of mapping each slot (with both and information present in each slot), would it be more efficient to use a for loop to only render the relevant slot, rather than the information for both slots? This I assume would require state to be used for both the add booking and remove booking function. Like this:
for (let i=0;i<timeslots.length;i++) {
if (!timeslot[i].bookedWith) {
return <EmptyTimeslot />
} else {
return <BookedTimeslot />
}
}
Hope that makes sense. Thank you for any help.
Your addBooking function is bad. Even if it seems to "work", you should not be mutating your state values. You should be using a state setter function to update them, which is what you are doing in removeBookings.
My question is therefore, instead of mapping each slot (with both and information present in each slot), would it be more efficient to use a for loop to only render the relevant slot, rather than the information for both slots?
Your map approach is not rendering both. For each slot, it uses an if statement to return one component or the other depending on whether the slot is booked. I'm not sure how the for loop you're proposing would even work here. It would just return before the first iteration completed.
This I assume would require state to be used for both the add booking and remove booking function.
You should be using setTimeslots for all timeslot state updates and you should not be mutating your state values. That is true no matter how you render them.

My angular 7 web component #Input<boolean> doesn't work when I bind a false value

I have setup an application which exports web components from angular 7 using angular custom-elements package.
Everything works fine. I am able to bind anything, arrays, objects, strings from javascript using the element instance:
const table = document.createElement('my-table-element');
table.someInputProp = 'Works Great';
table.otherInput = ['my', 'working', 'array'];
The problem comes when I try to bind to a literal false value:
table.myBooleanInput = false
This doesnt change anything on the component #Input myBooleanInput = true
The value is still true for ever. No matter how many times it changes to false.
I'am able to bind it as a truthy value which it renders just fine. The problem is only when using literal false.
Here is a working reproduction of this issue:
https://stackblitz.com/edit/angular-elements-official-example-q4ueqr
Thanks in advance.
PS: If I use the web components from within another angular app, the binding works fine.
I'm actually having this same issue. Was debugging it and there seems to be some code the #angular/elements (I'm using version 7.2.15) package which skips over setting properties on initialize when they evaluate to false.
/** Set any stored initial inputs on the component's properties. */
ComponentNgElementStrategy.prototype.initializeInputs = function () {
var _this = this;
this.componentFactory.inputs.forEach(function (_a) {
var propName = _a.propName;
var initialValue = _this.initialInputValues.get(propName);
if (initialValue) {
_this.setInputValue(propName, initialValue);
}
else {
// Keep track of inputs that were not initialized in case we need to know this for
// calling ngOnChanges with SimpleChanges
_this.uninitializedInputs.add(propName);
}
});
this.initialInputValues.clear();
};
As a workaround you could convert the flag to a string, wrap it in an object, or look at the element attributes from within the component.

add custom prop to Component instance(not via parent) - react-data-grid

Just to provide broader context -
My eventual goal is to enhance editable Cell with "synced" icon if synced with back-end.
I went on trying to add custom prop to a specific editable Cell, to indicate syncedWithBackEnd = true/false, then have custom Formatter to conditionally add style (if sync with DB prop is true).
The issue is, I'm failing to deliver this custom prop to the Cell instance
Tried so far:
Provide callback to Formatter and call it from outside. Didn't find way to call the function on Formatter instance(attached to specific cell)
Set the prop as part of handleRowUpdated logic. Setting the custom prop but it's not reaching the Cell:
var rows = this.getRows();
...
// e.updated.customProp = "whatever" ---> fails to reach Cell
Object.assign(rows[e.rowIdx], e.updated);
this.setState({rows: rows});
Any ideas on how to achieve this? Or maybe there is some obvious way to reach the eventual goal that I've completely missed (must mention I'm new to React and pretty new to js in general).
when the actual sync happens (prior to wanting to update your custom cell) i assume it is done with a callback? if so, then your callback that did the remote ajax call to update the value could then update the this.state.rows.customProp (this.state.rows.whatever.customProp) and once the state has changed and assuming your getRows() is getting from this.state.rows then it will all automatically happen. worse case you may have to add a call to this.forceupdate() after you change the state value. this is based on something i have that sounds similar and works...
//column data inside constructor (could be a const outside component as well)
this.columnData = [
{
key: whatever,
name: whatever,
...
formatter: (props)=>(<div className={props.dependentValues.isSynced? "green-bg" : "")} onClick={(evt)=>this.clickedToggleButton} >{props.value}</div>),
getRowMetaData: (data)=>(data),
},
...
}
]
this is a callback function in my data-grid wrapper component...
//when a span
clickedToggleButton(evt, props){
//do remote ajax call we have an ajax wrapper util we created so pseudo code...
MyUtil.call(url, params, callback: (response)=>{
if(this.response.success){
let rows = this.state.rows;
let isFound = false;
for(let obj of rows){
if(obj.id==props.changedId){
obj.isSynced = true;
isFound = true;
break;
}
}
//not sure if this is needed or not...
if(isFound){ this.forceUpdate(); }
}
}
}
not sure if this helps but may get your started

Incrementing the Material Design Lite Progress bar with React

I've got MDL running with React at the moment and it seems to be working fine at the moment.
I've got the Progress Bar appearing on the page as needed and it loads up with the specified 'progress' on page load when either entering in a number directly:
document.querySelector('#questionnaireProgressBar').addEventListener('mdl-componentupgraded', function() {
this.MaterialProgress.setProgress(10);
})
or when passing in a number via a Variable:
document.querySelector('#questionnaireProgressBar').addEventListener('mdl-componentupgraded', function() {
this.MaterialProgress.setProgress(value);
})
It stops working after this though. I try to update the value via the Variable and it doesn't update. I've been advised to use this:
document.querySelector('.mdl-js-progress').MaterialProgress.setProgress(45);
to update the value but it doesn't work. Even when trying it directly in the console.
When trying via the Console I get the following Error:
Uncaught TypeError: document.querySelector(...).MaterialProgress.setProgress is not a function(…)
When I try to increment the value via the Variable I get no errors and when I console.log(value) I am presented the correct number (1,2,3,4...) after each click event that fires the function (it fires when an answer is chosen in a questionnaire)
What I want to know is if there's something obvious that I'm missing when using MTL and React to make components to work? There was an issue with scope but I seem to have it fixed with the following:
updateProgressBar: function(value) {
// fixes scope in side function below
var _this = this;
document.querySelector('#questionnaireProgressBar').addEventListener('mdl-componentupgraded', function() {
this.MaterialProgress.setProgress(value);
})
},
In React I've got the parent feeding the child with the data via props and I'm using "componentWillReceiveProps" to call the function that updates the progress bar.
I've used the "componentDidMount" function too to see if it makes a difference but it still only works on page load. From what I've read, it seems that I should be using "componentWillReceiveProps" over "componentDidMount".
It's being fed from the parent due to components sending data between each other. I've used their doc's and some internet help to correctly update the parent function to then update the progress bar in the separate component.
updateProgressBarTotal: function(questionsAnsweredTotal) {
this.props.updateProgressBarValue(questionsAnsweredTotal);
}
The parent function looks like the following (I think this may be the culprit):
// this is passed down to the Questions component
updateProgressBarTotal: function(questionsAnsweredTotal) {
this.setState({
progressBarQuestionsAnswered : questionsAnsweredTotal
})
}
I can post up some more of the code if needed.
Thank you
Looks I needed a fresh set of eyes on this.
I moved the function to the child of the parent. It seems that using document.querySelector... when in the parent doesn't find the element but when it's moved to the child where I do all the question logic it seems to be fine. It increments the progress correctly etc now :)
// goes to Questionnaire.jsx (parent) to update the props
updateProgressBarTotal: function(questionsAnsweredTotal) {
// updates the state in the parent props
this.props.updateProgressBarValue(questionsAnsweredTotal);
// update the progress bar with Value from questionsAnsweredTotal
document.querySelector('.mdl-js-progress').MaterialProgress.setProgress(questionsAnsweredTotal);
},
I had same problem in angular2 application.
You don't necessary need to move to the child component.
I found after struggling to find a reasonable fix that you simply have to be sure mdl-componentupgradedevent already occurred before being able to use MaterialProgress.setProgress(VALUE). Then it can be updated with dynamic value.
That is why moving to the child works. In the parent component mdl-componentupgraded event had time to occur before you update progress value
My solution for angular2 in this article
Adapted in a React JS application :
in componentDidMount, place a flag mdlProgressInitDone (initiated to false) in mdl-componentupgraded callback :
// this.ProgBar/nativeElement
// is angular2 = document.querySelector('.mdl-js-progress')
var self = this;
this.ProgBar.nativeElement.addEventListener('mdl-componentupgraded', function() {
this.MaterialProgress.setProgress(0);
self.mdlProgressInitDone = true; //flag to keep in state for exemple
});
Then in componentWillReceiveProps test the flag before trying to update progress value :
this.mdlProgressInitDone ? this.updateProgress() : false;
updateProgress() {
this.ProgBar.nativeElement.MaterialProgress.setProgress(this.currentProgress);
}
After attaching the progress bar to the document, execute:
function updateProgress(id) {
var e = document.querySelector(id);
componentHandler.upgradeElement(e);
e.MaterialProgress.setProgress(10);
}
updateProgress('#questionnaireProgressBar');

Categories

Resources