react scroll to component - reactjs

I have 3 components which is my site. Each component js-file is loaded and all 3 shows on one page like this:
Topmenu
SectionOne
SectionTwo
In the Topmenu component I have a menu only. I’ve tried to setup the scrollToComponent onClick at a menu field (SectionOne). But I cannot figure out how to get it to scroll to SectionOne when clicked?
I know this.sectionOne is a ref to an internal ref, right? But how to direct it to a ref inside the “sectionOne.js” file?
I have the following inside my TopMenu.js file
onClick={() => scrollToComponent(this.sectionOne , { offset: 0, align: 'top', duration: 1500})}

To forward the ref to somewhere inside the component, you can use the logic of forwardRef.
This means that we create the ref in the parent component and pass the same to the component which passes it down to DOM element inside it.
class App extends React.Component {
constructor(props) {
super(props);
this.section1 = React.createRef();
this.section2 = React.createRef();
this.scrollToContent = this.scrollToContent.bind(this);
}
scrollToContent(content) {
switch(content) {
case 1:
this.section1.current.scrollIntoView({behavior: 'smooth'});
break;
case 2:
this.section2.current.scrollIntoView({behavior: 'smooth'});
}
}
render() {
return (
<main>
<Menu goTo={this.scrollToContent} />
<div className='main'>
<Section1 ref={this.section1} />
<Section2 ref={this.section2} />
</div>
</main>
);
}
}
and then, inside Section1,
const Section1 = React.forwardRef((props, ref)=>{
return (
<section ref={ref} className='section'>
<p>Section 1</p>
</section>
);
});
You can see a working sample here
I'm not using scroll-to-component package, but the same thing applies.
Forward Refs are supported only in React 16.3 now, you can use this alternative approach if you need the feature in lower versions.

Related

How to get rendered JSX in React?

Imagine a Container component that renders a div with the specified height, e.g.:
<Container height="80">
Hello World
</Container>
and MyHeader component that renders a Container with a certain height, e.g.:
function MyHeader() {
return (
<Container height="100">
Header content goes here
</Container>
);
}
Now, I'd like to implement a Fixed component that looks like this:
<Fixed>
<Fixed.Item>
<MyHeader />
</Fixed.Item>
<Fixed.Content>
Some content goes here
</Fixed.Content>
</Fixed>
When rendering Fixed.Content I'd like to automatically set its offset to 100px (since MyHeader is 100px high).
Is there a way for the Fixed component to get MyHeader's height so it could pass it to Fixed.Content?
Is there a better way to automate this?
Note: Using useEffect (or componentDidMount) is not an option because I'd like it to work in server rendered environments.
Generally you want data flowing down from parents to children. If that is not an option for you, you can use Contexts: https://reactjs.org/docs/context.html. Especially check out https://reactjs.org/docs/context.html#updating-context-from-a-nested-component. In your case, MyHeader could be a Consumer of your context and update it with its height, and Fixed.Content would also be a consumer that uses the value for its offset. But in general, I'd say what you're trying to do is a bit unnatural.
You can use refs for that.
To solve your specific problem, first turn your <Container> component into a class component to be able to set a ref to it.
Then use React.forwardRef to forward the ref from the MyHeader component to the <Container> component:
const MyHeader = React.forwardRef((props, ref) => {
return (
<Container ref={ref} height={100}>
Header content goes here
</Container>
);
});
Finally, create a ref hook in your component that renders the <Fixed> component and pass the ref to the <MyHeader> component. You can then also use the ref to set the height of the <Fixed.Content> component (or whatever you want to set), as follows:
function App () {
const headerRef = React.useRef(null)
return (
<Fixed>
<Fixed.Item>
<MyHeader ref={headerRef} />
</Fixed.Item>
<Fixed.Content height={headerRef.current && headerRef.current.props.height}>
Some content goes here
</Fixed.Content>
</Fixed>
)
}
This seems to only render the <App> component once, so it should also work for server-side rendering. See the following code snippet as an example: https://codesandbox.io/s/get-height-from-header-gvvlo
You can use React Test Renderer which renders React Node to an inspectable Object tree.
import TestRenderer from 'react-test-renderer';
const height = TestRenderer.create(<MyHeader />).toTree().rendered.props.height
This way, you can get its height without second render.
function Container(props) {
return (
<div style={{height:props.height}}>
your content
</div>
);
}
you need to pass height to Container as props which can varied dynamically.
function MyHeader() {
return (
<Container height="100px">
Header content goes here
</Container>
);
}
for fixed also you need to pass it as props by having the height in a variable,e.g.:
function TopBar() {
let height = '100px'
return (
<>
<MyHeader height={height} />
<FixedComponent height={height} />
</>
);
}
I think you need to pass height prop from MyHeader to Fixed. And then Fixed to Fixed.Content. For example you can do similar to the following:
MyHeader.jsx
class MyHeader extends React.Component {
constructor(props) {
super(props)
this.state = {
height: 100
}
this.props.setHeaderOffset(this.state.height)
}
render() {
return (
<Container height={this.state.height}>
</Container>
)
}
}
Fixed.jsx
class Fixed extends React.Component {
constructor(props) {
super(props)
this.state = {
headerOffset: 0
}
this.setHeaderOffset = this.setHeaderOffset.bind(this)
}
setHeaderOffset(headerOffset) {
this.setState({
headerOffset
})
}
render() {
return (
<>
<Item>
<MyHeader setHeaderOffset={this.setHeaderOffset} />
</Item>
<Content headerOffset={this.state.headerOffset}>
Some content goes here
</Content>
</>
)
}
}

