Focus on (scroll to) a DIV element in React - reactjs

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();
}
}

Related

How to change the state in conditional - React

I need to change the state of sibling components in my React App.
I use state and setstate
I need to change the state of sibling components. When loading the page, there must exist (visible in the page) <BkUser /> and when clicking "button id =" ds-visual "it must be deleted (<BkUser /> mustn't exist) and there must exist <BkDescanso />.
When you click on <BkSleep /> (in the div parent) you should remove <BkDescanso /> and show <BkUser />
This is the web.
There should never be <BkUser/> and <BkSleep> at the same time. <Bkuser /> is the blue block and <BkDescanso /> is the red block
This is my code:
Edit: I edit my original code because I fix the problem. This is the final OK Code. In the end the most important thing was the state conditional
{
this.state.usuario ? (<BkUser handleClick = {this.handleClick} usuario={this.state.usuario}/>): (<BkDescanso handleClick = {this.handleClick} usuario={this.state.usuario}/>)}
import React, { Component } from 'react';
class Header extends Component {
constructor(props) {
super(props);
this.state = {
usuario: true,
};
this.handleClick = this.handleClick.bind(this);
}
handleClick() {
this.setState(state => ({
usuario: !state.usuario
}));
//alert("Works button");
}
render(){
return (
<header className="header">
<div className="configuracion">
{
this.state.usuario
? (
<BkUser handleClick = {this.handleClick} usuario={this.state.usuario}/>
)
: (
<BkDescanso handleClick = {this.handleClick} usuario={this.state.usuario}/>
)}
<div className="content-btn">
<button id="config" className='btn btn--rounded'><span className="ico-configuracion"></span></button>
<button id="salir" className='btn btn--rounded'><span className="ico-exit"></span></button>
</div>
</div>
</header>
);
}
}
class BkUser extends Component{
render(){
return ((
<div className='usuario'>
<img src="../img//usuario.svg" alt="Imagen usuario"/>
<div className="content-usuario">
<span id="nm-usuario" className="h4">Hermione Jane Granger</span>
<span id="tp-usuario" className="h5">Supervisor</span>
</div>
<div className="content-descansos">
<div className="botones">
<button id="ds-visual" className='btn btn--rounded' onClick={this.props.handleClick}><span className="ico-visual"></span></button>
<button id="ds-admin" className='btn btn--rounded'><span className="ico-tiempo-administrativo"></span></button>
<button id="ds-otros" className='btn btn--rounded'><span className="ico-descanso"></span></button>
</div>
<div className="ds-actual">
<span id="ds-tipo">Administrativo</span>
<span id="ds-tiempo">00:08:47</span>
</div>
</div>
</div>
));
}
}
class BkDescanso extends Component {
render(){
return ((
<div className='usuario descanso' onClick={this.props.handleClick}>
<h3>Finalizar descanso</h3>
</div>
));
}
}
export default Header;
Right now handleClick works but always exist BkUser and BkDescanso. I need only one to exist. If you click on id = "ds-visual" the bkUser block should disappear and BkDescanso appear. Then if you click on div className = 'user rest' in BkUser there should only be BkDescanso.
I think that it is not able to know when it is true and when it is false to show or hide
Thanks a lot for the help.
You're missing two things:
First you have to pass the handleClick function to the BkUser component, and then you have to call it via this.props.handleClick.
...
<BkUser handleClick={this.handleClick} usuario={this.state.usuario} />
....
<button
id="ds-visual"
className="btn btn--rounded"
onClick={this.props.handleClick}
>
ds-visual
<span className="ico-visual" />
</button>
CodeSandbox here.
Read more here.
You can change the state of the siblings by passing a function from the parent via props into them.
In the end your siblings are the children of their parent.
You can read this articles on how to change the state of child components.
React js change child component's state from parent component
https://medium.freecodecamp.org/react-changing-state-of-child-component-from-parent-8ab547436271
An other thing you could look into would be React Redux.

Disable all buttons on click in react

