Is it appropriate to use Document in the react framework? - javascript

I have this React component. Which renders a simple HTML. I have an event handler attached to an element. On clicking that particular element I want some CSS styles to change. For that I used the code below-
import React from 'react';
import './start.css';
class Start extends React.Component {
handleEvent() {
const login = document.querySelector('.login');
const start = document.querySelector('.start')
login.style.right = '0';
start.style.left = '-100vw';
}
render() {
return (
<section className = 'page start'>
<h1>Welcome To Our App</h1>
<button onClick = {this.handleEvent}>Next</button>
</section>
)
}
}
export default Start;
My question is in the handleEvent() is it appropriate to select the elements using Document and style the elements using .style. Is there any other "react-specific" way to do this?

class Test extends React.Component {
constructor(){
super();
this.state = {
black: true
}
}
changeColor(){
this.setState({black: !this.state.black})
}
render(){
let btn_class = this.state.black ? "blackButton" : "whiteButton";
return (
<div>
<button className={btn_class}
onClick={this.changeColor.bind(this)}>
Button
</button>
</div>
)
}
}
ReactDOM.render(<Test />, document.querySelector("#app"))
button{
width: 80px;
height: 40px;
margin: 15px;
}
.blackButton{
background-color: black;
color: white;
}
.whiteButton{
background-color: white;
color: black;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.1/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.1/umd/react-dom.production.min.js"></script>
<div id="app"></div>
first of all yes you can use document in react for that. But "react specific" style you be something like this:
<div id="app"></div>
In css file :
button{
width: 80px;
height: 40px;
margin: 15px;
}
.blackButton{
background-color: black;
color: white;
}
.whiteButton{
background-color: white;
color: black;
}
and finally a component :
class Test extends React.Component {
constructor(){
super();
this.state = {
black: true
}
}
changeColor(){
this.setState({black: !this.state.black})
}
render(){
let btn_class = this.state.black ? "blackButton" : "whiteButton";
return (
<div>
<button className={btn_class}
onClick={this.changeColor.bind(this)}>
Button
</button>
</div>
)
}
}
ReactDOM.render(<Test />, document.querySelector("#app"))

You can set a state to check whether the button has been clicked and change the class name
Similar approach can be used.
This is the React Specific way!
You can refer to React doc
https://reactjs.org/docs/faq-styling.html
import React from 'react';
import './start.css';
class Start extends React.Component {
constructor(props) {
super(props);
this.state = {hasButtonClicked : false};
}
handleEvent() {
this.setState({hasButtonClicked : true});
}
render() {
let clicked = this.state.hasButtonClicked;
return (
<section className = { clicked ? someCssClass :'page start'} >
<h1>Welcome To Our App</h1>
<button onClick = {this.handleEvent}>Next</button>
</section>
)
}
}

Related

How to bind a delete method to a rendered web component

I am having trouble figuring out how to bind my handleDelete method.
The way I have it structured is the user clicks the plus button then data objects get stored in an array on the window object. Then render is called on the SelectedProducts component that renders a card that has the button I am trying to bind the handleDelete method to.
If you run the code you can see what I have so far.
Maybe this is not the right approach just trying to do it with out adding a library.
Still trying to wrap my head around which lifecycle methods I need or a custom event?
class SelectedProducts extends HTMLElement {
constructor() {
super();
this.mockData = [
{ id: 1, name: 'name-1', qty: 1 },
{ id: 2, name: 'name-2', qty: 1 },
{ id: 2, name: 'name-1', qty: 2 },
];
}
handleDelete(e) {
e.preventDefault();
console.log('called handleDelete');
}
connectedCallback() {
this.querySelector('button').addEventListener('click', this.handleDelete.bind(this));
}
attributeChangedCallback(attrName, oldValue, newValue) {
console.log('attributeChange called');
this.handleDelete(e);
}
static get observedAttributes() {
return ['data-id'];
}
disconnectedCallback() {
console.log('disconnectedCallback ran');
}
render() {
this.mockData.forEach((item, index) => {
this.innerHTML += `
<div style="display:flex; align-items: center; background-color:white; padding:15px; ">
<button data-id="${item.id}" class="delete-btn">
Delete me
</button>
</div>
`;
});
}
handleDelete(e) {
e.preventDefault();
alert('Called Handle delete');
}
}
customElements.define('selected-products', SelectedProducts);
class ProductCard extends HTMLElement {
constructor() {
super();
this.attachShadow({
mode: 'open',
});
this.shadowRoot.innerHTML =
`<style>
::slotted(div){
color: #4B5563;
font-weight: 900;
text-align: center;
font-size: 20px;
}
</style>
` +
` <div style="background: white; margin-right: 15px;">
<slot name="button"></slot>
<slot name="img"></slot>
</div>
`;
}
}
customElements.define('product-card', ProductCard);
class SelectBtn extends HTMLElement {
constructor() {
super();
// This is called with render below
this.itemsPicked = document.querySelector('selected-products');
this.attachShadow({
mode: 'open',
});
this.shadowRoot.innerHTML = `
<button
aria-label="Select"
type="button"
class="pressed"
data-addbtn="add-btn"
>
+
</button>
`;
this.id = this.getAttribute('id');
this.name = this.getAttribute('name');
this.shadowRoot
.querySelectorAll('button')
.forEach((button) => button.addEventListener('click', this.handleSelect.bind(this)));
}
// Get data from attributes & store object in
// an array on window object
handleSelect(e) {
e.preventDefault();
this.itemsPicked.render();
}
}
customElements.define('select-button', SelectBtn);
<body>
<div style="display: flex; justify-content: center; align-items: center; background: lightblue; padding: 10px">
<product-card>
<div slot="button">
<select-button id="1" name="product name"></select-button>
</div>
<div slot="img">
<div style="height: 100px; width: 100px">Select Button</div>
</div>
</product-card>
<div>
<selected-products></selected-products>
</div>
</div>
<script src="app.js"></script>
<script>
window.selectedItems = {
items: [],
};
</script>
</body>
The way I solved this problem was not to attach an event listener to inner html itself. Instead I created another web component (delete-button) with and event listener and a method to handle the event. Any feedback would be super cool.
class DeleteButton extends HTMLElement {
constructor() {
super();
this.selectedProducts = document.querySelector('selected-products');
this.querySelectorAll('button').forEach((button) => button.addEventListener('click', this.handleDelete.bind(this)));
}
handleDelete(e) {
e.preventDefault();
alert('Delete button Event');
}
}
customElements.define('delete-button', DeleteButton);
class SelectedProducts extends HTMLElement {
constructor() {
super();
this.mockData = [
{ id: 1, name: 'name-1', qty: 1 },
{ id: 2, name: 'name-2', qty: 1 },
{ id: 2, name: 'name-1', qty: 2 },
];
}
render() {
this.mockData.forEach((item, index) => {
this.innerHTML += `
<div style="display:flex; align-items: center; background-color:white; padding:15px; ">
<delete-button>
<button data-id="${item.id}" class="delete-btn">
Delete me
</button>
</delete-button>
</div>
`;
});
}
handleDelete(e) {
e.preventDefault();
alert('Called Handle delete');
}
}
customElements.define('selected-products', SelectedProducts);
class ProductCard extends HTMLElement {
constructor() {
super();
this.attachShadow({
mode: 'open',
});
this.shadowRoot.innerHTML =
`<style>
::slotted(div){
color: #4B5563;
font-weight: 900;
text-align: center;
font-size: 20px;
}
</style>
` +
` <div style="background: white; margin-right: 15px;">
<slot name="button"></slot>
<slot name="img"></slot>
</div>
`;
}
}
customElements.define('product-card', ProductCard);
class SelectBtn extends HTMLElement {
constructor() {
super();
// This is called with render below
this.itemsPicked = document.querySelector('selected-products');
this.attachShadow({
mode: 'open',
});
this.shadowRoot.innerHTML = `
<button
aria-label="Select"
type="button"
class="pressed"
data-addbtn="add-btn"
>
+
</button>
`;
this.id = this.getAttribute('id');
this.name = this.getAttribute('name');
this.shadowRoot
.querySelectorAll('button')
.forEach((button) => button.addEventListener('click', this.handleSelect.bind(this)));
}
// Get data from attributes & store object in
// an array on window object
handleSelect(e) {
e.preventDefault();
this.itemsPicked.render();
}
}
customElements.define('select-button', SelectBtn);
<body>
<div style="display: flex; justify-content: center; align-items: center; background: lightblue; padding: 10px">
<product-card>
<div slot="button">
<select-button id="1" name="product name"></select-button>
</div>
<div slot="img">
<div style="height: 100px; width: 100px">Select Button</div>
</div>
</product-card>
<div>
<selected-products></selected-products>
</div>
</div>
<script src="app.js"></script>
<script>
window.selectedItems = {
items: [],
};
</script>
</body>

How to call a function with arguments onclick of a button in a react component

Here is my function with arguments that i added in index.html in publics folder in a script tag
function displayContent(event, contentNameID) {
let content = document.getElementsByClassName("contentClass");
let totalCount = content.length;
for (let count = 0; count < totalCount; count++) {
content[count].style.display = "none";
}
let links = document.getElementsByClassName("linkClass");
totalLinks = links.length;
for (let count = 0; count < totalLinks; count++) {
links[count].classList.remove("active");
}
document.getElementById(contentNameID).style.display = "block";
event.currentTarget.classList.add("active");
}
Trying to call this function from click of buttons on my react component that looks like below
<button class="linkClass" onclick="displayContent(event, 'project2')">Meet at Campus
</button>
Please guide me with the syntax
Here's the correct syntax
<button className="linkClass" onClick={(event)=>displayContent(event,'project2')}>Meet at Campus</button>
Edit: please note that React components return JSX
It looks like you're trying to make some sort accordion but you shouldn't really be mixing vanilla JS with React as React needs control of the DOM.
So here's a brief example of how you might approach this using 1) state, and 2) a Panel component which comprises a button, and some content.
const { useState } = React;
function Example() {
// Initialise state with an array of false values
const [ state, setState ] = useState([
false, false, false
]);
// When a button in a panel is clicked get
// its id from the dataset, create a new array using `map`
// and then set the new state (at which point the component
// will render again
function handleClick(e) {
const { id } = e.target.dataset;
const updated = state.map((el, i) => {
if (i === id - 1) return true;
return false;
});
setState(updated);
}
// Pass in some props to each Panel component
return (
<div>
<Panel
name="Panel 1"
active={state[0]}
id="1"
handleClick={handleClick}
>
<span className="text1">Content 1</span>
</Panel>
<Panel
name="Panel 2"
active={state[1]}
id="2"
handleClick={handleClick}
>
<span className="text2">Content 2</span>
</Panel>
<Panel
name="Panel 3"
active={state[2]}
id="3"
handleClick={handleClick}
>
<span className="text3">Content 3</span>
</Panel>
</div>
);
}
function Panel(props) {
// Destructure those props
const {
name,
id,
active,
handleClick,
children
} = props;
// Return a div with a button, and
// content found in the children prop
// When the button is clicked the handler is
// called from the parent component, the state
// is updated, a new render is done. If the active prop
// is true show the content otherwise hide it
return (
<div className="panel">
<button data-id={id} onClick={handleClick}>
{name}
</button>
<div className={active && 'show'}>
{children}
</div>
</div>
);
}
ReactDOM.render(
<Example />,
document.getElementById('react')
);
.panel button:hover { cursor: pointer; }
.panel { margin: 1em 0; }
.panel div { display: none; }
.panel div.show { display: block; margin: 1em 0; }
.add { margin-top: 1em; background-color: #44aa77; }
.text1 { color: darkblue; font-weight: 600; }
.text2 { color: darkgreen; font-weight: 700; }
.text3 { color: darkred; font-weight: 300; }
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.2/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.2/umd/react-dom.production.min.js"></script>
<div id="react"></div>
Can't you use
document.getElementById("linkClass").onclick = () =>{
displayContent();
}
by giving the element an id with same of the class?

Is it possible to pass dynamic props, from one page to another with next.js?

I'm new to Next and have been trying to make a page(index.js) that fetches data(countries) and then displays that data, where each returned element(country) has a button to go to a page(info.js) where that specific countries data will be displayed, was wondering if its possible to pass the props(all country data) to the info.js page? I've tried reading the documentation and watching YT videos but can't seem understand what i'm reading/watching.
index.js:
import Link from 'next/link'
Welcome.getInitialProps = async function (props) {
const res = await fetch('https://restcountries.eu/rest/v2/all')
const data = await res.json()
return {
data: data
}
}
const MyLink = props => {
return (
<p>
<Link href={`/info?name=${props.name}`} >
<a>Learn More</a>
</Link>
</p>
)
}
function Welcome(props) {
return (
<div>
<div className="main-content">
<style jsx>{`
.main-content {
width: 80%;
margin: 0 auto;
display: grid;
grid-template-columns: repeat(5, 1fr);
grid-gap: 5px;
}
.item {
border: 1px solid black;
text-align: center;
}
.item ul{
padding: 0;
}
.item ul li {
list-style-type: none;
}
`}</style>
{props.data.map(country => (
<div key={country.numericCode} className="item">
<h4>{country.name}</h4>
<p>Region: {country.region}</p>
<p>Population: {country.population}</p>
<MyLink name={country.name} borders={country.borders} currencies={country.currencies}/>
</div>
))}
</div>
</div>
)
}
export default Welcome
info.js:
import { withRouter } from 'next/router'
import Link from 'next/link'
const Info = (props) => {
return (
<div>
<h1>{props.router.query.name}</h1>
<Link href="/">
<a>Home</a>
</Link>
</div>
)
}
export default withRouter(Info)
In MyLink component instead of using Link you can create a normal div (style it like a link) and onClick of that div push it to different page using nextjs router:
//import useRouter
import { useRouter } from 'next/router'
//then call it
const router = useRouter()
const MyLink = props => {
return (
<p onClick={() => {
router.push({
pathname: `/info?name=${props.name}`,
query: { data: //data to pass },
})
}}>
<a>Learn More</a>
</p>
)
}
You can access that data in the location object in the query key
import {useLocation} from ""
const location = useLocation()
const data = location.query

Rendering empty space in React

Please see the snippet below. I am trying to render a blinking text, that, when it does not appear, leaves an empty space. However, React is just removing the element all together it seems. How do I properly render an empty space in React? Tried to do some searching and testing with various spans but still didn't get anywhere. Thanks.
class Blinker extends React.Component {
constructor(props) {
super();
this.state = {
appear: true
}
this.blinker = this.blinker.bind(this);
}
blinker() {
this.setState({appear: !this.state.appear });
}
componentDidMount() {
setInterval(this.blinker, 1000)
}
componentWillUnmount() {
clearInterval(this.blinker);
}
render() {
const name = "testing";
const underScore = "_";
const com = "com";
return (
<div>
<div id="test"> { name } </div>
<div id="test">
{ (this.state.appear) ? underScore : ' '}
</div>
<div id="test"> { com } </div>
</div>
);
}
}
ReactDOM.render(<Blinker />, app);
#test {
display: inline-block;
}
<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>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="app"></div>
You could use <span> </span>:
class Blinker extends React.Component {
constructor(props) {
super();
this.state = {
appear: true
}
this.blinker = this.blinker.bind(this);
}
blinker() {
this.setState({appear: !this.state.appear });
}
componentDidMount() {
setInterval(this.blinker, 1000)
}
componentWillUnmount() {
clearInterval(this.blinker);
}
render() {
const name = "testing";
const underScore = "_";
const com = "com";
return (
<div>
<div id="test"> { name } </div>
<div id="test">
{ (this.state.appear) ? underScore : <span> </span>}
</div>
<div id="test"> { com } </div>
</div>
);
}
}
ReactDOM.render(<Blinker />, document.getElementById('app'));
#test {
display: inline-block;
}
<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>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="app"></div>
It is because the three nested lines get transpiled as individual children of the parent div element, without taking the spaces into account. Solution is putting a space explicitly between the elements:
<div>
<div id="test"> { name } </div>
{''}
<div id="test">
{ (this.state.appear) ? underScore : "\u00a0\u00a0"}
{''}
</div>
<div id="test"> { com } </div>
</div>
I generally use <Fragment> </Fragment>, and programmatically render it as many times as I want. The following is another similar usage of 3 different special characters intended to be embedded. 🙂
import './rating.css'
let starMap={
'1': <Fragment></Fragment>,
'0.5': <Fragment></Fragment>,
'0': <Fragment></Fragment>
}
export default class Rating extends React.Component {
constructor(ops) {
super(ops)
}
render() {
return (
<Fragment>
<span className={'score'} style={{marginRight: '7px'}}>
<span className={'star'}>{starMap[1]}</span>
<span className={'star'}>{starMap[1]}</span>
<span className={'star'}>{starMap[1]}</span>
<span className={'star'}>{starMap[1]}</span>
<span className={'star'}>{starMap[0.5]}</span>
</span>
</Fragment>
)
}
}
class Blinker extends React.Component {
constructor(props) {
super();
this.state = {
appear: true
}
this.blinker = this.blinker.bind(this);
}
blinker() {
this.setState({appear: !this.state.appear });
}
componentDidMount() {
setInterval(this.blinker, 1000)
}
componentWillUnmount() {
clearInterval(this.blinker);
}
render() {
const name = "testing";
const underScore = "_";
const com = "com";
return (
<div>
<div id="test"> { name } </div>
<div id="test">
{ (this.state.appear) ? underScore : "\u00a0\u00a0"}
</div>
<div id="test"> { com } </div>
</div>
);
}
}
ReactDOM.render(<Blinker />, app);
#test {
display: inline-block;
}
<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>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="app"></div>
Haven't tested it, but it should work.
<div id="test" style={{ visibility: this.state.appear ? 'visible' : 'hidden' }}>
{{underScore}}
</div>
I use styled components and I've created an <Em /> component:
import styled from "styled-components"
const Em = styled.span`
display: inline-block;
width: ${({ size = 1 }) => size}em;
`
const App = () => {
return <h1>I need<Em size={.5} />a half space</h1>
}
If you had a hard time remembering this , You can instead use:
<span> </span> // <- this is inline empty space

