Parent child component event handler in loop - reactjs

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

Related

Getting null when invoking sibling component function

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

Show search result component in another route

I am working with the MovieDB API. I want to show now playing movies on the root route but search result in another route.
I have tried putting history.push() method in handlesubmit but it shows error. Here's the code. Currently I am showing search result component in the home page itself.
App.js
import React, { Component } from "react";
import "./App.css";
import { BrowserRouter, Link, Switch, Route } from "react-router-dom";
import Nav from "./component/Nav";
import axios from "axios";
import { Provider } from "./context";
import Home from "./component/Home";
import SearchResult from "./component/SearchResult";
import MovieDetails from "./component/movieDetails";
class App extends Component {
state = {
movieList: [],
searchResult: [],
currentpage: 1,
totalpage: 1,
API_KEY: "c51081c224217a3989b0bc0c4b3d3fff"
};
componentDidMount() {
this.getCurrentMovies();
}
getCurrentMovies = e => {
axios
.get(
`https://api.themoviedb.org/3/movie/now_playing?api_key=${
this.state.API_KEY
}&language=en-US&page=${this.state.currentpage}`
)
.then(res => {
this.setState({
movieList: res.data.results,
currentpage: res.data.page,
totalpage: res.data.total_pages
});
console.log(this.state);
});
};
getMovies = e => {
e.preventDefault();
const moviename = e.target.elements.moviename.value;
axios
.get(
`https://api.themoviedb.org/3/search/movie?api_key=${
this.state.API_KEY
}&query=${moviename}`
)
.then(res => {
this.setState({
searchResult: res.data.results
});
console.log(this.state.searchResult);
});
console.log(this.router);
};
nextPage = () => {
this.setState(
{
currentpage: (this.state.currentpage += 1)
},
() => console.log(this.state.currentpage)
);
this.getCurrentMovies();
};
prevPage = () => {
if (this.state.movieList && this.state.currentpage !== 1) {
this.setState(
{
currentpage: (this.state.currentpage -= 1)
},
() => console.log(this.state.currentpage)
);
this.getCurrentMovies();
}
};
render() {
const contextProps = {
myState: this.state,
getMovies: this.getMovies,
nextPage: this.nextPage,
prevPage: this.prevPage,
};
return (
<Provider value={contextProps}>
<BrowserRouter>
<Nav />
<Switch>
<Route exact path="/" component={Home} />
<Route path="/:id" component={MovieDetails} />
</Switch>
</BrowserRouter>
</Provider>
);
}
}
export default App;
Home.js
import React, { Component } from "react";
import NowPlaying from "./NowPlaying";
import SearchResult from "./SearchResult";
import SearchBox from "./SearchBox";
class Home extends Component {
state = {};
render() {
return (
<div>
<SearchBox />
<SearchResult />
<NowPlaying />
</div>
);
}
}
export default Home;
SearchBox.js
import React, { Component } from "react";
import { MyContext } from "../context";
import { withRouter } from "react-router-dom";
class SearchBox extends Component {
static contextType = MyContext;
render() {
return (
<React.Fragment>
<div className="jumbotron jumbotron-fluid">
<div className="container" style={{ textAlign: "center" }}>
<h1 className="display-4">Find your Movie</h1>
<p className="lead">
Find rating, descrips and much more of your fev. movie.
</p>
<form onSubmit={this.context.getMovies}>
<input
name="moviename"
className="form-control mr-sm-2"
type="search, submit"
placeholder="Search"
aria-label="Search"
style={{ height: "50px" }}
/>
</form>
</div>
</div>
<div />
</React.Fragment>
);
}
}
export default withRouter(SearchBox);
SearchResult.js
import React, { Component } from "react";
import Movie from "./movie";
import { withRouter } from "react-router-dom";
import { MyContext } from "../context";
import SearchBox from "./SearchBox";
class SearchResult extends Component {
static contextType = MyContext;
render() {
return (
<React.Fragment>
<div className="container">
<div className="row justify-content-center">
{this.context.myState.searchResult.map(movie => {
return <Movie id={movie.id} image={movie.poster_path} />;
})}
</div>
{/* <button>Prev</button>
<button>Next</button> */}
</div>
</React.Fragment>
);
}
}
export default SearchResult;
and another thing. The pagination works for Now Playing Movies but couldn't make it to work with search result. Please help.
You can pass data with Redirect like this:
<Redirect to={{
pathname: '/movies',
state: { id: '123' }
}}
/>
and this is how you can access it:
this.props.location.state.id

Nested React Router Component Won't Render on New Page

