scrollIntoView using React refs - javascript

We are trying to scroll to a specific component when the user closes another component.
Our example is very similar to that down below, taken from https://reactjs.org/docs/refs-and-the-dom.html#exposing-dom-refs-to-parent-components
function CustomComponents(props) {
const items = [1,2,3,4].map((x, i) => return (
<div ref={props.inputRef} key={i}>
x + hello
</div>
)
return (
<div>
{ items }
</div>
);
}
function Parent(props) {
return (
<div>
<CustomComponents inputRef={props.inputRef} />
</div>
);
}
class Grandparent extends React.Component {
render() {
return (
<Parent
inputRef={el => this.inputElement = el}
/>
);
}
}
We are rendering a big list of cards and want to be able to scroll to a specific card by calling this.refs[elem].scrollIntoView()
But our problem is that calling this.refs returns an empty object at all levels, and so we are unable to attach to a specific element and then fire it when the user arrives back to view this component.
Would like some help on how one would go about solving this issue.

Where are you trying to call this.refs[elem].scrollIntoView()? You should be working with refs inside componentDidMount or componentDidUpdate, not inside the render method.

I've solved my specific issue by providing an if statement written below in my Grandparent component.
inputRef={el => {
if (el && el.id == this.state.selectedBuildingRef) {
this.myelement.scrollIntoView();
window.scrollBy(0, -65);
}
}}

Related

What's the correct pattern to change parent state inside a child state in React?

Imagine I have a page "Parent" which conditionally renders a div "Child".
On the click of a button, "Child" opens. To close "Child" one has to click in a X button inside it.
This is how I would do it and in my opinion it looks clean.
const Parent = (props) => {
const [childVisible, setChildVisible] = useState(false);
return (
<>
{childVisible && <Child close={setChildVisible.bind(false)} />}
<button onClick={setChildVisible.bind(true)}>
Open Child
</button>
</>
)
}
const Child = (props) => {
return (
<div>
<p>Im Child</p>
<button onClick={props.close()}> X </button>
</div>
)
}
Since react v16.13.0 react has introduced a warning Warning: Cannot update a component from inside the function body of a different component. and it seems I can't do this anymore.
What's the correct pattern now? I would rather not have a state in both components stating the same thing.
Call back was not properly added .You could do like this onClick={props.close}
While use onClick={props.close()} like this. close() function run on child mount instead of click event
const Child = (props) => {
return (
<div>
<p>Im Child</p>
<button onClick={props.close}> X </button>
</div>
)
}

How to return an HTML <div> tag from a javascript function in React?

I am working on a React application where I am trying to render text on the screen when a button is clicked. I have defined a function onButtonClick which gets triggered whenever the button is clicked. However, the HTML that I am returning from the function is not rendered on the screen. I am in the learning stages of React so please excuse me if the question seems silly.
class App extends Component {
constructor() {
super();
this.state = {
blockno:0
}
}
OnButtonClick = () => {
this.setState({blockno: this.state.blockno + 1})
return(
<div>
<h3>Some text</h3>
</div>
);
}
render() {
return(
<div>
<Button onButtonClick={this.OnButtonClick}/>
</div>
);
}
}
The value is being returned, but the framework/browser/etc. has no reason to do anything with that value.
Try thinking about this a different way, a "more React way". You don't want to return the value to be rendered, you want to update state. Something like this:
constructor() {
super();
this.state = {
blockno:0,
showDiv: false // <-- note the new property in state
}
}
OnButtonClick = () => {
this.setState({blockno: this.state.blockno + 1, showDiv: true})
}
Now you're not returning anything, but rather updating the state of the component. Then in your render method you conditionally render the UI based on the current state:
render() {
return(
<div>
<Button onButtonClick={this.OnButtonClick}/>
{
this.state.showDiv
?
<div>
<h3>Some text</h3>
</div>
: ''
}
</div>
);
}
The click handler doesn't modify the page, it just modifies the state of the component you're writing. The render method is responsible for rendering the UI based on that state. Any time state changes, render will be called again to re-render the output.
(Note: It's not 100% clear if this is exactly the functionality you're looking for in the UI, since it's not really clear what you're trying to build. But the point here is to illustrate how to update state and render output in React. Your logic can be tweaked as needed from there.)
You have to make a render based on your state. Please check the tutorial at the react docs to learn more about how React works. It's really good
Here is a version of your code that works. Hope it helps
class App extends Component {
constructor() {
super();
this.state = {
blockno: 0
};
}
OnButtonClick = () => {
//updates the states
this.setState({ blockno: this.state.blockno + 1 });
};
//remember: every time there is an update to the state the render functions re-runs
render() {
//variable holding the blocks in an array
let blocks = []
//if blockno is greater than 0, it checks everytime that there is a state change
if (this.state.blockno > 0) {
//for every block added
for (let index = 0; index < this.state.blockno; index++) {
//We`re going to add to the array of blocks a new div with the block number
blocks.push(
<div>
<h3>My block number is {index}</h3>
</div>
);
}
}
return (
<div>
<div>
{/**button that updates the state on every click */}
<button onClick={this.OnButtonClick}>
Click me to add a new div!
</button>
</div>
{/**This render the blocks variable that holds the divs */}
{blocks}
</div>
);
}
}
What I see is that you are trying to build a counter. The value that you're returning from the click handler function can't be rendered, instead you need to manage it in the render function as follow:
class App extends Component {
constructor() {
super();
this.state = {
blockno: 0
}
}
OnButtonClick = () => {
this.setState(prevState => ({ blockno: prevState.blockno + 1 }));
}
render() {
return(
<div>
{this.state.blockno > 0 && <div>some text {this.state.blockno}</div>}
<Button onButtonClick={this.OnButtonClick} />
</div>
);
}
}
Also note that the setState method is asynchronous, please read the documentation https://reactjs.org/docs/react-component.html#setstate

How to select the clickable parent in my React component even if I click on its children or anywhere else inside the parent

I have started an application which I want to work same as weather.com next 36 hours section. The idea is when you click on each weatherCard which has a seperate component in my app you will update the below section which is my weatherDetails component based on the selected weatherCard /weather box. So I made the entire component clickable by giving it the click event via props from my stateful component which is my weatherLocation component. This is my WeatherCard component:
const WeatherCard = (props) => (
<div id={props.date} className="weatherCard" onClick={props.clicked}>
<h2 className="cardDate">{props.date}</h2>
<h4>{props.forcast}</h4>
<div className="minmaxDeg">
<data>{props.min}</data>
<data>{props.max}</data>
</div>
<data>{props.rain}</data>
</div>
);
And here in render method in WeatherLocation component I loop through data coming from state and give props the WeatherCard component:
const WeatherCards = this.state.reports.map( report => {
return(
<WeatherCard
key={report.id}
{...report}
clicked={() => this.handleCardClick(event)}
/>
);
});
And this is the handleCardClick that I added for it just for testing:
handleCardClick = event => {
// const { reports , selectedCardInfo , activeCard } = this.state;
const selectedDate = document.getElementById(event.target.id);
console.log(event.target.id);
}
I don't want to use anchor tag as I don't need href. The click works fine by itself. But because I need to get the id of the parent which is the div with the class of weatherCard. At the moment when I click on other elements inside the card I cannot get the id because they are not the parent. The reason I need its id is when I get data with from the API I need a unique value for each card so that when you click on the card the data for that card will be shown in the other component which is the WeatherDetails component. But for now I need to be able to somehow choose that selected card and pull out the state for that unique card. Could someone help me out? Thanks.
You just need to pass the Parent component ID to your onClick function in Weather Card.
Here is your WeatherCard - Component
const WeatherCard = (props) => (
<div id={props.date} className="weatherCard" onClick={event => props.clicked(event, props.id)}>
<h2 className="cardDate">{props.date}</h2>
<h4>{props.forcast}</h4>
<div className="minmaxDeg">
<data>{props.min}</data>
<data>{props.max}</data>
</div>
<data>{props.rain}</data>
</div>
);
You can see that I have added props.id to your onClick function and with help of event now you can access that id from the parent component.
Now here is your Parent Component- WeatherCards
const WeatherCards = this.state.reports.map( (report, i) => {
return(
<WeatherCard
key={report.id}
id={i}
{...report}
clicked={this.handleCardClick}
/>
);
});
You can see in the code I am passing index number as id to your child component.
So this will give you an id (for now it's an index number) of the card in your onClick handler.
and Finally, here is your on click handler.
handleCardClick = (event, weatherCardID) => {
console.log(weatherCardID)
}
As of now, I am using the index as id if you want to use a unique identifier, you can change that easily.
General JavaScript solution is to differentiate the elements and .stopPropogation after you've captured the event you are targeting. A nested unordered list, <ul>would be an example. Tag the containing <li> with an .opened class upon rendering/displaying each level of nesting, tag those <li> elements accordingly, e.g. a dataset attribute such as data-make, then data-model, then data-option. You then attach and fire event listeners on the different level <li>'s.
Thank you #RutulPatel. I made your answer as the answer. But I changed your code a bit as I got your point so I wrote an answer as it is long. I think we might not need to change the WeatherCard at all and I don't pass event or any logic there. so it will be intact:
const WeatherCard = (props) => (
<div id={props.date} className="weatherCard" onClick={event => props.clicked(event, props.id)}>
<h2 className="cardDate">{props.date}</h2>
<h4>{props.forcast}</h4>
<div className="minmaxDeg">
<data>{props.min}</data>
<data>{props.max}</data>
</div>
<data>{props.rain}</data>
</div>
);
But I use your tip changing my weatherCards array to look like this:
const weatherCards = this.state.reports.map( report => {
return(
<WeatherCard
key={report.id}
id={report.date}
{...report}
clicked={() => this.handleCardClick(event, report.date)}
/>
);
});
So I use the report.date which is a unique value as my id. Also I don't pass event as a parameter to the arrow function I just pass it with the report.date to the handler:
clicked={() => this.handleCardClick(event, report.date)}
And the handler will be the same as you did:
handleCardClick = (event, weatherCardID) => {
console.log(weatherCardID)
}
I might even remove event later on from both if there was no need fo that.
Thank you again.

Array.map item to a state

How do I get the values of array.map outside of it?
For example:
array.map(index, item) => { return ( <Text>{item.ID}</Text> ) }
Ouside of it I have something like:
<Button onPress={() => this.editItem(CURRENT_ITEM_ID)}></Button>
Where CURRENT_ITEM_ID is the current item on a Card, for example.
Like Tinder I have an array of Cards to Swipe, that I'm mapping, but the Like button is not part of the Card. This way I'm not moving the Like button with the Card, instead it is static, I'm just moving the content of the card. So I need a way to access the current item from outside of the map. The Card has Text and ID. The component below have a Button that I need to click passing the item.ID of the current Card.
Is there a way to set this item to a state?
Thank you.
One solution is to create a state property that holds the id of the card that is showing, and then when the button is clicked, you grab that state and do something with it. Here is an example with onClicks and divs.
const arr = ['card1', 'card2', 'card3', 'card4'];
class App extends React.Component {
constructor(props) {
super(props)
this.handleClick = this.handleClick.bind(this);
this.handleButtonClick = this.handleButtonClick.bind(this);
}
handleClick(index) {
this.setState({ visibleCard: index });
}
handleButtonClick() {
console.log('this.state.visibleCard', this.state.visibleCard);
console.log('visibleCard', arr[this.state.visibleCard]);
}
render() {
return (
<div>
{arr.map((card, i) => <div onClick={() => this.handleClick(i)}>{card}</div>)}
<button onClick={this.handleButtonClick}>test</button>
</div>
)
}
}
The basic idea is that you tie the index to the card. A handler then sets that state to visible(not sure what this would be in your case since it seems like a mobile app). Then in your button, you use the state to grab the visible card and pass the data to whatever other function you need.
What I normally do is bind the event in the map function.
array.map( (item, key) => {
return (
<div>
<Text>{item.ID}</Text>
<button onClick={this.editItem.bind(this, item.ID)}>Edit</button>
</div>
)
}
Not sure what your HTML looks like, but you need to bind the function params in the map.

Is my owner react components state preventing me from updating child component state?

I'm trying to update the state of a checkbox within a modal that is mounted via button on the UI. I'm loading the settings when AppWrapper mounts so I can pass them around as needed. Right now i'm just passing the settings as props to SettingsList component, which then renders a series of child nodes as checkboxes. I'm able to click the checkboxes when the modal is open, and the settings successfully save to the database. However when the modal is closed and reopened the settings are refreshed to the initially set state from the owner. Refreshing the page though shows the accurately checked boxes. That makes sense to me, but i'm unsure they best way to resolve it.
Should I/Can I update the state of the parent from the child setting so when the modal is reopened that passed props reflect the user changes?
My react structure looks like this:
<AppWrapper>
getInitialState {settings:[]}
<Modal>
<SettingList settings={this.state.settings}>
<Setting/>
<SettingList/>
<Modal/>
<AppWrapper/>
It's not direct one to one code, bust just a representation of the hierarchy.
My Modal component looks like this:
var Modal = React.createClass({
render: function() {
if(this.props.isOpen){
return (
<ReactCSSTransitionGroup transitionName={this.props.transitionName} transitionEnterTimeout={500} transitionLeaveTimeout={500}>
<div className="mymodal">
{this.props.children}
</div>
</ReactCSSTransitionGroup>
);
} else {
return <ReactCSSTransitionGroup transitionName={this.props.transitionName} transitionName={this.props.transitionName} transitionEnterTimeout={500} transitionLeaveTimeout={500} />;
}
}
});
My SettingList component looks like this:
var SettingsList = React.createClass({
render: function() {
var settingNodes = this.props.settings.map(function(setting, i){
return (
<Setting data={setting}
key={i}>
</Setting>
)
}.bind(this));
return (
<div className="settings-block">
<h2>Notifications</h2>
<ul className="account-settings">
{settingNodes}
</ul>
</div>
)
}
});
And the Setting component looks like this:
var Setting = React.createClass({
saveSetting: function(one) {
core.setAccountSettings(this.refs.setting_checkbox.id, this.refs.setting_checkbox.checked).done(function(response){
this.setState({
defaultChecked: this.refs.setting_checkbox.checked
};
console.log(response)
}.bind(this));
},
render: function() {
//get values from settings object
for (var k in this.props.data) {
this.settingId = k
this.settingName = String(k.split(/_(.+)?/)[1]).replace(/_/g, " ");
this.settingValue = (this.props.data[k].toLowerCase() == "true")
}
return (
<li className="checkbox">
<input onChange={this.saveSetting} ref="setting_checkbox" id={this.settingId} className="settings_checkbox" type="checkbox" defaultChecked={this.settingValue}></input>
<label htmlFor={this.settingName}>{this.settingName}</label>
</li>
)
}
});
As pointed out in the comments above there is a number of ways to pass data between components.
http://andrewhfarmer.com/component-communication/
Following the article regarding callbacks was the solution for me.

Categories