Why this strange thing happening to my DOM element? - reactjs

So I basically started learning react-redux and I am in the middle of creating some simple store app. I would introduce my problem in a few bullet points.
I have got a basic store with some states and the Navbar where I have the Cart button.
After the click of the cart button, I want to open the cart list and rotate the Cart icon for 90deg.
I want to put everything in the one function and attach it to onClick()
my code looks like that: https://codepen.io/szygendaborys/pen/wZYZor?editors=0010
The main problem is that all works if I cancel the statement after else. That one:
else {
style = 'none';
cartArrow.style.transform = 'rotate(0deg)'
}
If I put back that statement, I receive an error: TypeError: Cannot read property 'style' of null connected to
cartArrow.style.transform = 'rotate(0deg)'
Can anyone help me and explain why the if statement works and else does not?

Your cartArrow element likely hasn't rendered at the point of your DOM query (getElementById), which is an anti-pattern in React to begin with. Make use of refs instead.
<i
ref={(ref) => this.cartArrow = ref}
id='cartArrow'
className="fas fa-angle-right"
></i>
this.cartArrow.style.transform = 'rotate(0deg)'

Related

React IonSelect not rendering pre selected value from state

I'm stuck on the following issue from a project I have taken over. I have a page that has a component with an IonSelect. The options of the select are loaded from data obtained from a server and set in the state whilst being passed as a prop to the select, There is also a user object being loaded that contains a value that should be used as a pre selected value. The IonSelect doesn't show the pre selected text value when screen loads. If I click the IonSelect then the selected value shows. I tried a timeout to see if the render was happening before the data is loaded but it makes no difference. This question here shows the same problem but for an angular app. Is there something similar I can do for react?
The previous dev was new to react so I'm trying to refactor some code for efficiency and readability. Is there any advice that can be offered without code being uploaded? Currently, select is in a 5th child component and is using prop drilling to pass the data down.
Update: please see some of the code I've managed to pull for this issue.
I have tried using a standard html select and the pre selected value shows fine, so the problem is maybe within the IonSelect element or its wrapper? I have also tried overriding the compare to function of the select as explained in the docs. No joy. I've spent too many hours trying to solve this to no avail.
<IonItem className="statusField">
<IonLabel position="stacked">Job Status</IonLabel>
<IonSelect
id="jobStatus"
value={props.theJob.jobStatus!.toString()}
style={{color: getStatusColour(props.theJob.jobStatus)}}
interface="popover"
onIonChange={(e) => {
console.log("the value")
//document.getElementById("jobStatus")!.style.color = getStatusColour(parseInt((document.getElementById("jobStatus") as HTMLInputElement).value));
props.switchCancel();
props.setDirtyFlag(true);
}}>
{props.theJob.jobStatus && props.jobStatuses?.map((jobStatus, i) =>{
return<IonSelectOption
key={`Jstat ${i}${jobStatus.recordID}`}
value={jobStatus.recordID.toString()}
>
{jobStatus.summaryStatus.toString()}</IonSelectOption>
})}
</IonSelect>
</IonItem>
UPDATE
I think I've got it going. I had to do the following but it now renders the pre-selected value on screen refresh and page load.
In the component i added a Ref to the select and updated the selected value using a useEffect with a watch on my prop:
let mySelect = useRef<HTMLIonSelectElement|null>(null)
React.useEffect(()=>{
mySelect.current!.value = props.theJob.jobStatus?.toString();
},[props.theJob.jobStatus])
In the IonSelect I set the ref and also set the value of the select to the refs current value:
<IonSelect
id="jobStatus"
ref={mySelect}
value={mySelect.current?.value} ....
Finally, in the parent component where the data is being fetched. I ensure I await the fetch of the options of the select prior to awaiting the data for the preselected value fetch. Its now working as expected. The UI seems responsive and there doesn't seem to be a noticeable overhead. Hopefully it will help someone if they stumble upon this.

Add Input Field onClick stateless component React Electron

