Reactjs: Callback for dangerouslySetInnerHTML complete - reactjs

I'm currently having a React Component like this:
<div id="product-content" className="bz">
<a className="anchor" id="content-anchor"></a>
<h2 className="title-section">Thông tin sản phẩm</h2>
<div className="fixed-menu-content">
<div className="container">
<ul>
<li>Thông tin sản phẩm</li>
<li>Video sản phẩm</li>
<li>Đánh giá (19)</li>
<li>Hướng dẫn mua hàng</li>
</ul>
</div>
</div>
<div dangerouslySetInnerHTML={{__html: description}}></div>
</div>
It seems that dangerouslySetInnerHTML doesn't impact to Component Lifecycle. I put this line in componentDidMount, but it return wrong result:
let b = $("#product-content").height(); // wrong: 600, true: 6500
If I try to run above line in console dev tool, it returns true result because component has been rendered completely.
How can I trigger callback for dangerouslySetInnerHTML?

It looks like the DOMSubtreeModified event has been deprecated in favor of the Mutation Observer API.
You could refactor the code proposed by lustoykov to use the new API:
class ContentRenderer extends React.Component {
componentDidMount() {
this.observer = new MutationObserver(this.handleMutation);
this.observer.observe(this.myElement, {
// Check config in https://developer.mozilla.org/en-US/docs/Web/API/MutationObserver
childList: true,
attributes: true,
characterData: true
});
}
componentWillUnmount() {
this.observer.disconnect();
}
handleMutation() {
console.log('mutation');
}
render() {
return (
<div
dangerouslySetInnerHTML={{ __html: "<div>Test</div>" }}
ref={(myElement) => { this.myElement = myElement; }}
/>
);
}
}

There isn't a callback for completion of dangerouslySetInnerHTML yet, you'll have to resort to refs and DOMSubtreeModified.
// use ref to get the DOM Node
<div
dangerouslySetInnerHTML={{__html: description}}
ref={myElement => this.myElement = myElement}
>
</div>
// Listen for changes inside the DOM Node
componentDidMount() {
this.myElement.addEventListener('DOMSubtreeModified', () => {
// execute your logic here...
});
}
// Don't forget to clean up the listener
componentWillUnmount() {
this.myElement.removeEventListener('DOMSubtreeModified');
}
PS.
Be very careful with this event (DOMSubtreeModified) it is easy to cause an infinite loop if you decide to change the DOM inside the event handler.

Related

Where to use componentDidUpdate in React [duplicate]