Creating a parent 'workspace' component in ReactJS

Using ReactJS, I am trying to create a common workspace component that will have toolbar buttons and a navigation menu. The idea I have is to re-use this component to wrap all other dynamic components that I render in the app.
Currently, I've created a Toolbar and MenuBar components that I then add to each component in the app as such:
<Toolbar/>
<MenuBar/>
<Vendors/>
This does not feel right, since my aim is to have just one component which would be something like:
<Workspace>
<Vendor/>
</Workspace>
However, I am not sure of how to achieve this and whether this is the right approach.
As to whether or not it is the right approach is subjective, but I can provide insight into one way to make a "wrapper" type component:
// Your workspace wrapper component
class Workspace {
render() {
return (
<div className="workspace">
<div className="workspace__toolbar">
Toolbar goes here
</div>
<div className="workspace__nav">
Navgoes here
</div>
<div className="workspace__content">
{this.props.children}
</div>
</div>
)
}
}
// Using the component to define another one
class MyComponent {
render() {
return (
<Workspace>
This is my workspace content.
</Workspace>
)
}
}
You can also look at HOC's or Higher Order Components to wrap things.
React offer two traditional ways to make your component re useable
1- High-order Components
you can separate the logic in withWorkspace and then give it a component to apply that logic into it.
function withWorkSpace(WrappedComponent, selectData) {
// ...and returns another component...
return class extends React.Component {
render() {
// ... and renders the wrapped component with the fresh data!
// Notice that we pass through any additional props
return <WrappedComponent data={this.state.data} {...this.props} />;
}
};
}
const Component = () => {
const Content = withWorkSpace(<SomeOtherComponent />)
return <Content />
}
2- Render Props
or you can use function props then give the parent state as arguments, just in case you need the parent state in child component.
const Workspace = () => {
state = {}
render() {
return (
<div className="workspace">
<div className="workspace__toolbar">
{this.props.renderTollbar(this.state)}
</div>
<div className="workspace__nav">
{this.props.renderNavigation(this.state)}
</div>
<div className="workspace__content">
{this.props.children(this.state)}
</div>
</div>
)
}
}
const Toolbar = (props) => {
return <div>Toolbar</div>
}
const Navigation = (props) => {
return <div>Toolbar</div>
}
class Component = () => {
return (
<Workspace
renderNavigation={(WorkspaceState) => <Navigation WorkspaceState={WorkspaceState} />}
renderTollbar={(WorkspaceState) => <Toolbar {...WorkspaceState} />}
>
{(WorkspaceState) => <SomeComponentForContent />}
</Workspace>
)
}

Persist dom state on route change