Rendering a modal in React

I'm trying to trap an onclick method on a React component to create a React Modal.
I've added react-overlay as a dependency and added it to my file.
import Modal from 'react-overlays';
This is the anchor element,
<a href="#" onClick={this.handleClick} data-id={image.id}>
This is the handleclick method,
handleClick(event) {
event.preventDefault();
let mediaId = event.currentTarget.attributes['data-id'].value;
this.setState({ overlay: <Modal show={this.state.showModal} onHide={this.close} mediaId={mediaId}/> });
}
I get the following error,
Warning: React.createElement: type should not be null, undefined, boolean, or number. It should be a string (for DOM elements) or a ReactClass (for composite components).
Uncaught Error: Element type is invalid: expected a string (for built-in components) or a class/function (for composite components) but got: undefined.(…)
I recently had this problem and got around it by creating a Modal-component.
import Modal from 'react-modal'
export default class CustomModal extends React.Component {
constructor () {
super();
this.openModal = this.openModal.bind(this);
this.closeModal = this.closeModal.bind(this);
this.state = {
open: false
}
}
openModal () { this.setState(
{open: true});
$(function(){
$("#custom-modal").appendTo("body");
});
}
closeModal () {
this.setState({open: false});
}
componentDidMount(){
$(function(){
$("#custom-modal").appendTo("body");
});
}
render () {
return (
<div>
<button onClick={this.openModal}>My modal</button>
<Modal id="custom-modal" isOpen={this.state.open} onRequestClose={this.closeModal}>
// Modal body content here
<button onClick={this.closeModal}>Close</button>
</Modal>
</div>
);
}
}
And then using it like this:
import CustomModal from '../components/CustomModal'
...
<li><CustomModal/></li>
Hope this is of any help.
Your state should contain information that allows you to render the modal, but not the modal itself.
It's highly unusually to store components in state.
Try this:
Store a flag in your state to indicate whether the modal should be shown.
Set the flag in handleClick()
In render(), render the modal if the flag is set.
Let me know if you need an example.
Looks like you're importing undefined..
Also, take a look at https://github.com/fckt/react-layer-stack. This is universal and clean way to solve the "modal problem" in React. Demo - https://fckt.github.io/react-layer-stack/
In case anyone still has an issue with this, a modern approach is to build the modal using React hooks. as shown below
import React from 'react';
import './modal.css';
import FontAwesome from 'react-fontawesome';
const Modal = (props) => {
const { closeModal } = props;
const closeicon = () => (
<FontAwesome
name="times"
onClick={closeModal}
style={{
color: '#000000',
padding: '10px',
cursor: 'pointer',
backgroundColor: 'transparent',
border: 0,
position: 'absolute',
top: '0.3rem',
right: '0.5rem',
}}
/>
);
return (
<div className="overlay">
<div className="content">
{ closeicon() }
{props.children}
</div>
</div>
);
};
export default Modal;
The css is as shown below
.overlay {
position: fixed;
display: block;
overflow: auto;
width: 100%;
height: 100%;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: rgba(0,0,0,0.5);
z-index: 999;
cursor: pointer;
}
.content {
margin: 15% auto;
background-color: white;
border-radius: 0.25rem;
width: 50vw;
padding: 2rem;
position: relative;
}
So you can use the modal component like this
const [status, setStatus] = useState(false);
//this button will trigger the modal
<button onClick={() => setStatus(true)}>Open Modal</button>
{
status && (
<Modal closeModal={() => setStatus(false)}><p>hello worls</p></Modal>
)
}
No need to worry about responsiveness It's been taken care of in the styling.
For further explanation, you can check this link
https://dev.to/adeyemiadekore2/how-to-build-a-reusable-and-responsive-modal-in-react-from-scratch-1o0f

Categories