This question already has answers here:
When to use 'componentDidUpdate' method?
(9 answers)
Closed 2 years ago.
I am a complete newbie in the world of development. I am trying to make an app where we fetch movies. We can search and sort. I don't want to use any plugins as of now.
I have been successful in fetching the data and showing on the grid. Also, searching is working well. But I'm confused if I shall put the searching method in componentDidUpdate React lifecycle hook or not. If it shall be how to do it?
Following is my code:
class App extends Component
{
constructor(props)
{
super(props);
this.state = {
movies: [],
filteredMovies: []
}
}
componentDidMount()
{
axios.get('https://api.themoviedb.org/3/movie/top_rated?api_key={0}&language=en-US&page=1')
.then(response => {
this.setState({
movies : response.data.results,
filteredMovies : response.data.results
});
});
}
componentDidUpdate()
{
???
}
searchBy = (columnSearch, evt) =>
{
console.log(columnSearch);
let movie = this.state.movies;
if (evt!==undefined && evt.target.value !== undefined && evt.target.value.length > 0) {
movie = movie.filter(function(i) {
console.log(columnSearch);
return i[columnSearch].toString().match( evt.target.value );
});
}
this.setState({
filteredMovies: movie
});
}
render(){
const movieRows =( this.state.filteredMovies ===[] || this.state.filteredMovies === undefined)?[]:
this.state.filteredMovies.map( (rowData, index) => <Movie key ={index} {...rowData} />);
if(movieRows !== [] && movieRows !== undefined && movieRows !== null)
return (
<div className="table">
<div className="header row">
<div className="headercenter">Movie Id
<input type="text" onChange={(evt)=>this.searchBy('id',evt)}/>
</div>
<div className="headercenter"> Title <input type="text" onChange={(evt)=>this.searchBy('title',evt)}/></div>
{/* <div className="" onClick={() => this.sortBy('overview')}>OverView</div> */}
<div className="headercenter" >Popularity<input type="text" onChange={(evt)=>this.searchBy('popularity',evt)}/></div>
<div className="headercenter" >Rating<input type="text" onChange={(evt)=>this.searchBy('vote_average',evt)}/></div>
</div>
<div className="body">
{movieRows}
</div>
</div>
) ;
};
}
export default App;
const movie = (props) => (
<div className="row">
<div>{props.id}</div>
<div>{props.title}</div>
{/* <div>{props.overview}</div> */}
<div>{props.popularity}</div>
<div>{props.vote_average}</div>
</div>
);
export default movie;
ComponentDidUpdate called when any state update take places inside the component, so you can put search method inside ComponentDidUpdate or any method but normally we do things inside ComponentDidUpdate which do require some state change and also do not require user input. For any searching you must be requiring user input so I don't recommend that you should put search method inside ComponentDidUpdate instead put that method on onChange event handler.
Note: ComponentDidUpdate if not handled correctly i.e., you update any state inside this without any condition will cause Infinite Loop
IF you want to call your api in componentDidUpdate method. You should use if condition. Because when your dom is update this method will trigger.
It should look like something like this. I do not know your logic. Please change it for your use case.
componentDidUpdate(prevProps, prevState){
if(prevProps.movies.lenght != prevState.movies.lenght){
// call api
}
}
You don't need to put the searching function in componentDidUpdate, as it will be automatically called when the input field changes. The componentDidUpdate function will be called when the component rerenders (for example after state changes).
What you want, is to call the search function when the input in the input field changes, for which I would add keyup event like so:
<!DOCTYPE html>
<html>
<body>
<p>Write in the text field</p>
<input type="text" id="mySelect" />
<p>Down here will show what you write</p>
<p id="demo"></p>
<script>
document.getElementById('mySelect').addEventListener('keyup', search)
function search(event) {
console.log(event.target.value)
document.getElementById('demo').innerText = event.target.value
}
</script>
</body>
</html>

Focus on (scroll to) a DIV element in React

