I am implementing a scrolling functionality on the same page when the Contact Us button is clicked. The Contact Us is contained in a child component (MyNavbar); when clicked, it will scroll to a fragment contained in another child component (MyContactForm), which is sibling of MyNavbar.
Here's the parent component:
// App.js
import React, { Component } from 'react';
import MyNavbar from './components/MyNavbar';
import MyContactForm from './components/MyContactForm';
export default class App extends Component {
constructor(props) {
super(props);
...
}
scrollToContactForm = () => {
this.refs.contactForm.scrollTo();
}
render() {
return (
<main>
<MyNavbar onClickToContactUs={ () => this.scrollToContactForm() } />
<MyContactForm ref="contactForm" />
</main>
);
}
}
And here are the two child components, MyNavbar
// MyNavbar.js
import React, { useState } from 'react';
import { Navbar, Nav, NavItem, NavLink } from 'reactstrap';
const MyNavbar = (props) => {
return (
<Navbar>
<Nav>
...
<NavItem>
<NavLink href="/products/"> Products </NavLink>
</NavItem>
<NavItem>
<NavLink href="/services/"> Services </NavLink>
</NavItem>
<NavItem>
<NavLink onClick={ () => props.onClickToContactUs() } href="#"> Contact Us </NavLink>
</NavItem>
</Nav>
</Navbar>
);
}
export default MyNavbar;
and MyContactForm:
// MyContactForm.js
import React, { Component } from 'react';
import { Form, ... } from 'reactstrap';
export default class MyContactForm extends Component {
constructor(props) {
super(props);
this.state = {
email: '',
...
inquiry: ''
};
this.setEmail = this.setEmail.bind(this);
...
this.setInquiry = this.setInquiry.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
this.myRef = React.createRef();
}
setEmail(event) {
this.setState( { email: event.target.email } );
}
...
setInquiry(event) {
this.setState( { question: event.target.inquiry } );
}
handleSubmit(event) {
alert("Thank you for contacting us. We will respond to you shortly");
event.preventDefault();
}
scrollTo = () => window.scrollTo(0, this.myRef.current.offsetTop);
render() {
return (
<React.Fragment ref={this.myRef} >
<Form onSubmit={this.handleSubmit}>
...
</Form>
</React.Fragment>
);
}
}
The app runs, however when I click Contact Us, I get a message saying
this.myRef.current is null
How can I get this to work?
Here's what worked for me:
I replaced <React.Fragment> with a <div>. Putting the ref in <Form> doesn't work either, as it should be on a DOM node (HTML element, not React component). So MyContactForm.js becomes:
render() {
return (
<div ref={this.myRef} >
<Form onSubmit={this.handleSubmit}>
...
</Form>
</div>
);
}
Related
I have a NavMenu component class like this:
import React, { Component } from "react";
import { Navbar, NavItem, NavLink, OffcanvasBody } from "reactstrap";
import "./styles/NavMenu.css";
import Offcanvas from "react-bootstrap/Offcanvas";
export class NavMenu extends Component {
static displayName = NavMenu.name;
constructor(props) {
super(props);
this.state = {
showOffcanvas: false,
};
this.setShowOffcanvas = this.setShowOffcanvas.bind(this);
this.setHideOffcanvas = this.setHideOffcanvas.bind(this);
}
setShowOffcanvas() {
this.setState({ showOffcanvas: true });
}
setHideOffcanvas() {
this.setState({ showOffcanvas: false });
}
render() {
return (
<>
<Navbar className="sticky-top">
<ul className="left-navbar">
<NavItem>
<NavLink onClick={this.setShowOffcanvas} className="original">
Open Offcanvas
</NavLink>
</NavItem>
</ul>
</Navbar>
<Offcanvas
className="offcanvasPlayer"
show={this.state.showOffcanvas}
onHide={this.setHideOffcanvas}
>
<NavLink
className="text-white card-link close-btn"
data-bs-dismiss="offcanvas"
onClick={this.setHideOffcanvas}
></NavLink>
<OffcanvasBody>{/* offcanvas body here */}</OffcanvasBody>
</Offcanvas>
</>
);
}
}
then my navbar button to open offcanvas in another component like this :
import React, { Component } from "react";
import { NavLink } from "reactstrap";
import "./styles/TopNavbar.css";
export class TopNavbar extends Component {
static displayName = TopNavbar.name;
constructor(props) {
super(props);
this.state = {
showOffcanvas: false,
showPlayer: false,
};
this.setShowOffcanvas = this.setShowOffcanvas.bind(this);
this.setHideOffcanvas = this.setHideOffcanvas.bind(this);
}
setShowOffcanvas() {
this.setState({ showOffcanvas: true });
}
setHideOffcanvas() {
this.setState({ showOffcanvas: false });
}
render() {
return (
<>
<NavLink className="item" onClick={this.setShowOffcanvas}>Opening Offcanvas NavMenu component</NavLink>
</>
);
}
}
but when i'm clicking on the (this.setShowOffcanvas), project show this error:
Uncaught TypeError: Cannot read properties of null (reading 'hide')
at HTMLAnchorElement.<anonymous> (offcanvas.js:254:1)
at HTMLDocument.handler (event-handler.js:118:1)
I swear to God, whatever I did didn't work
I have a button that I am using to toggle my sidebar in react application. The toggle button works fine for first two toggle states than it repeats the state twice for third time.
This is how I am toggling state from child component to parent:
import React, { Component } from 'react'
export default class Header extends Component {
constructor(props) {
super(props)
this.state = {
toggle: false
}
}
toggleSidebar = () => {
this.setState({
toggle : !this.state.toggle
});
console.log(this.state.toggle)
this.props.getToggleState(this.state.toggle);
}
render() {
return (
<div>
<button style={{width: '60px'}} onClick={this.toggleSidebar}>Toogle</button>
</div>
)
}
}
export default class App extends Component{
constructor(props) {
super(props)
this.state = {
toggleVal:''
}
}
getData = (val) => {
this.setState({
toggleVal: val
})
}
render(){
let toggleConst = '';
if(this.state.toggleVal){
toggleConst = (
<Router>
<div style={{display: 'flex', backgroundColor: '#ccc', height: '100%', flexDirection:'row'}}>
<div style={{flexDirection:'column'}}>
<Header getToggleState={this.getData}/>
<Routes/>
<Footer/>
</div>
</div>
</Router>
)
}
else{
toggleConst = (
<Router>
<div style={{display: 'flex', backgroundColor: '#ccc', height: '100%', flexDirection:'row'}}>
<SideNav toggleVal={this.state.toggleVal}/>
<div style={{flexDirection:'column'}}>
<Header getToggleState={this.getData}/>
<Routes/>
<Footer/>
</div>
</div>
</Router>
)
}
return (
toggleConst
);
}
}
Toggling the button hides/open the sidebar perfectly but it stuck on state when gets 'false' as twice.
This is how state console goes:
I am not able to find the problem here. Any help appreciated.
App.js
import React, {Component} from 'react';
import { BrowserRouter as Router} from "react-router-dom";
import Header from './Header';
import Sidebar from './Sidebar'
export default class App extends Component{
constructor(props) {
super(props)
this.state = {
toggleVal: false
}
}
getData = (val) => {
this.setState({
toggleVal: val
});
}
render(){
console.log("called.....123...",this.state.toggleVal)
if(this.state.toggleVal){
return (
<Router>
<div style={{display: 'flex', backgroundColor: '#ccc', height: '100%', flexDirection:'row'}}>
<Sidebar toggleVal={this.state.toggleVal}/>
<div style={{flexDirection:'column'}}>
<Header getToggleState={this.getData} />
</div>
</div>
</Router>
)
}
else{
return (
<Router>
<div style={{display: 'flex', backgroundColor: '#ccc', height: '100%', flexDirection:'row'}}>
<Sidebar toggleVal={this.state.toggleVal}/>
<div style={{flexDirection:'column'}}>
<Header getToggleState={this.getData}/>
</div>
</div>
</Router>
)
}
}
}
Header.js
import React, { Component } from 'react'
export default class Header extends Component {
constructor(props) {
super(props)
this.state = {
toggle: false
}
}
toggleSidebar = () => {
this.setState({
toggle: !this.state.toggle
},()=>{
// console.log(this.state.toggle)
this.props.getToggleState(this.state.toggle);
});
}
render() {
return (
<div>
<button onClick={()=>this.toggleSidebar(this.state.toggle)}>Toogle</button>
</div>
)
}
}
Sidebar.js
import React, { Component } from 'react'
import { NavLink } from "react-router-dom";
export default class Sidebar extends Component {
render() {
return (
<>
{
this.props.toggleVal &&
<div className="sidebar_container">
<nav className="nav_container">
<ul>
<li>
<NavLink to="/" activeClassName="active" exact={true}>Dashboard</NavLink>
</li>
<li>
<NavLink to="/user" activeClassName="active">User PRofile</NavLink>
</li>
<li>
<NavLink to="/register" activeClassName="active">Register</NavLink>
</li>
</ul>
</nav>
</div>
}
</>
)
}
}
https://repl.it/repls/IncredibleLinedCgi
This Will Work for You
Change this part of the code:
this.setState({
toggle : !this.state.toggle
});
To this:
this.setState(prev => {
return { toggle : !prev.toggle }
});
You should call getToggleState inside your setState callback in order to use proper state as argument
this.setState(prevState => {
this.props.getToggleState(!prevState.toggle);
return { toggle: !prevState.toggle };
});
Despite this solution, it's better if you don't keep duplicate state in child component <Header /> as conditional render is Parent duty.
This could be much simpler in my opinion.
Define the state on the parent component App ìsToggled
Call from the child component Header via callback this.props.onToggle()
Use conditional rendering on parent component {this.state.isToggled && <Sidebar/>}
import React, {Component} from 'react';
import {BrowserRouter as Router} from "react-router-dom";
import Header from './Header';
import Sidebar from './Sidebar'
export default class App extends Component {
constructor(props) {
super(props);
this.state = {
isToggled: false
}
};
onToggle = () => {
this.setState({
isToggled: !this.state.isToggled
});
console.log(this.state.isToggled);
};
render() {
return (
<Router>
<div style={{display: 'flex', backgroundColor: '#ccc', height: '100%', flexDirection: 'row'}}>
<div style={{flexDirection: 'column'}}>
<Header onToggle={this.onToggle}/>
</div>
{this.state.isToggled && <Sidebar/>}
</div>
</Router>
)
}
}
import React, {Component} from 'react'
export default class Header extends Component {
constructor(props) {
super(props)
}
render() {
return (
<div>
<button onClick={() => {
this.props.onToggle()
}}>Toggle
</button>
</div>
)
}
}
import React, {Component} from 'react'
import {NavLink} from "react-router-dom";
export default class Sidebar extends Component {
render() {
return (
<div className="sidebar_container">
<nav className="nav_container">
<ul>
<li>
<NavLink to="/" activeClassName="active" exact={true}>Dashboard</NavLink>
</li>
<li>
<NavLink to="/user" activeClassName="active">User PRofile</NavLink>
</li>
<li>
<NavLink to="/register" activeClassName="active">Register</NavLink>
</li>
</ul>
</nav>
</div>
)
}
}
I'm not getting props in my Nav component. Odd thing is, 'this.props.history.push' is working in my other components.
The same function is working in my other components, but when I try to call the push function, I'm getting 'err in logout TypeError: Cannot read property 'push' of undefined'. The 'this.props' object is logging as '{}'.
Any help is appreciated, thank you.
import React from 'react'
import logo from 'logo.png'
import css from './Nav.module.scss'
import { Link } from 'react-router-dom'
import Cookies from 'js-cookie'
import axios from 'axios'
class Nav extends React.Component {
constructor(props) {
super(props)
this.state = {
loggedIn: false
}
console.log(this.props)
}
_handleLogout = () => {
// const self = this
console.log(this.props)
axios.get('http://localhost:8080/logout', {
withCredentials: true
})
.then(res => {
console.log(res)
console.log('logout')
if (Cookies.get('sid') === undefined) {
this.props.history.push('/')
}
console.log(this.props)
})
.catch(err => {
console.log('err in logout', err)
})
}
render() {
return (
<div className={css.nav}>
<div className={css.leftPart}>
<Link to="/">
<div className={css.brandicon}>
<img src={logo} alt="Logo" />
</div>
<div className={css.brandname}>
somebrand
</div>
</Link>
</div>
<div className={css.rightPart}>
{
Cookies.get('sid') === undefined ?
<Link to="/login">
<div className={css.loginButton}>
Login
</div>
</Link>
:
<div className={css.logoutButton} onClick={this._handleLogout}>
Logout
</div>
}
</div>
</div>
)
}
}
export default Nav
My Nav component is only referenced once in my Layout component:
import React from 'react'
import Nav from 'components/Nav/Nav'
import css from './BasicLayout.module.scss'
class Basic extends React.Component {
render() {
return (
<div className={css.page}>
<Nav />
<div className={css.content}>
{this.props.children}
</div>
</div>
)
}
}
export default Basic
history and location are special props injected by React Router's HOC withRouter
import { withRouter } from 'react-router-dom'
class Nav extends React.Component{
render(){
const { history, location } = this.props
return <div>{`I'm at ${location.pathname}`}</div>
}
}
export default withRouter(Nav)
It works for functional components as well
export const Component = withRouter(({ history, location })) =>(
<div>{`I'm at ${location.pathname}`}</div>
)
I have created one child component and one parent component the child component gets called in from loop and event handlers also get called from prop but when i click on navlink from child component every click event is called
if first navlink is called then only handler for first navlink should be called.
import { NavLink } from "react-router-dom";
import React, { Component } from "react";
import axios from "axios";
// import SearchComponent from "../Common/SearchComponent";
// import Navbar from "../Common/Navbar";
import { CompanyId } from "../../locales/global.json";
import CategoryComponent from "./CategoryComponent";
class CategoryPageComponent extends Component {
state = {
Categories: []
};
componentDidMount() {
if (this.state.Categories.length === 0) this.bindCategoryData();
}
onCategorySearch = e => {
if (this.state.Categories.length === 0 && e === "")
this.bindCategoryData(0);
};
bindCategoryData = () => {
var $this = this;
axios
.post(
"http://fstrumplifyml.azurewebsites.net/api/ApiCategory/GetCategories",
{
companyid: CompanyId,
languageid: 1
}
)
.then(function(response) {
$this.setState({
Categories: response.data.Data
});
});
};
UpdateSubCategories = (categories, categoryId) => {
console.log(categories);
};
renderCategoryComponent = category => {
return (
<CategoryComponent
Category={category}
OnUpdateSubCategories={this.UpdateSubCategories.bind(this)}
/>
);
};
render() {
return (
<React.Fragment>
{/* <Navbar /> */}
<section className="space--sm">
<div className="container">
{/* <SearchComponent onSearch={this.onCategorySearch} /> */}
<div className="row">
{this.state.Categories.map(this.renderCategoryComponent)}
</div>
</div>
</section>
</React.Fragment>
);
}
}
export default CategoryPageComponent;
import React, { Component } from "react";
import { NavLink } from "react-router-dom";
class CategoryComponent extends Component {
render() {
return (
<div className="masonry__item col-md-4 filter-computing">
<div className="product">
<NavLink
to={this.props.OnUpdateSubCategories(
this.props.Category.SubCategories,
this.props.Category.CategoryId
)}
>
<img
alt={this.props.Category.CategoryName}
className="ProductImage"
src={this.props.Category.ImageUrl}
/>
</NavLink>
<NavLink
className="block"
to={this.props.OnUpdateSubCategories(
this.props.Category.SubCategories,
this.props.Category.CategoryId
)}
>
<div>
<h5>{this.props.Category.CategoryName}</h5>
</div>
</NavLink>
</div>
</div>
);
}
}
export default CategoryComponent;
<NavLink
className="block"
to={this.props.OnUpdateSubCategories(
this.props.Category.SubCategories,
this.props.Category.CategoryId
)}
>
when you put a function call inside props, it will be called everytime when render() runs
you can use arrow functions in props
to={
()=>{this.props.OnUpdateSubCategories(
this.props.Category.SubCategories,
this.props.Category.CategoryId
)}
}
but a function instance will be created in every render
so it is better to create a function inside CategoryComponent
Im getting a big headache.. I dont know what Im doing wrong here. When my Podcast.js component renders, I get 'Cannot read property 'params' of undefined... '
Someone that can point me in the right direction?
This is the parent component of Podcast:
import React, { Component } from 'react';
import { BrowserRouter as Router, Route } from 'react-router-dom';
import NavLinks from './components/NavLinks';
import Home from './components/Home';
import Podcast from './components/Podcast';
import './App.css';
class App extends Component {
render() {
return (
<Router>
<div className="App">
<NavLinks />
<Route exact path='/' component={Home} />
<Route path='/podcast/:podID' component={Podcast} />
</div>
</Router>
);
}
}
export default App;
This is my main Component (Podcast):
import React, { Component } from 'react';
import PodcastList from './PodcastList';
class Podcast extends Component {
constructor(props) {
super(props);
this.state = {
podcast: []
};
}
// Fetches podID from props.match
fetchPodcast () {
const podID = this.props.match.params.podID
fetch(`https://itunes.apple.com/search?term=podcast&country=${podID}&media=podcast&entity=podcast&limit=20`)
.then(response => response.json())
.then(data => this.setState({ podcast: data.results }));
}
componentDidMount () {
this.fetchPodcast()
}
// Check if new props is not the same as prevProps
componentDidUpdate (prevProps) {
// respond to parameter change
let oldId = prevProps.match.params.podID
let newId = this.props.match.params.podID
if (newId !== oldId)
this.fetchPodcast()
}
render() {
return (
<div>
<PodcastList />
</div>
)
}
}
export default Podcast;
This is the component thats list's all podcasts:
import React, { Component } from 'react';
class PodcastList extends Component {
render() {
return (
<div>
<h2>Country ({this.props.match.params.podID}) </h2>
<ul>
{this.state.podcast.map(podcast =>
<li key={podcast.collectionId}>
<a
href={podcast.collectionId}>
{podcast.collectionName}</a>
</li>
)}
</ul>
</div>
)
}
}
export default PodcastList;
Where does the error comes from? Podcast or PodcastList ? Maybe because you're not passing the props down to PodcastList ?
Try:
<PodcastList {...this.props} {...this.state} />
Also, in the child component (PodcastList) use this.props and not this.state
I guess you are using react-router. To have match prop of the React Router you have to decorate it by withRouter decorator of the module
import React, { Component } from 'react';
import { withRouter } from 'react-router';
class PodcastList extends Component {
render() {
return (
<div>
<h2>Country ({this.props.match.params.podID}) </h2>
<ul>
{this.state.podcast.map(podcast =>
<li key={podcast.collectionId}>
<a
href={podcast.collectionId}>
{podcast.collectionName}</a>
</li>
)}
</ul>
</div>
)
}
}
export default withRouter(PodcastList);
UPDATE:
One of the ways how to handle podcast prop in the PodcastList. The solutions fits all React recommendations and best practices.
import React, { PureComponent } from 'react';
import PodcastItem from './PodcastItem';
class Podcast extends PureComponent { // PureComponent is preferred here instead of Component
constructor(props) {
super(props);
this.state = {
podcast: []
};
}
// Fetches podID from props.match
fetchPodcast () {
const podID = this.props.match.params.podID
fetch(`https://itunes.apple.com/search?term=podcast&country=${podID}&media=podcast&entity=podcast&limit=20`)
.then(response => response.json())
.then(data => this.setState({ podcast: data.results }));
}
componentDidMount () {
this.fetchPodcast()
}
// Check if new props is not the same as prevProps
componentDidUpdate (prevProps) {
// respond to parameter change
let oldId = prevProps.match.params.podID
let newId = this.props.match.params.podID
if (newId !== oldId)
this.fetchPodcast()
}
render() {
return (
<div>
<h2>Country ({this.props.match.params.podID}) </h2>
<ul>
{this.state.podcast.map(podcast => (
<PodcastItem key={podcast.collectionId}> podcast={podcast} />
))}
</ul>
</div>
)
}
}
export default Podcast;
import React from 'react';
// Here Stateless function is enough
const PodcastItem = ({ podcast }) => (
<li key={podcast.collectionId}>
<a href={podcast.collectionId}>{podcast.collectionName}</a>
</li>
);
export default PodcastItem;