I have two components each on separate routes. I would like to know how I can keep the DOM elements in the same state on route change. For example I would like for all the DOM elements to have the same css classes applied as before the route change when navigating back to the same component.
I have tried redux persist and using nested routes with switch but none of these seem to work. From the research I have done it appears that React always mounts and unmount the component on route change and I haven't' been able to find a way to prevent this happening.
I would like for the red background color to remain when going back to test1.
class test1 extends React.Component {
constructor(props) {
super(props);
}
addClassFucn = event => {
$(event.target).parent().css("background-color", "red")
}
renderButton() {
return (
<div>
<div>
<button onClick={this.addClassFucn}>Click me</button>
<Link to="/test2" className="ui button primary back" >
test2
</Link>
</div>
</div>
)
}
render() {
return (
<div>
<div>This is test 1{this.renderButton()}</div>
</div>
);
}
}
export default test1;
class test2 extends React.Component {
constructor(props) {
super(props);
}
renderButton() {
return (
<div>
<Link to="/test1" className="ui button primary back" >
back
</Link>
</div>
)
}
render() {
return (
<div>
<div>This is test 2{this.renderButton()}</div>
</div>
);
}
}
export default test2;
It really depends on the logic of how you maintain the state of your component.
Redux persist should work. Just persist all the state that affects how the DOM currently displayed. Afterwards, inside the component, you should do a check whether there is a persisted state or not. If it is then you shouldn't do any change and just render.
You can use react's shouldComponentUpdate() which by default returns true allowing the component to re render. If useful than you can add the logic to return false which won't all the component to re-render. This is not recognized as best practice though you can refer this link for more details.

Reactjs: can I safely access a sibling component using state?

