I'm creation my portfolio section in ReactJS with Gatsby and I have some trouble to handle KeyDown.
My code permits me to detect when I'm pressing on ESC key but I can't trigger the close function as I did for the overlay (onClick event).
I have three differents files :
modal.js - Modal component
project.js - Project component
projets.js - project page
I create the modal window which will display the details of the project. The project component will display all the project thumbnails and finally the project page will render the Project Component.
Maybe there's something that I missing. I will appreciate your help.
Here is the code for the modal component :
modal.js
import React, { Component } from 'react'
import PropTypes from 'prop-types'
import { Link } from 'gatsby-link'
import './modal.scss'
import MdClose from 'react-icons/lib/md/close'
export class Modal extends Component {
constructor(props) {
super(props)
}
componentDidMount() {
this.initializeEscClosing();
}
initializeEscClosing() {
if (typeof window !== 'undefined') {
window.addEventListener('keydown', (e) => {
if (e.which == 27) {
//this.props.onClose
console.log('It\'s working')
}
});
}
}
render() {
// Render nothing if the "show" prop is false
if (!this.props.show) {
return null;
}
return (
<div className={`modal`}>
<div className={`modal__overlay`}
onClick={this.props.onClose}
onKeyDown = {
this.initializeEscClosing
}
tabIndex = "0"
>
</div>
<div className={`modal__container`}>
<div className={`modal__body`}>
<div className={`top`}>
<button onClick={this.props.onClose}><MdClose /></button>
</div>
<div className={`content`}>
{this.props.children}
</div>
</div>
</div>
</div>
)
}
}
Modal.propTypes = {
onClose: PropTypes.func.isRequired,
show: PropTypes.bool,
children: PropTypes.node
};
export default Modal
I noticed that when I'm pressing on ESC, the function is triggered 3 times because I have 3 projects in my .json file. How can I fix this issue ?
Here is the code for the project component :
project.js
import React, { Component } from 'react'
import PropTypes from 'prop-types'
import {Link} from 'gatsby-link'
import './project.scss'
import {LinkWebsite, ButtonProject} from '../../../components/atoms/button'
import {Modal } from '../modal'
export class Project extends Component {
constructor(props){
super(props)
this.state = {
opened:false
}
this._toggleModal = this._toggleModal.bind(this)
}
_toggleModal(){
this.setState({
opened: !this.state.opened
})
}
render(){
const { title, category, image, logo, children, website} = this.props
return(
<div className="project__container">
<div className="project__preview">
<button onClick={this._toggleModal}>
{logo ? <img src={logo.src} alt={title} /> : null}
<h2>{title} <span className="category">{category}</span></h2>
</button>
</div>
<div className="project__details">
<Modal
onClose={this._toggleModal}
show={this.state.opened}
>
{image ? <img src={image.src} alt={title} /> : null}
<h3>{title} <span className="category">{category}</span></h3>
{children}
{website ? <LinkWebsite link={website}>Voir le site</LinkWebsite> : null}
</Modal>
</div>
</div>
)
}
}
export default Project
Project.propTypes = {
title: PropTypes.string.isRequired,
category: PropTypes.string.isRequired,
image: PropTypes.shape({
src: PropTypes.string.isRequired,
alt: PropTypes.string.isRequired,
}).isRequired,
logo: PropTypes.shape({
src: PropTypes.string.isRequired,
alt: PropTypes.string.isRequired,
}).isRequired,
children: PropTypes.element.isRequired,
website: PropTypes.string,
};
Project.defaultProps = {
title: 'Nom du projet',
image: null,
logo: null,
children: 'Texte introductif du projet. Il fourni les éléments clés',
website: null,
};
Here is the code for the project page :
projets.js
import React, { Component } from 'react'
import Link from 'gatsby-link'
import { Project } from '../components/molecules/project'
const projectPage = ({ data }) => {
return(
<div>
<h1>Projets récents</h1>
<div className="projects__container">
{data.allProjectsJson.edges.map(({ node }, i) =>
(<Project
key={i}
title={node.title}
category={node.category}
image={{
src: node.image.childImageSharp.original.src,
alt: node.title,
}}
logo={{
src: node.logo.childImageSharp.original.src,
alt: node.title,
}}
website={node.website}
>
<p dangerouslySetInnerHTML={{ __html: node.description }} />
</Project>),
)}
</div>
</div>
)
}
export default projectPage
export const pageQuery = graphql`
query ProjectsQuery {
allProjectsJson {
edges {
node {
title
category
description
image {
childImageSharp {
original {
src
}
}
}
logo {
childImageSharp {
original {
src
}
}
}
website
}
}
}
}
`;
Thank you in advance for taking the time to help
Wish you a great Friday,
Kind regards,
Maral
I think that there is something not completely correct in the initializeEscClosing() function.
Why you create a listener inside it ? onKeyDown is a listener itselfs.
Try to do something like this to trigger the event.
<div onKeyDown = {event => this.keyDownHandler(event)}/>
event prop has all the infos about the key event so in the function that handles it you could check if the key is the correct one and eventually close the modal
You could try using arrow functions, I don’t have a laptop handy, apologize for that, but you can try something like this
// Modal component
...
onClick={()=> {this.props.onClose()}
// Project component
onClose={() => {console.log(‘might be triggered’); this.toggleModal()}}
Using arrow functions gets rid you of binding the functions from constructors
source
Related
On a React page I have:
<a
data-tab="settings"
onClick={() =>
this.setState({ active_tab: "settings" })
}
>
<i style={{ backgroundImage: "url(/icons/settings.svg)" }} />
<span>To settings</span>
</a>
How can I refactor this to a Component? How can I update the parent component (i.e., the React page) its local state from a Component?
1. With classes: based on #tom203 his answer below
Component file:
import React, { Component } from "react";
class MyButton extends Component {
constructor(props) {
super(props);
this.state = {
tab: props.tab,
icon: props.icon,
label: props.label,
};
}
render() {
return (
<div>
<button
onClick={ () => this.props.updateActiveTab(this.state.tab) }
data-tab={ this.state.tab }
>
<i style={{ backgroundImage: `url(${this.state.icon})` }} />
<span> { this.state.label } </span>
</button>
</div>
)
}
};
export default MyButton;
React page (parent Component):
import MyButton from "../components/MyButton/";
class MyReactPage extends Component {
updateActiveTab = value => {
this.setState({ active_tab: value });
}
render() {
return (
...
<MyButton
updateActiveTab={this.updateActiveTab}
tab="settings"
icon="/icons/settings.svg"
label="To settings"
/>
...
2. With React hooks
Component file:
import React, { useState } from "react";
const MyButton = (tab, icon, label) => {
const [active_tab, setActive_tab] = useState("data");
return (
<button
data-tab={ tab }
onClick={ () => setActive_tab({ tab }) }
>
<i style={{ backgroundImage: `url(${icon})` }} />
<span> { label } </span>
</button>
);
};
export default MyButton;
On React page (parent Component):
import MyButton from "../components/MyButton/";
class MyReactPage extends Component {
render() {
return (
...
MyButton({
tab: "settings",
icon: "/icons/settings.svg",
label: "To settings",
})
...
The hook setup generates two errors:
Invalid hook call. Hooks can only be called inside of the body of a
function component.
'active_tab' is declared but its value is never read.
You can't use this type of states in a function. You need a React component class or use generally React hooks.
So replace
const MyButton = (props) => {
by
class MyButton extends Component {
constructor(props) {
super(props);
}
and use a
render() {return (<div><a onClick={this.props.updateActiveTab(tab)} ...>XXX</a></div>)}
Call component:
<MyButton updateActiveTab={this.updateActiveTab} />
Main class:
...
updateActiveTab = value => {
this.setState({active_tab: {value}});
}
...
With Functional Components you have to use React Hooks.
Here's a link with an overview: https://reactjs.org/docs/hooks-intro.html
In your case it looks like you just need to use the useState hook to keep track of and change your active_tab state.
Here's How I would do it:
import React from "react";
const MyButton = ({tab, icon, label}) => {
const [tab, setTab] = useState(tab)
return (
<div className="tab-button">
<a
data-tab={tab}
// next line doesn't work...
onClick={() => setTab(tab)}
>
<i style={{ backgroundImage: `url(${icon})` }} />
<span> {label} </span>
</a>
</div>
);
};
The useState calls you see return the current state and a function to change the state.
For example:
const [tab, setTab] = useState(tab)
Here tab is the current state and setTab is a function that takes an argument, in this case a tab, it would then change the current state to whatever was passed in.
Hope that helps some. Let me know :)
I'm attempting to render Markdown using 'react-markdown' and setting the content as state. This state is set in componentDidMount, and console logging the state successfully logs the content like so (it's just nonsense as this is an example while I set it up):
**Bold text**
*Italic text*
__Underlined text__
~~Strikethrough text~~
List
- Hello
Numbered list
1. Number one
Image
![AA.png](/uploads/AA_6834a9a7ea.png)
However, when I then try to render my content in my component, it just comes through as blank.... Which I can't figure out why it's happening.
Here's my component code:
import React, { Component } from 'react';
import Strapi from 'strapi-sdk-javascript/build/main';
import Moment from 'react-moment';
import Markdown from 'react-markdown';
import './blog.css';
import MainHeader from '../global/main-header'
import Footer from '../global/footer'
class IndividualBlogPost extends Component {
constructor(props) {
super(props)
this.state = {
title: '',
description: '',
datePosted: '',
content: '',
author: ''
}
}
async componentDidMount() {
const params = this.props.match.params.id
try {
const posts = await strapiInstance.getEntry('blog-posts', params)
this.setState({
title: posts.Title,
description: posts.Description,
datePosted: posts.created_at,
content: posts.Content,
author: `${posts.created_by.firstname} ${posts.created_by.lastname}`
})
console.log(this.state.content)
}
catch(err) {
console.log(err)
}
}
render() {
const content = this.state.content
return (
<div>
<MainHeader />
<section className="individual-blogpost-section">
<div className="individual-blogpost-container">
<h1 className="individual-blogpost-heading">{this.state.title}</h1>
<div className="individual-blogpost-author-datePosted">
<p className="individual-blog-post-title-paragraph">{this.state.author} | <Moment format="MMM Do YYYY">{this.state.datePosted}</Moment></p>
</div>
<h4 className="individual-blogpost-description">{this.state.description}</h4>
<div className="individual-blogpost-content">
<Markdown src={content} />
</div>
</div>
</section>
<Footer />
</div>
)
}
}
export default IndividualBlogPost
As you can hopefully see, I'm attempting to set a const named content as this.state.content, and then passing the source of the markdown in to the Markdown component.
This is the first time I'm using Markdown, so I'm probably doing something obviously wrong, but any help would be appreciated!
According to their documentation, the content is actually passed as children to Markdown.
So you can do this:
<div className="individual-blogpost-content">
<Markdown >
{content}
</Markdown>
</div>
i am add package npm i remarkable.
and wrote this code
import React from "react";
import { Remarkable } from "remarkable";
class MarkdownEditor extends React.Component {
constructor(props) {
super(props);
this.md = new Remarkable();
this.handleChange = this.handleChange.bind(this);
this.state = { value: "Hello, **world**!" };
}
handleChange(e) {
this.setState({ value: e.target.value });
}
getRawMarkup() {
return { __html: this.md.render(this.state.value) };
}
render() {
return (
<div className="MarkdownEditor">
<h3>Input</h3>
<label htmlFor="markdown-content">Enter some markdown</label>
<textarea
id="markdown-content"
onChange={this.handleChange}
defaultValue={this.state.value}
/>
<h3>Output</h3>
<div
className="content"
dangerouslySetInnerHTML={this.getRawMarkup()}
/>
</div>
);
}
}
export default MarkdownEditor;
Work Demo
Learning react and using semantic-ui-react Menu to build a SideBarMenu , and have below Questions
How does clicking on a Menu.Item trigger parent component (SBMenu) render ?
like render on component is triggered either due to change in state or props , but what is changing for SBMenu ?
Why is SBMenu render function called twice ?
Rendering .... future
Rendering .... future
SidebarMenu
import React from 'react';
import { Sidebar, Menu, Segment, Icon } from 'semantic-ui-react';
import { Link } from 'react-router-dom';
import { withRouter } from 'react-router-dom';
import SBMenu from './SBMenu'
import './SidebarMenu.css';
import SBRoutes from './SBRoutes'
const menuItems = [
{ icon: 'dashboard', label: 'Future ITEM', name: 'future', url: '/future' },
{ icon: 'dashboard', label: 'Future ITEM1', name: 'future', url: '/future1' }
];
class SidebarMenu extends React.Component {
constructor(props) {
super(props);
this.handleItemClick = this.handleItemClick.bind(this);
}
handleItemClick () {
console.log('item clicked')
}
/*shouldComponentUpdate() {
return true;
}*/
render() {
console.log("SidebarMenu called....")
return (
<Sidebar.Pushable as={Segment} className="SidebarMenu">
<Sidebar
as={Menu}
borderless
animation="push"
icon="labeled"
inverted
onHide={this.handleSidebarHide}
vertical
visible={true}
width="thin"
>
<SBMenu menuItems={menuItems} />
</Sidebar>
<Sidebar.Pusher>
<div className="container">
<SBRoutes />
</div>
</Sidebar.Pusher>
</Sidebar.Pushable>
);
}
}
export default SidebarMenu;
SBMenu
class SBMenu extends React.Component {
constructor(props) {
super(props);
}
render() {
const {location,menuItems} = this.props;
console.log("Rendering .... " + location.pathname)
return (menuItems.map((item, index) => (
<Menu.Item
name={item.name}
as={Link}
to={item.url}
active={location.pathname === item.url}
data-menu={item.label}
key={`menu_item_${index}`}
>
<Icon name={item.icon} />
{item.label}
</Menu.Item>
))
);
}
}
export default withRouter(SBMenu);
You can change child state from parent using lifting state up. You can simply pass a method to the SBMenu and work with it.
SBMenu render twice because you use the same name in menuItems.
It should be working fine now if you change menuItems name.
Modal.tsx
import React from 'react';
import './Modal.css'
interface IProps {
show?: boolean;
handleClose?: any;
children: any;
};
const Modal:React.SFC<IProps> = ({ handleClose, show, children }: IProps) => {
const showHideClassName = show ? "display-block" : "display-none";
return <div className={showHideClassName}>
<section className="modal-main">
{children}
<button onClick={handleClose}>close</button>
</section>
</div>;
};
export default Modal;
infoPresenter.tsx
import { Icon } from 'antd';
import React from 'react';
import Modal from '../Modal';
import './Info.css';
class Info extends React.Component{
public state = {
show: false
};
public showModal = () => {
this.setState({ show:true })
}
public hideModal = () => {
this.setState({ show:false })
}
public render(){
return (
<section className="Info">
<div className="InfoTitle">
<h1>Philociphy</h1>
<p>
lorem ipsum
</p>
</div>
<div className="WholeBox">
<div className="BoxLeft" onClick={this.showModal}>
<Modal show={this.state.show} handleClose={this.hideModal}>
<p>Modal</p>
<p>Data</p>
</Modal>
<p>VISION</p>
<Icon type="arrow-left" />
</div>
<div className="BoxRight">
<p>VALUE</p>
<Icon type="arrow-right" />
</div>
</div>
</section>
)
}
}
export default Info;
I want to make the modal. it works but not vanish.
I checked all my state and props, but couldn't find problems.
I don't know where's wrong in my code.
please find it and fix it.
if you have more good idea which make the modal, please let me know how make it in typescript react.
Thanks for all guys who watch this questions
I need to render a modal/lightbox component dynamic into a list array component, but it only renders the last modal content.
How can I turn this modal component dynamic to call it from the main component and populate it with correct data from an object array?
My List component is:
import React, { Component } from 'react';
import LightBox from './LightBox';
class ListPrice extends Component {
constructor(props) {
super(props);
this.state = { isOpen: false };
}
toggleModal = () => {
this.setState({
isOpen: !this.state.isOpen
});
}
render() {
return (
<div>
{this.props.products.map(product => {
return(
<div>
<a key={product.id} onClick={this.toggleModal}>
<h3>{product.title}</h3>
<p>{product.description}</p>
</a>
<LightBox key={product.id} show={this.state.isOpen}
onClose={this.toggleModal}>
{product.modalContent}
</LightBox>
</div>
);
})}
</div>
);
}
}
export default ListPrice;
And my LightBox component is (I removed styles to display short code here):
import React from 'react';
import PropTypes from 'prop-types';
class LightBox extends React.Component {
render() {
if(!this.props.show) {
return null;
}
return (
<div>
<div>
{this.props.children}
<div>
<button onClick={this.props.onClose}>
Close
</button>
</div>
</div>
</div>
);
}
}
LightBox.propTypes = {
onClose: PropTypes.func.isRequired,
show: PropTypes.bool,
children: PropTypes.node
};
export default LightBox;
Thank you for any advice :)
With show={this.state.isOpen} you always display all the modals - only the last one is visible as other modals are displayed behind it.
In order to fix that you must show only the selected dialog. You can store opened dialog in state with construct like this.setState({ openedDialog: product.id }).
Then you can query if the dialog is open by using this.state.openedDialog === product.id. That should do the job.
openModal = (id) = () => {
this.setState({
openedDialog: id
});
}
closeModal = () => {
this.setState({
openedDialog: null
});
}
show={this.state.openedDialog === product.id}
onClick={this.openModal(product.id)}
onClose={this.closeModal}