I'm new to electron, and working on trying to get React/Typescript with Hooks and ApplicationContext to all come together. I didn't create the framework and I need to learn how to make this work. Just setting the context to avoid answers like, use Redux instead. :)
I have a function stateless component that is a form. It needs to be stateless so I have access to values that are held in ApplicationContext. I'm trying to render extra input fields on button click, and so far when I click the button Electron calls the method, and then re-renders. I have searched high and low, and have been banging my head on this for a few hours. I apologize in advance if there is already an answer out there.
So far the code that displays the button looks as such:
<div className="form-group">
<button onClick={() => addUrls()}>
Add a URL
</button>
</div>
And the method is just printing to the console at the moment. It looks as such:
const addUrls = () => {
console.log('clicked')
}
the print statement is getting to the console, and then Electron re-renders. The rest of the component's methods are called, and are behaving in a predictable way. I'm really confused as to why this particular action is causing renders. If anyone can point me in the direction of an answer, or point out where I am doing something dumb in my code, I would be very much grateful.
Try replacing onClick={() => addUrls()} with onClick={addUrls}. The former method will create a new reference on every render, possibly causing rerenders.
Turns out I was doing something dumb. Because my button is inside a form, and I didn't specify the button type, it was doing the default behavior of "submit" which causes a render with the onClick.
The solution was to add type="button" to the button and now it's solved.
<div className="form-group">
<button onClick={addUrls} type="button">
Add a Reference URL
</button>
</div>
I hope this can save someone some time if they come across this problem in the future.

Why does accessing id work different on if and else in react?

So I basically started learning react-redux and I am in the middle of creating some simple store app. I would introduce my problem in a few bullet points.
I have got a basic store with some states and the Navbar where I have the Cart button.
After the click of the cart button, I want to open the cart list and rotate the Cart icon for 90deg.
I want to put everything in the one function and attach it to onClick()
my code looks like that: https://codepen.io/szygendaborys/pen/wZYZor?editors=0010
The main problem is that all works if I cancel the statement after else. That one:
else {
style = 'none';
cartArrow.style.transform = 'rotate(0deg)'
}
If I put back that statement, I receive an error: TypeError: Cannot read property 'style' of null connected to
cartArrow.style.transform = 'rotate(0deg)'
Can anyone help me and explain why the if statement works and else does not?

How do I tell tell a child element in React to seek its video element?

I have a simple React app with a video player and chart displaying data about the video. Both are in their own components at the top level:
class App extends Component {
...
render() {
return (
<div className="App">
<VideoDisplay .../>
<MetricsDisplay .../>
</div>
)
}
}
Inside VideoDisplay.js is a <video> element that I want to control, specifically by seeking its play position, which I can do using videoElement.currentTime=seekToTime. I want to control this from the MetricsDisplay element: if I click on the graph I want the video to seek to the time clicked on. But to do that it seems I have to call a function in VideoDisplay.js from App.js when it receives the click from MetricsDisplay.js. But this is imperative instead of declarative, and indeed I'm having trouble implementing this in React. Is it even possible? How else could I tell the video in an element to seek, triggered by a click from a sibling element?
I have a workaround that I'll post as an answer but I'm very convinced it's pretty sub-optimal from a design perspective, and will cause code maintenance headaches. So what's a better method? What the "React" way to do something like this?
See attached screenshot: when clicking on the graph in the right, the video should seek to the clicked point in time.
So to get around this I can pass in a seek time in the props, and to prevent it from repeatedly seeking I can increment a seekId counter. This seems very backwards, both because I'm re-creating imperative logic using the very declarative logic meant to avoid it, and because I'm indicating that the video element has a persistent "property" of the position I want the video to seek to now and the id of the command to do so.
That is, I respond to a click from the MetricsDisplay element with this function in App.js:
class App extends Component {
...
setPlayheadTime(time) {
this.setState((state, props) => ({seekTo: time, seekId: state.seekId + 1}));
}
...
}
I pass both seekTo and seekId to VideoDisplay and handle it in a lifecycle function in MetricsDisplay.js:
class VideoDisplay extends Component {
...
componentWillUpdate() {
if (this.props.seekTo && this.props.seekId !== this.seekId) { this.seekTo(this.props.seekTo); }
}
seekTo(time) {
console.log(`(${this.props.seekId}) Seek to ${time}`);
this.seekId = this.props.seekId;
document.getElementById("the-video").currentTime = time;
}
...
}
This works, but I have to believe this is not the best way to accomplish what in "normal" javascript is a simple callback function. Any suggestions? What am I missing?

dangerouslySetInnerHtml doesn't update during render