I have a react app that consists of a main Component. , which comprises a element with navbar markup.
//app.js
import React, { Component } from 'react';
import {
Route,
NavLink,
BrowserRouter,
Switch,
} from "react-router-dom";
import Home from './components/home';
import Create from './components/create';
import './App.css';
const pathBase = 'http://127.0.0.1:8000';
const pathSearch = '/posts/';
class App extends Component {
constructor(props) {
super(props);
this.state = {
posts: [],
post: null,
error: null,
}
}
setPostsState = result => {
this.setState({ posts: result})
}
setPostState = post => {
this.setState({ post: post});
console.log(this.state.post)
}
retrievePost = (event, post) => {
event.preventDefault();
this.fetchPost(post);
}
deletePosts = id => {
fetch(`${pathBase}${pathSearch}${id }`, { method: 'DELETE'})
.then(res => res.status === 204 ? this.fetchPosts() : null)
}
editPosts = id => {
fetch(`${pathBase}${pathSearch}${id }`, { method: 'PUT'})
.then(res => res.status === 204 ? this.fetchPosts() : null)
}
fetchPost = (post) => {
fetch(`${pathBase}${pathSearch}${post.id}`)
.then(response => response.json())
.then(result => this.setPostState(result))
.catch(error => console.log(error));
}
fetchPosts = () => {
fetch(`${pathBase}${pathSearch}`)
.then(response => response.json())
.then(result => this.setPostsState(result))
.catch(error => this.setState({ error: error }));
}
componentDidMount() {
this.fetchPosts();
}
render() {
const { posts, error } = this.state;
if (error) {
return <div className="container">
<h3>Error: {error.message}</h3>
</div>;
}
return (
<BrowserRouter>
<div className="container">
<header>
<nav className="navbar navbar-expand-md navbar-light bg-light">
<a className="navbar-brand" href="">Django Rest API</a>
<div className="collapse navbar-collapse" id="navbarSupportedContent">
<ul className="navbar-nav mr-auto">
<li className="nav-item active">
<NavLink to="/posts">Posts</NavLink>
</li>
<li className="nav-item">
<NavLink to="/create">Create</NavLink>
</li>
</ul>
</div>
</nav>
</header>
<Switch>
<Route path="/create"
render={(props) =>
<Create {...props}
fetchPosts={this.fetchPosts}
/>
}
/>
<Route path="/posts"
render={(props) =>
<Home {...props}
posts={posts}
deletePost={this.deletePosts}
/>
}
/>
</Switch>
</div>
</BrowserRouter>
);
}
}
export default App;
There is also a (using React-Router v4) which path="/posts" that renders a Component . This component consists of a list which maps over blog posts obtained from the DB. Each post displays as a card with the post name serving as a link, that when the user clicks should bring up the component to show Post details.
// components/home.js
import React, { Component } from 'react';
import Post from './post';
import {
Route,
NavLink,
} from "react-router-dom";
class Home extends Component {
render() {
const ulStyle = { listStyleType: 'none' }
const { deletePost, posts } = this.props;
const match = this.props;
console.log(match)
return (
<ul style={ ulStyle }>
<h3>Posts</h3>
{posts.map(post =>
<li
key={posts.indexOf(post)}
value={post.id}
>
<div className="card">
<div className="card-body">
<NavLink to={`/posts/${post.id}`}>{post.title}
</NavLink>, Created: {post.timestamp}
<button type="button"
onClick={() => deletePost(post.id)}
className="float-right btn btn-outline-danger btn-sm"
>
Delete
</button>
</div>
</div>
</li>
)}
<Route exact path={`/posts/:postId`} render={(props) =>
(<Post {...props} posts={posts}/>)}/>
</ul>
);
}
}
export default Home;
I am having trouble getting the post component to render on a new page. Currently if the user clicks a link, the component renders at the bottom of the existing page (the component). I need the Post component to render on a entirely new page. How did I do this?
import React, { Component } from 'react';
class Post extends Component {
render() {
const { children, match, posts} = this.props;
console.log(match);
console.log(posts);
const p = (posts) => {
posts.find(post => post === match.postId)
}
return (
<div>
{match.params.postId}
<p>HELLO WORLD</p>
</div>
);
}
}
export default Post;
What you've seen is an expected behaviour for React Router nested routes. Content of the nested route will be rendered as part of it's parent component.
It's designed this way so that the number of re-renderings will be kept to minimum when navigation (the content of the parent component will not be re-rendered when the nested routes changed).
If you want the whole page content to be changed, you should not use nested route. Simply put all the routes you have under the Switch component. In other words, move your single post route to App component.
you should have a div to the router components. for example
<Router>
<div className = "app">
<nav />
<div className = "container">
//routes to be render here
<Route exact path = "/sample" component = {Sample} />
</div>
<footer />
</div>
</Router>
so that the component will be render on the middle of the page and remove the current if the route changes.

React - show menu when clicking on just one item from iterating item