I'm using ReactJS since just a week or two and I'm now trying to build an App using it.
I think I understood how I should make a Child Component communicates with its Parent Component passing a function as a prop.
But now I'd like to do something different and make 2 sibling Components communicate with each other.
I know I could achieve this using their common Parent Component, but I'd really love to declare some methods on one of those sibling Components and reuse them all over the App.
So here is my idea and my question: can I safely set the state of a Parent Component putting there the "this" from Child Component and then use this variable on other Components?
I already wrote this code and it's working, but I don't understand if this is a good approach or a bad one.
Here some parts of my code to let you see what I'm doing.
Parent Component:
class App extends Component{
state = {}
render(){
return <Router>
<div id="page">
<Header app={this} />
<div id="main" class="row">
<Sidebar app={this} />
<Content app={this} />
</div>
<Footer app={this} />
</div>
</Router>
}
}
Sidebar:
class Sidebar extends Component{
state = {menu: []}
componentDidMount() {
this.props.app.setState({sidebar: this})
}
populateSidebar = (sidebar) => {
this.setState({menu: sidebar})
}
render(){
if (this.state.menu.length == 0){
return null;
}
return (
<sidebar class="col-3">
<ul>
{this.state.menu.map(item => <li><Link to={item.url}>{item.text}</Link></li>)}
</ul>
</sidebar>
)
}
}
User Component (it's a Child of the Content Component. The Content Component just does some routing based on the url):
class User extends React.Component {
async componentDidMount() {
await this.props.app.state.sidebar
this.props.app.state.sidebar.populateSidebar(
[
{
url: "/user/add",
text: "Add new user"
},
{
url: "/user/list",
text: "Users list"
}
]
)
}
async componentWillUnmount() {
await this.props.app.state.sidebar
this.props.app.state.sidebar.populateSidebar([])
}
render() {
return (
<div>
<UserAdd />
<UserList />
</div>
);
}
}
I know that what I'm accomplishing here is so basic that I could totally do it in a different way, for example putting the sidebar menu as an array on the Parent Component's state. But let's say that I want a bunch of methods on Sidebar and let all my other components using them without rewriting too much code. Is this a bad idea?
I'd really love to declare some methods on one of those sibling
Components and reuse them all over the App.
A better approach is to create a helper class with some static methods and use it everywhere across your components, this class even doesn't have to be a react component just a regular ES6 class, for example:
class MyHelper {
static doSummation(num1, num2) {
return num1 + num2;
}
static doMultiplication(num1, num2) {
return num1 * num2
}
// ... other helper methods as you want
}
export default MyHelper;
Then in your React components you can import it and use its helper methods:
import React, {Component} from 'react';
import MyHelper from './MyHelper';
class MyComponent extends Component {
render() {
return (
<div>
{MyHelper.doSummation(1, 2)};
</div>
);
}
}
You can even, for better organization, have as many helper classes as you want, for example MathHelper, StringFormattingHelper, etc...

Render a component when another component is clicked

I want to render BlackSpark when RedSpark is clicked, but I'm not sure how to change the state of a component in another component. I know how to set state in the component itself, but how do I affect another component when I click a different component?
class BlackSpark extends React.Component {
render() {
return (
<div className="black"></div>
);
}
}
class RedSpark extends React.Component {
render() {
return (
<div className="red"></div>
);
}
}
class App extends React.Component {
render() {
return (
<div>
<BlackSpark />
<RedSpark />
</div>
);
}
}
In React, there's a concept of component composition as you've already embraced -- it allows you to accomplish what you want by rendering children based on the parent's state, another key concept known as lifting state up. What this means, is if you have mutually dependent components, create a single parent which composes them, and have state in the parent control the presentation and logic of the children. With the parent App, you can keep your state inside App, and based on App's state, conditionally render whatever you want -- either BlackSpark or both. For example, using the logical && operator:
{condition && <Component />}
This will only render <Component> when condition is truthy, or else it will not render anything at all (except for when condition is 0). Applying it to this situation, try adding state to your App component to utilize conditional rendering.
There's another key concept you need to understand: component props. They are essentially inputs to a component, certain properties passed to the component to tell how it should behave -- like attributes on regular HTML elements such as input placeholders, URLs, and event handlers. For example:
<Component foo="bar" bar={3} />
This will pass the props foo and bar down to Component with the values "bar" and 3 respectively and are accessible through this.props. If you were to access this.props.foo inside the Component component it would give you "bar". If you pair this up with composition, you can accomplish what you want:
class Example extends React.Component {
constructor() {
super();
this.state = {
showHello: true
}
this.handleChange = this.handleChange.bind(this);
}
handleChange() {
this.setState(prevState => ({
showHello: !prevState.showHello
}));
}
render() {
return (
<div>
{this.state.showHello && <Child2 />}
This is a test.
<Child1 onClick={this.handleChange} />
</div>
);
}
}
class Child1 extends React.Component {
render() {
return <div onClick={this.props.onClick}>Click me!</div>
}
}
class Child2 extends React.Component {
render() {
return <div>Hello!</div>
}
}
ReactDOM.render(<Example />, document.getElementById('root'));
<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="root"></div>
The above example lifts state up by having a parent compose the children and maintain the state. It then uses props to pass down an onClick handler to Child1, so that whenever Child1 is clicked, the state of the parent changes. Once the state of the parent changes, it will use conditional rendering to render <Child2> if the condition is truthy. Further reading at the React documentation and on the logical && operator.
I know how to set state in the component itself, but how do I affect another component when I click a different component?
The recommended way to do it would be to create a parent component that has the state. You'd then use that state to determine when to render the other child component.
I want to render BlackSpark when RedSpark is clicked, but I'm not sure how to change the state of a component in another component. Also, what if I want to hide BlackSpark when GreenSpark is clicked and GreenSpark is inside BlackSpark?
In this case, here's how you'd do it.
const GreenSpark = ({ onClick }) => (
<button className="green" onClick={onClick}>X</button>
)
const BlackSpark = ({ onClick }) => (
<div className="black">
<GreenSpark onClick={onClick} />
</div>
)
const RedSpark = ({ onClick }) => (
<div className="red" onClick={onClick}></div>
)
class Spark extends React.Component {
constructor(props) {
super(props)
this.state = {
showBlack: false
}
this.boundShowBlack = this.showBlack.bind(this)
this.boundHideBlack = this.hideBlack.bind(this)
}
showBlack() {
this.setState({ showBlack: true })
}
hideBlack() {
this.setState({ showBlack: false })
}
render() {
return (
<div>
<RedSpark onClick={this.boundShowBlack} />
{this.state.showBlack && <BlackSpark onClick={this.boundHideBlack} />}
</div>
)
}
}

Resources