So I have a component, called AddButton
export default class AddButton extends React.Component {
constructor(props) {
super(props);
}
addItem(e) {
this.btn.setAttribute('disabled', 'disabled');
this.props.addItem(e.target.getAttribute('data-row-index'))
}
render() {
return (
<div className="row">
<div className="col-md-12 text-center">
<button ref={btn => {this.btn = btn }} className="btn btn-success" onClick={this.addItem.bind(this)} data-row-index={this.props.rowIndex}>Add</button>
</div>
</div>
)
}
}
Some where else in the code I do:
if (this.props.addButton) {
rows.push(
<td key="add">
<AddButton
addItem={this.props.addItem}
rowIndex={this.props.rowIndex}
/>
</td>
)
}
So I have at one time 50 of these in a table at the end of the row. When one is clicked I wanted to disable all the buttons.
So as you can see I have done, in addItem(e):
addItem(e) {
this.btn.setAttribute('disabled', 'disabled');
this.props.addItem(e.target.getAttribute('data-row-index'))
}
But when I test this, only the button after the one is clicked is disabled. I want them all to be disabled
Any way I could modify this to achieve that?
React components have syntax like HTML DOM but they are not, they are modules and every time you use that module for example in your table it would be a new instance of that module. So if you want to share a state between them you have to pass it as props to them and you shouldn't treat them as HTML nodes.

Render Component Only Once in a Div Below an LI When In-Line Button is Clicked

Currently, this will render a component below each of the list items when the img is clicked by keeping an array of shown components per index in local state. Eg. (state.showItems ==[true,false,false,true]).
I would like to restrict the values in this array to only one 'true' at a time so that the <SuggestStep /> component is rendered only once in the div under the button that was clicked. I'm not using CSS because the list can grow very large and don't want to render and hide a component for each one. Also considered using a radio button displayed as an image, but don't know if that would involve mixing forms with LI's and if that is bad. Feedback on the question of restricting the showItems array items to only one true at a time, and general patterns to approaching the component rendering problem I'm describing are welcome.
class CurrentSteps extends Component {
constructor(props) {
super(props)
this.state = {
toggleOnSuggestInput: false,
showItems: []
}
this.clickHandler = this.clickHandler.bind(this)
}
clickHandler(index){
let showItems = this.state.showItems.slice();
showItems[index] = !showItems[index]
this.setState({showItems})
this.setState(prevState => ({
toggleOnSuggestInput: !prevState.toggleOnSuggestInput
}))
}
render() {
let steps = this.props.currentGoalSteps.map((step, index) => {
return (
<div key={`divKey${index}`}>
<li key={index}>{step}</li>
<img key={`imageKey${index}`} onClick={this.clickHandler.bind(this,index)} alt="" src={plus}/>
{this.state.showItems[index] ? <SuggestStep /> : null}
</div>
)
});
return (
<div>
<ul> {steps} </ul>
</div>
)
}
Try making the following modifications to your code...
Change your this.state like so.
this.state = {
toggleOnSuggestInput: false,
activeIndex: null
};
Change your clickHandler to this.
clickHandler(event, index) {
this.setState({ activeIndex: index })
}
Change your map to like the one below. Notice the onClick prop change.
let steps = this.props.currentGoalSteps.map((step, index) => {
return (
<div key={`divKey${index}`}>
<li key={index}>
{step}
</li>
<img
key={`imageKey${index}`}
onClick={e => this.clickHandler(e, index)}
alt=""
src={plus}
/>
{this.state.activeIndex === index ? <SuggestStep /> : null}
</div>
);
});

Reactjs: Callback for dangerouslySetInnerHTML complete

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.

User react router inside of map

I would like to use react-router (must use 2.8.1) for rendering content inside a list (using map).
However, if I display {this.props.children} outside the .map, it renders one at time.
I need it to display inline/under the list entry.
How can I achieve this?
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
movies: x.movies
};
}
render() {
return (
<div>
<h2>Repos</h2>
{x.movies.map(movie =>
<span key={movie.id}>
{movie.name}
<NavLink to={"/repos/" + movie.id + "/next"}>React Router</NavLink>
</span>
)}
{this.props.children}
</div>
);
}
}
You are rendering the children inside the loop, which i believe is causing the extra Next Id:4 to be displayed between each entry.
Try the below, by rendering the children outside the loop.
{
x.movies.map(movie => (
<span>
<Result key={movie.id} result= {movie}/>
</span>
))
}
<span>{this.props.children}</span>

Resources