I currently have multiple components within a bootstrap modal. My goal is to be able to do a window.scroll to a given component following a user action. Basically, this is what I have tried:
class App extends Component {
constructor (props) {
super(props);
this.myref = React.createRef();
}
// function that I have been trying to invoke.
scrollToRef = () => {
window.scrollTo({top: this.myref.current.offsetTop, behavior: "smooth"})
}
render () {
return (
<Modal>
<ComponentOne/>
<ComponentTwo/>
<ComponentThree ref={this.myref}/>
</Modal>
)
}
}
All of my components are class components. I even tried wrapping ComponentThree in a div tag if that made a difference, but no luck. Any pointers would be greatly appreciated, Thank you!
window.scrollTo would pertain to the window object, therefore will attempt to scroll the window, not the <Modal> component. For this, you can have another ref attached to the <Modal>, and that is the element you would use scrollTo on. Since <ComponentThree> is already a direct child of <Modal>, you can continue using the offsetTop property but take note:
offsetTop is the number of pixels from the top of the closest
relatively positioned parent element
Related
I am rendering a Dialog component with a lot of text and the dialog has therefore a scroll. Text contains some links, and the content of the Dialog changes when a link is clicked. The problem is, when the content changes, the scroll stays in the same position at the height of the link. I would like to scroll to the top of the dialog when the new content is displayed.
I have created a class component for the Dialog content and implemented the handleTop function that uses refs and scrollIntoView to scroll to the top of the Dialog. When this function is called from a Button click event (TestButton), then the content is scrolled to the top. However when this function is called from the componentDidMount(), nothing happens.
export default class DialogContent extends React.Component {
constructor(props) {
...
this.myRef=React.createRef();
}
handleTop = () => {
this.myRef.current.scrollIntoView({
behavior: 'smooth',
block: 'start'
});
};
componentDidMount() {
this.handleTop();
}
render() {
return (
<div>
<h1 ref={this.myRef}>Title</h1>
<p>A lot of text</p>
<Button>TestButton</Button>
<div>
)
}
I also verified that the componentDidMount function gets called by placing a console.log message.
Any ideas or suggestions would be kindly appreciated.
In my sidebar I have a list of radio buttons that should each select a component located in the main area of my app. I would like to set it so the component scrolls into view when I click the radio button that selects it, and as far as I can figure, I need to reference the component in order to scroll it into view, but I'm having trouble with this because I can't figure out how to properly pass a reference from child to parent component.
The parent component is a class component and in its constructor I set createRef() like this:
constructor(props) {
super(props);
this.myRef = React.createRef();
}
I then created a function that should be passed to child component and return ref:
getElem(ref) {
this.myRef = ref;
}
Now, inside the render of the parent component I pass the function to the child component like this:
<Designer
usedComponents={this.state.usedComponents}
myRef={this.getElem}
/>
Inside the Designer component I map the main area components and pass the ref to them like this:
const components = props.usedComponents.map((component, index) => {
return (
<div onMouseUp={() => props.selectComponent(index)} key={index}>
<DesignerComponent {...component} myRef={props.myRef} />
</div>
);
});
return components;
DesignerComponent component is determined through switch statement because each has a different set of props, but inside the switch statement I also pass myRef={props.myRef} to each individual component and then I set the ref inside each individual component like this:
<section ref={props.myRef}></section>
However, this breaks my app and gives me this error message:
"TypeError: Cannot set property 'myRef' of undefined".
What am I doing wrong?
I figured it out. The function getElem(ref) doesn't seem to do anything. I simply passed myRef={this.myRef} to the Designer component and now it works.
As an example (real tried code)
I have a component of which I want to initiate a NEW instance for rendering.
import React, { Component } from 'react';
export default class TinyObject extends Component {
constructor(props) {
super(props);
console.log("TinyObject constructor");
}
render() {
console.log("TinyObject render");
return (
<div>HEY THIS IS MY TINY OBJECT</div>
);
}
}
Then in main App constructor I do the following:
var myTinyObject = new TinyObject();
var myArray = [];
myArray.push(myTinyObject);
this.state = {testing: myArray};
Then a created a function to render this:
renderTest()
{
const {testing} = this.state;
const result = testing.map((test, i) => {
console.log(test);
return {test};
});
}
And I call this from the App render function like this:
render() {
const { gametables, tableActive } = this.state;
console.log("render");
return <div><div>{this.renderTest()}</div></div>;
}
It runs, no errors.
I see console log of the following:
console.log("TinyObject constructor");
console.log(test);
But I don't see console log of the TinyObject render nor do I see the render output.
Thanks to lustoykov answer I got a little further
JSX: var myTinyObject = <TinyObject />;
works!
but in the real app I add a little more and don't know how to do it here.
return <GameTable key={'gt'+index} data={table} settings={this.settingsData} sendTableNetworkMessage={this.sendTableNetworkMessage} />
this is the way I was rendering; and I needed more instances of GameTable
now the question is; how do I add the arguments like data & settings to myTinyObject.
thanks for helping so far.
You don't manually instantiate react component, use JSX or createElement. For instance
via JSX
var myTinyObject = <TinyObject prop1={prop1} prop2={prop2} />;
via React.createElement
var myTinyObject = React.createElement(TinyObject, { prop1, prop2 }, null);
I would definitely check out some tutorials and how React works at a basic level. You aren't really going to call your react components like you would normally do in javascript since the render function returns jsx.
Fundamentally, React is what is called a single page application. That means that your browser will load up a single html file with a div. Now that div will be where React performs its magic by using Javascript to change stuff around.
It is easiest for me to think of React as a tree. You create these components that you place on the DOM or in your HTML and React will add and remove them downwards. For instance, take a look at this picture of twitter.
So first the Feed component is going to be put on the DOM. Then the Feed component will render the Tweet components. So as you can see the rendering goes in one direction, downwards.
Now, as you can see your render methods are not returning javascript. It is returning something that looks like HTML but we call it JSX. That means we want to render it a little differently with our react classes.
If we have a child component:
class Child extends React.Component {
render() {
return <h1>Hello, I am inside the parent component</h1>;
}
}
We can call the render method like this:
class Parent extends React.Component {
render() {
<Child /> //This is how I use the Child class
}
}
Now the reason why react is so performant is that the child cannot be re-rendered unless we do 1 of two things:
It is a component with a state and we call a method setState()
We pass down new props to a child component from the parent component
You can read about it here
Now the only way to get React to call that render function again is by doing those two things.
I'm a bit new to React, so forgive me if this is a bit of a newb question.
I have a base component (Page) which uses state to control whether or not a modal popup is displayed:
constructor(props) {
super(props);
this.state = {
showModal : false,
modalContent : 'Initial Modal Content'
};
this.showModal = this.showModal.bind(this);
this.hideModal = this.hideModal.bind(this);
}
showModal(modalContent) {
this.setState({
showModal : true,
modalContent : modalContent
});
}
hideModal(e) {
this.setState({showModal : false});
}
My problem is that I want a grandchild component to be able to open up my modal.
I know I can do this by passing the state to the child component and then to the grandchild component:
<PartnersTable showModal={this.showModal} partners={PARTNERS} />
That just seems a bit sloppy to me, but maybe that's just the React way.
Can someone let me know if I'm doing this properly or if there's a cleaner way to do this?
You can view my full app on GitHub: https://github.com/CCChapel/Thy-Kingdom-Come/blob/master/react.js
Thanks!
-Eric
You're doing it correctly.
In React the only way for a parent to pass props/state to it's children is by passing it downwards. React is unidirectional from top to bottom; from parent to child only.
So your assumption is correct. It can get pretty sloppy. You must know that React is for presenting the UI and simple cases of state management. When you're application gets more complex and you need to pass down state in a direct and simplified manner use Redux, MobX or any other state containers out there.
If you don't like the complexity of passing down props down the tree considering using Redux (I use Redux myself).
Consider the following resources:
https://egghead.io/courses/getting-started-with-redux
http://redux.js.org/docs/introduction/
https://github.com/reactjs/redux/tree/master/examples
To learn how React works though get used to using only React first then use a state container when the application gets more complex.
In order to achieve that you need to pass your showModal method as a prop to child component(s) in which you want to trigger the visibility of the modal. This is how the render method of the parent component should be:
render() {
return(
<ChildComponent showModal={this.showModal}>
)
}
Then in you child component:
class ChildComponent extends Component {
render() {
return(
<input type="button" onClick={this.props.showModal} value="show modal"/>
)
}
}
I have a main component App containing some children according to the routes (I use react-router) etc :
class App extends Component {
otherClick = () => { /* run every children's `handleButton2` function */ }
<div className="App">
<Button handleMenuClick={this.toggleSideBar}>Button 1</Button>
<Button handleOtherClick={this.otherClick}>Button 2</Button>
<SideBar ref="sideBar" title="Toto"/>
{this.props.children}
</div>
}
So, according to the route, App will contain some other containers such as:
class ContainerABC extends React.Component {
constructor(props) {
super(props);
}
handleButton2 = () => {
let sc = this.refs.subCont;
sc.setState({visible : !sc.visible});
// Change the color of Button 2 ???
};
render() {
return (
<div>
<SubContainer ref="subCont"/>
</div>
);
}
};
The role of Button 2 depends on the current Container. In the example above, when I have a ContainerABC as child, I want that Button 2 toggles the SubContainer of ContainerABC.
How can I tell to Button 2 to do the appropriate action according to the child of the component ?
And/or how can I modify Button 2 (or any trigger) from SubCont when Button 2 triggers an action on SubCont ?
Maybe using Redux ? I don't see how it could be helpful
Redux might help only because it can trigger an action that, in return, modifies the global state tree (e.g. redux store through a reducer). If that's the only purpose you need fulfilling, then I'd recommend against adding complexity (as much as I fancy Redux).
I assume you want a random child from {this.props.children} fire a random action once Button 2 is clicked?
Let's observe this commonly enforced React pattern:
Properties flow downwards. Actions (read: callbacks) go upwards.
That said, you may want to iterate through your {this.props.children} and check for the existence of a special callback prop that adheres to your API requirements.
React.Children.forEach(this.props.children, (child) => {
if (typeof child.props.toggleButton2State !== "function") {
throw('Woah, cowboy, you need that toggleButton2State function);
}
}
Then your button could cycle through children in the same manner and execute that function, if exists.
handleButton2Click() {
React.Children.forEach(this.props.children, (child) => {
if (typeof child.props.toggleButton2State === "function") {
child.props.toggleButton2State.call(child, !oldState, this);
}
}
}
So you just called child's callback function in scope of the child with boolean state being toggled and you also passed the reference to the parent component (this).
I would strongly suggest you never manipulate the parent container from a child. You never know how your hierarchy may change.
Obviously, this is a very rough example but it should get you going. Let me know how it goes.
If the behavior of the button depends on what container is being rendered, then it sounds to me like the container should render the buttons. You could wire up some props (could even use cloneElement to put them on the children) so you can pass callbacks down that would change the behavior of the button, but that sounds like a nightmare to maintain.
You could put those buttons in a separate component (with a prop to determine what they do) and render it in the containers. That sounds much simpler to me.