I have a toggle DIV element and I want to focus on (scroll to) it when it opens.
I followed the doc together with tabindex="-1" with no effect.
class App extends Component {
constructor(props) {
super(props);
this.state = {
formVisible: false
}
this.formRef = React.createRef();
this.onReply = this.onReply.bind(this);
}
onReply(e) {
this.formRef.current.focus(); // doesn't have any effect
this.setState({formVisible: true});
}
render() {
return (
<div>
<div>
<a onClick={this.onReply} href="#">Reply</a>
</div>
<div>
....
</div>
<div ref={this.formRef} tabindex="-1">
{this.state.formVisible &&
<form>
...
</form>
}
</div>
</div>
);
}
}
So far I'm using an anchor, but I don't find this solution very elegant...
...
<a onClick={this.onReply} href="#form">Reply</a>
...
<a name="form"></a>
{this.state.formVisible &&
...
Another problem of the anchor approach is, when there are more dynamically created elements on the page. Then I have to use some kind of ID for the anchor, which is pretty complex:
const {id} = this.props.data;
...
<a onClick={this.onReply} href={"#form-" + id}>Reply</a>
...
<a name={"form-" + id}></a>
{this.state.formVisible &&
...
Because your form is rendered only when formVisible is true, you need to wait until your component is re-rendered before trying to scroll to it. Use componentDidUpdate combined with scrollIntoView:
componentDidUpdate(prevProps) {
// `formVisible` went from false -> true, scroll the <form> into view
if (!prevProps.formVisible && this.props.formVisible) {
this.formRef.current.scrollIntoView();
}
}

How to add scroll event on a React component instead of adding on window object?

I used ReactJS to create a resume, that resume's body have 100vw and 100vh dimension ie, there is no scroll at window/body level, but inside resume there is one component which contains scroll. At the end I want to create a sticky Header. But before creating sticky header this code should run first. I tried this:
componentDidMount() {
ReactDOM.findDOMNode(this.refs.stickyHeader).addEventListener('scroll', this.isScrollOccured.bind(this));
}
componentWillUnmount() {
ReactDOM.findDOMNode(this.refs.stickyHeader).removeEventListener('scroll', this.isScrollOccured.bind(this));
}
isScrollOccured() {
console.log('Scrolling Occured');
}
render() {
return (
<div className="Container">
<div className="Header" ref="stickyHeader">
[...]
</div>
</div>
)
}
This is not giving any response, but if I change scroll event with wheel event inside addEventListener, it works fine, But I don't want to use wheel. I checked Other's Solution but it didn't work for me. Help me to find out the solution. If necessary I will upload Image.png of my work.
The refs API has changed. Try this instead
stickyHeader = React.createRef()
componentDidMount() {
this.stickyHeader.current.addEventListener(
"scroll",
this.isScrollOccured.bind(this)
);
}
componentWillUnmount() {
this.stickyHeader.current.removeEventListener(
"scroll",
this.isScrollOccured.bind(this)
);
}
isScrollOccured() {
console.log("Scrolling Occured");
}
render() {
return (
<div className="Container">
<div className="Header" ref={this.stickyHeader}>
[...]
</div>
</div>
);
}
You can learn more about refs here.

Error 'ref is not a prop' when using ref on a div inside of a react component

So the main aim of me using refs is so that I can reset the scroll position of a scrollable div, this is an image of the div before adding content this is how it looks before dynamically adding divs to the scrollable container div
This is a screenshot of the div after adding boxes to it:
the box is created outside of viewport and is created at the top of the scrollable area
So to be able to maintain the viewport at the top of the scrollable area I am hoping to use refs to do ReactDOM.findDOMNode(this.songIdWrapper) and then manipulate the scrollTop or use scrollTo methods.
Please find the code snippet below:
import React, {Component} from 'react';
import ReactDOM from 'react-dom';
class AddPlaylist extends Component {
constructor(props){
super(props);
this.state = {
displaySearch: false,
id: '',
playlistName:'',
playlistTitle:'',
songs:[]
}
this.handleIdSubmit = this.handleIdSubmit.bind(this);
this.handleIdChange = this.handleIdChange.bind(this);
this.handleNamechange = this.handleNamechange.bind(this);
this.handleNameSubmit= this.handleNameSubmit.bind(this);
this.callback=this.callback.bind(this);
}
componentWillUpdate () {
console.log(ReactDOM.findDOMNode(this.songIdWrapper));
}
componentDidUpdate () {
}
callback (songId) {
this.songIdWrapper=songId;
}
render () {
return(
<div className='add-playlist-wrapper'>
<div className='form-wrapper container'>
<form onSubmit={this.handleNameSubmit} className='playlist-name-wrapper'>
<input className={this.state.submittedName ? 'hide-input' : ''} required onChange={this.handleNamechange} value={this.state.playlistName} placeholder='Playlist title'/>
{this.state.submittedName ? <p className='title'>{this.state.playlistTitle}</p> : null}
</form>
<form onSubmit={this.handleIdSubmit} className='add-id-wrapper'>
<div className='input-add-playlist'>
<input required onChange={this.handleIdChange} value={this.state.id} placeholder='Add song...'/>
<button type='submit' className='fabutton'>
<i className="add-button fa fa-plus-square-o fa-3x" aria-hidden="true"></i>
</button>
</div>
</form>
<div id='song-id-wrapper' ref={this.callback}>
{this.state.songs.map((song, i) => {
return (<div key={i} className='song'>
<p>{song}</p>
</div>
)
})}
</div>
</div>
</div>
)
}
handleIdSubmit (event) {
event.preventDefault();
const newState = this.state.songs.slice();
newState.push(this.state.id);
this.setState({
songs:newState
})
}
handleIdChange (event) {
this.setState({
id: event.target.value
})
}
handleNamechange (event) {
this.setState({
playlistName: event.target.value
})
}
handleNameSubmit (event) {
event.preventDefault();
this.setState({
playlistTitle: this.state.playlistName
})
}
}
export default AddPlaylist;
The error message I get is:
this is the error message stating that ref is not a prop
So I am quite new to react and as far as I'm aware this is an attribute on a div element not passed as a prop to a component. So I hope you can see my confusion as when I search google/stack-overflow I see a lot of comments relating to child components. I am fully aware string refs have been depreciated and that callbacks should be used but no matter what I try I cannot get rid of this error message.
Any help would be greatly appreciated.
I guess the issue is that you try to access the ref in the componentWillUpdate Hook. Because from the pure setup there is nothing wrong.
The componentWillUpdate Hook gets actually called before the next render cycle, which means you access the ref BEFORE your component gets rendered, which means that you always access the ref from the render cycle before. The ref gets updated with the next render cycle.
https://developmentarc.gitbooks.io/react-indepth/content/life_cycle/update/tapping_into_componentwillupdate.html
I think you should do the scroll position handling AFTER the component did update, not before it will update!

react-dom: Incrementally change opacity of an element on click

I'd like to be able to increase opacity of a DOM element on click of a button.
Wrapping my head around React's concepts, I think this should be done through using state and a functional setState() function.
However, I keep running into an error with the following code:
import React, { Component } from 'react'
class ThoughtfulThoughts extends Component {
state = {
opacity: 0.4,
thoughts:
[
"live and let leave",
"just pee yourself",
"who knows what 'laf' is?"
]
}
makeAppear = () => {
// TODO: this causes a "RangeError: Maximum call stack size exceeded"
this.setState(prevState => ({
opacity: prevState.opacity + 0.2
}))
}
render() {
return (
<div className="thoughtful-thoughts">
<div className="current-thought" style={{opacity: this.state.opacity}}>
{this.state.thoughts.map((thought, i) => (<p>{thought}</p>))}
</div>
<button onClick={this.makeAppear()}>Think for me</button>
</div>
)
}
}
export default ThoughtfulThoughts
I don't want to use jQuery, nor direct DOM manipulation, nor
CSS transitions, but would like to do this just in JS in "the React way".
Any pointers would be greatly appreciated!
You have a couple of minor issues in your code:
On your button component, change onClick={this.makeAppear()} into onClick={this.makeAppear}.
Nicely done on using the function approach instead of the object approach for updating a state variable based on a previous state variable. However, you have a minor syntax. Either do:
this.setState(prevState => (
{opacity: prevState.opacity + 0.2}
));
or
this.setState(prevState => {
return {opacity: prevState.opacity + 0.2}
});
whichever you prefer.
Add a key property to each item you return from your map(): So basically:
{this.state.thoughts.map((thought, i) => (<p key={i}>{thought}</p>))}
You can probably use i safely as the key here because the order of the items in thoughts array will remain static. For more info about keys and how to use them properly, take a look here.
Demo:
class ThoughtfulThoughts extends React.Component {
constructor() {
super();
this.state = {
opacity: 0.4,
thoughts:
[
"live and let leave",
"just pee yourself",
"who knows what 'laf' is?"
]
}
}
makeAppear = () => {
this.setState(
prevState => ({opacity: prevState.opacity + 0.2})
);
}
render() {
return (
<div className="thoughtful-thoughts">
<div className="current-thought" style={{opacity: this.state.opacity}}>
{this.state.thoughts.map((thought, i) => <p key={i}>{thought}</p>)}
</div>
<button onClick={this.makeAppear}>Think for me</button>
</div>
);
}
}
ReactDOM.render(<ThoughtfulThoughts />, document.getElementById("app"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="app"></div>

Resources