I'm having problem with sliding menu in just one item.
When I click on the config button every item shows menu. I tried to figure out something by passing props {mail.id} but I'm afraid I don't understand this.
I would like to have sliding menu just in one item -- the clicked one.
This is ConfigButton
import React, { Component } from "react";
import './Menu.css';
class ConfigButton extends Component {
render() {
return (
<button className="configButton"
onClick={this.props.onClick}
>
<i className="configButtonIcon fas fa-cog"></i>
</button>
);
}
}
export default ConfigButton;
And this is the Component which renders:
import React, { Component } from 'react';
import { NavLink, HashRouter } from 'react-router-dom';
import axios from 'axios';
import Menu from './Menu';
import ConfigButton from './ConfigButton';
const API = myAPI;
const navLinkStyle = {
textDecoration: 'none',
color: '#123e57'
};
class Emails extends Component {
constructor(props) {
super(props);
this.state = {
visible: false,
mails: []
};
this.handleMouseDown = this.handleMouseDown.bind(this);
this.toggleMenu = this.toggleMenu.bind(this);
}
handleMouseDown(e) {
this.toggleMenu();
e.stopPropagation();
}
toggleMenu() {
this.setState({
visible: !this.state.visible
});
}
componentDidMount() {
axios.get(API)
.then(response => {
const mails = response.data;
this.setState({ mails });
})
}
truncate = (text, chars = 140) =>
text.length < chars ? text : (text.slice(0, chars) + '...')
render() {
let mails = this.state.mails;
console.log(mails);
mails = mails.map(mail => {
return (
<div key={mail.id}>
<div className='mail'>
{
!mails.displayed
? <i className="notDisplayed fas fa-circle"></i>
: <i className="displayed far fa-circle"></i>
}
<HashRouter>
<NavLink
to={`/openemail/${mail.id}`}
style={navLinkStyle}
>
<ul className='ulMailWrap'>
<div className='mailHeader'>
<li>{mail.sender}</li>
<li>{mail.created}</li>
</div>
<li>{mail.subject}</li>
<li>{this.truncate(mail.message)}</li>
</ul>
</NavLink>
</HashRouter>
<ConfigButton onClick={this.handleMouseDown} />
<Menu handleMouseDown={this.handleMouseDown}
menuVisibility={this.state.visible}
/>
</div>
</div>
)
});
return (
<div>
{ mails }
</div>
);
}
}
export default Emails;
You can pass a function that will send a different parameter to the handler, depending on value of each element in the array.
Do something like this:
...
<div key={mail.id} onClick={() => this.handleOpenMenu(mail.id)}>
...
Then at the handler:
handleOpenMenu = id => {
// do different stuffs on the id you get here
this.setState({ visibleMenuId: id });
}
And then change the props you are passing to your menu component:
<Menu menuVisibility={this.state.visibleMenuId === mail.id} />

access state of react component from other component

I have the following spinner
import React, { Component } from 'react'
import './Spinner.scss'
export default class Spinner extends Component {
constructor(props) {
super(props);
this.state = {showLoading: true};
}
render () {
return (
<div className="spinner">
<div className="double-bounce1"></div>
<div className="double-bounce2"></div>
</div>
)
}
}
and from other component I would like to show or hide this spinner here is the code of the component:
import React, { Component } from 'react'
import RTable from '../../../components/RTable/RTable'
import Spinner from '../../../components/Spinner/Spinner'
import CsvDownload from '../containers/CsvDownloadContainer'
export default class Table extends Component {
_renderBreadcrumb () {
const { breadcrumb, handleBreadcrumbClick } = this.props
return (
<ol className="breadcrumb">
{(breadcrumb || []).map(el => {
return (
<li key={el.datasetKey}>
<a onClick={() => { handleBreadcrumbClick(el.granularity, el.datasetKey, el.datasetKeyHuman) }}>
{el.datasetKeyHuman}
</a>
</li>
)
})}
</ol>
)
}
render () {
const { datasetRows, columns, metadata, showLoading } = this.props
return (
<div className="row">
<div className="col-sm-12">
{this._renderBreadcrumb()}
<RTable rows={datasetRows} columns={columns} metadata={metadata} />
{ this.props.showLoading ? <Spinner /> : null }
<CsvDownload />
</div>
</div>
)
}
}
as you can see I trying to show or hide the spinner using:
{ this.props.showLoading ? <Spinner /> : null }
but I'm always getting undefinde. Some help please.
You have to move this
constructor(props) {
super(props);
this.state = {showLoading: true};
}
to your <Table /> component, otherwise you access showLoading from <Table />'s props, but it is not passed from anywhere.
Then change also
{ this.props.showLoading ? <Spinner /> : null }
to
{ this.state.showLoading ? <Spinner /> : null }
To show / hide <Spinner /> just call this.setState({ showLoading: Boolean }) in your <Table /> component.

Resources