So I made a component for including content-editable components in my app. I copied it from some gist I believe, then edited to what i needed.
The code is below. When I edit it, it triggers updates on the parent just fine, but when I attempt to set props.html in the parent, it doesn't reflect in the UI.
FURTHER, the console.log shows that this.props.html is equal to '' a blank string, yet the UI doesn't update, and maintains the text that was originally in there.
I don't understand how this is possible... dangerouslySetInnerHtml = {__html: ''} should make it so the UI reflects an empty string... it feels like it should be impossible for it to show the old text.
var React = require('react');
var ContentEditable = React.createClass({
render: function(){
//TODO: find where html=undefined and fix it! So I can remove this? Maybe I should keep this safety.
var html = this.props.html || '';
console.log('content editable render, html: ', this.props.html);
return <div id="contenteditable"
onInput={this.emitChange}
onBlur={this.emitChange}
contentEditable
dangerouslySetInnerHTML={{__html: html}}></div>;
},
shouldComponentUpdate: function(nextProps){
return nextProps.html !== this.getDOMNode().innerHTML;
},
emitChange: function(){
var html = this.getDOMNode().innerHTML;
if (this.props.onChange && html !== this.lastHtml) {
this.props.onChange({
target: {
value: html
}
});
}
this.lastHtml = html;
}
});
module.exports = ContentEditable;
(A little background, I'm trying to clear my input after submitting it to be saved. The clearing isn't working, hence this question.)
I got a very similar problem using contentEditable and shouldComponentUpdate, it looks like there is a bug when resetting the innerHTML to the same previous value using dangerouslySetInnerHTML function (or prop) (I think it does not work even if you insert the code without using it) ... I suspect (this is just an idea) that React compares the last value set through dangerouslySetInnerHTML with the new one you are trying to send and decides not to update the component because it is the same (even if the real innerHtml has changed due to user interactions, because those interactions does not trigger any state or props update on React).
Solution: The easiest solution I found was to use a different key each time I needed it to re-render. for example using key={Date()}.
Example: Here you can find your code (I changed some of it to make it work), when you type '?' into the div, the text inside the ContentEditable component should become an empty string (i.e. ''), it works only once, the second time you type '?' won't work because the innerHTML for react will be the same to the one you're setting (i.e. an empty string so it won't update the component).
And here, I added the key={Date()} (this is the easiest way to show you that this work, but it is not the best way to set a unique key each time it re-render) to the editable component, now you can type any number of '?' and it will work.
I found another solution that is probably better than generating random keys. Putting a key specifically on the div that calls #dangerouslySetInnerHtml, and not just on the component itself
<div class='wrapper'>
<div key={this.props.thing.id} dangerouslySetInnerHtml={this.props.thing.content} />
</div>
This is not the case here but make sure you always dangerouslyset html on div tag and never on span, p ... because if span element child is div, there will be a problem.
Solved rerender bug to me
My (very simple) React (version 16) app: It has a contentEditable <div>.
It successfully re-renders this <div> upon a progression of submit button clicks. Instead of dangerouslySetInnerHtml, I used ref={el => this.myRefElem = el} with componentWillUpdate(nextProps) { this.myRefElem.innerHTML = nextProps.myInputText; } For me, nextProps was important for the proper value to re-render. See my app's project files, to see the rest of the required code.
CLICK-HERE​ to see my React app. It has a button to download its (development mode) project files. It (basically) only has an index.js file. - - - This app was initiated by mlbrgl, who asked me for an alternative technique.
I ran into the same issue (React 16) and used an approach suggested by MLR which consists in dropping dangerouslySetInnerHTML and using componentDidMount() instead for the initial render and componentDidUpdate() for any subsequent renders.
Solution here, adapted to React 16: https://codepen.io/mlbrgl/pen/PQdLgb
These hooks would perform the same update, directly updating innerHTML from props:
componentDidMount() {
this.updateInnerHTMLFromProps();
}
componentDidUpdate() {
this.updateInnerHTMLFromProps();
}
updateInnerHTMLFromProps() {
this.refEl.innerHTML = this.props.html;
}
This makes it clearer (for me at least) to see what is really going on, without having the false expectation that dangerouslySetInnerHTML would keep the DOM in sync in all circumstances, as suggested by Mike Woodcock here https://stackoverflow.com/a/38548616/9408759.
For a complete view of the problem and both solutions outlined here, please check https://codepen.io/mlbrgl/pen/QQVMRP.
Adding key property for an element with dangerouslySetInnerHTML did resolve my issue.
As a key I used
key={new Date().getTime()} // timestamp

Resources