Input field value working in one area and not in another - javascript

I am designing my website in ReactJS and I'm having an issue. The desktop version of the website works perfectly! The mobile version, however, has one problem: my input field claims to have no value! I'll post the code below, but basically the desktop version has the email signup in the toolbar at the top (this is where it works) and the mobile version has it in the sidedrawer, where my links are (doesn't work here).
App.js
addEmailSub = () => {
var email = document.getElementById("email").value;
mailerlite.addSubscriber(104625980, email).then(() => {
this.setState({isSubscribed: true});
});
}
render() {
var backdrop;
if(this.state.sideDrawerOpen) {
backdrop = <Backdrop click={this.backdropClickHandler}/>;
}
return(
<div>
<Toolbar drawerClickHandler={this.drawerToggleClickHandler} addEmailSub={this.addEmailSub} isSubscribed={this.state.isSubscribed}/>
<SideDrawer show={this.state.sideDrawerOpen} addEmailSub={this.addEmailSub} isSubscribed={this.state.isSubscribed}/>
{backdrop}
<Switch>
<Route path="/books" component={Books} />
<Route path="/events" component={Events} />
<Route path="*" component={About} />
</Switch>
</div>
);
}
}
Toolbar.js (this is where the signup component works)
const toolbar = props => (
<header className="toolbar">
<nav className="toolbar-navigation">
<div className="toolbar-toggle">
<DrawerToggleButton click={props.drawerClickHandler}/>
</div>
<div className="toolbar-logo">
<img className="logo" src={logo}></img>
</div>
<SocialMedia />
<div className="spacer" />
<div className="signup">
<Signup addEmailSub={props.addEmailSub} isSubscribed={props.isSubscribed}/> //this works
</div>
<div className="spacer" />
<div className="toolbar-nav-items">
<ul>
<li><Link to="/about">About</Link></li>
<li><Link to="/books">Books</Link></li>
<li><Link to="/events">Events</Link></li>
</ul>
</div>
</nav>
</header>
);
Sidedrawer.js
const sideDrawer = props => {
var drawerClasses = 'side-drawer';
if(props.show) {
drawerClasses = 'side-drawer open';
}
return(
<nav className={drawerClasses}>
<ul>
<li><Link to="/about">About</Link></li>
<li><Link to="/books">Books</Link></li>
<li><Link to="/events">Events</Link></li>
</ul>
<div className="side-drawer-signup">
<Signup addEmailSub={props.addEmailSub} isSubscribed={props.isSubscribed}/> //this does not work
</div>
</nav>
);
};
Signup.js
const signup = props => {
if(props.isSubscribed) {
return(<div>
<h3 className="signup-thanks">Thanks for signing up! Check your email :)</h3>
</div>)
}
else{
return(<div>
<input id="email" className="signup-input" placeholder="Enter your email address"></input>
<button onClick={props.addEmailSub} className="signup-button">Beta Read!</button>
</div>);
}
}
Unless I forgot how to read, I am positive I passed down all the props and functions appropriately, but the Signup component in the Sidedrawer doesn't capture any form data. I posted on Reddit and have searched the first few pages of Stackoverflow, but I couldn't find anything pertaining to my exact issue: where the input field captures text in one spot but not the other.
EDIT:
Thanks to yjay, I got it working properly! They were correct, it was looking for the first input id of "email". I thought it would be treated as the same input box rendered in a different spot, but it actually treats it as two separate inputs. I will post how I fixed it here:
App.js
addEmailSub = () => {
var email = document.getElementById("email").value;
if (!email) { //this checks if the first input box has no value
email = document.getElementById("sidebar-email").value; //this tells the website to look at the other input box
}
console.log('value of email is: ' + email); //VINDICATION
mailerlite.addSubscriber(104625980, email).then(() => {
this.setState({isSubscribed: true});
});
}
Toolbar.js
<Signup inputID="email" addEmailSub={props.addEmailSub} isSubscribed={props.isSubscribed}/> //this is all I changed from above. Added inputID prop
Sidedrawer.js
<Signup inputID="sidebar-email" addEmailSub={props.addEmailSub} isSubscribed={props.isSubscribed}/> //this is all I changed here, just like toolbar, adding inputID as a prop with a different id
Signup.js
return(<div>
<input id={props.inputID} className="signup-input" placeholder="Enter your email address"></input> //here I use the prop to dynamically change the id based on where it's being rendered
<button onClick={props.addEmailSub} className="signup-button">Beta Read!</button>
</div>);
Fixed just in time, thank you yjay!

It seem you have two elements with same id "email". It makes your html invalid and it seems to force getElementById, which return one elem, to choose. My guess is it has chosen the one from Toolbar (prolly cause it's higher). I think you should show one of them conditionally or give them different IDs.

Related

How do I make React onClick calls only affect a single item, instead of all?

I have an array of objects, and for each one I .map it into a component called Card.js. Each card has an 'edit' button, and I have an edit form which I want to appear ONLY for the card on which I clicked the button.
At the moment, whatever I try to do to pass an id into the Editform.js component, it still makes the form appear for all of the card components.
Here's the current component I call which is meant to render just form for the clicked button. I pass in all of the cards in the 'cards' array, and what I believe is the id of the current .map object from the calling function:
function Editform({ cards, setCards, id }) {
const thisCard = cards.filter((card) => card.id === id)[0];
const editThisCard = thisCard.id === id; // trying to match id of passed card to correct card in 'cards' array.
console.log(editThisCard);
return (
<>
{editThisCard && ( // should only render if editThisCard is true.
<div className="form">
<p>Name of game:</p>
<input type="text" value={thisCard.gamename}></input>
<p>Max players: </p>
<input type="text" value={thisCard.maxplayers}></input>
<p>Free spaces: </p>
<input type="text" value={thisCard.freespaces}></input>
<p>Table #: </p>
<input type="text" value={thisCard.tablenum}></input>
<p></p>
<button type="button" className="playbutton">
Save changes
</button>
</div>
)}
</>
);
}
export default Editform;
edit: apologies, I forgot to paste in the other code. Here it is. Note that I'm just hardcoding in a couple of cards for now:
import React from "react";
import ReactFitText from "react-fittext";
import Editform from "./Editform";
function Displaycards({ lastid }) {
const [cards, setCards] = React.useState([
{
id: 1,
gamename: "El Dorado",
maxplayers: 4,
freespaces: 1,
tablenum: 5,
},
{
id: 2,
gamename: "Ticket to Ride",
maxplayers: 4,
freespaces: 2,
tablenum: 3,
},
]); // using the React state for the cards array
const [showForm, setShowForm] = React.useState((false);
return (
<div className="cardwrapper">
{cards.map(({ id, gamename, maxplayers, freespaces, tablenum }) => {
return (
<div key={id}>
<div>
<div className="card">
<ReactFitText compressor={0.8}>
<div className="gamename">{gamename}</div>
</ReactFitText>
<div className="details">
<p>Setup for: </p>
<p className="bignumbers">{maxplayers}</p>
</div>
<div className="details">
<p>Spaces free:</p>
<p className="bignumbers">{freespaces}</p>
</div>
<div className="details">
<p>Table #</p>
<p className="bignumbers">{tablenum}</p>
</div>
<button type="button" className="playbutton">
I want to play
</button>
<br />
</div>
<div className="editbuttons">
<button
type="button"
className="editbutton"
onClick={() => setShowForm(!showForm)}
>
Edit
</button>
<button type="button" className="delbutton">
X
</button>
</div>
{showForm && (
<div>
<Editform
cards={cards}
setCards={setCards}
id={id}
/>
</div>
)}
</div>
</div>
);
})}
</div>
);
}
export default Displaycards;
I feel like I'm missing something obvious, but I can't get my head around what it is. The current iteration of it is here - https://github.com/TSDAdam/lfp/tree/usestate-trial - and it was created with create-react-app .
It sounds like you have one state controlling all of the Cards. You haven't shown the Card component yet however. Have every Card control its own state, so when the edit button bound to the card is clicked, it only applies to that one card. If you show us more code we can narrow it down, but this is most likely the gist of your problem.
The problem is that the EditForm is inside the map function, so for every item in your cards array, a separate EditForm is rendered with the corresponding values, and all these EditForms get shown/hidden based on the same boolean in your state.
The solution is to move the EditForm outside the map function, and create a new state object that tracks an "active" card, from where the single EditForm could take its values.
This of course won't work if you want to render the EditForm in a position relative to the "active" card.
[Edit]
Okay, I ended my answer with a caveat, but I should add a solution for that as well, since it isn't very complicated.
If you want to render an EditForm below the selected card, for example, the approach would be to keep it inside the map function as it is now, and change the boolean state variable showForm into one that accepts a string/number (depending on what you use as the identifier for each card). And then use this state variable to determine which form shows at any given time.
const [showForm, setShowForm] = React.useState("");
{cards.map(({ id, gamename, maxplayers, freespaces, tablenum }) => {
return (
<div key={id}>
// Rest of the JSX
<div className="editbuttons">
<button
type="button"
className="editbutton"
onClick={() => setShowForm(id)}
>
Edit
</button>
<button type="button" className="delbutton">
X
</button>
</div>
{showForm == id && (
<div>
<Editform
cards={cards}
setCards={setCards}
id={id}
/>
</div>
)}
</div>
</div>
);
})}

React: how to reference an external function?

I have onClick events in my html that calls a function to get the alt value of each list item (so getting the movie name). I want the next step to simply get the single value that the function got and insert it into a separate webpage. How would I go about doing it? I gave it a try but I'm still very new to react, so I'm aware this doesn't work.
My html:
<div class="now-showing">
<ul class="movie-items">
<li>
<img src="/images/movie-a.jpg" alt="Movie A">
<button>BOOK</button>
</li>
<li>
<img src="/images/movie-b.jpg" alt="Movie B">
<button>BOOK</button>
</li>
<li>
<img src="/images/movie-c.jpg" alt="Movie C">
<button>BOOK</button>
</li>
</ul>
</div>
Script:
// getting movie name for movie bookings
let filmTitle = [];
function getMovieName(e) {
let link = e.target;
let img = link.closest("li").querySelector("img");
let item = img.alt;
filmTitle.push(item);
};
// input movie title to schedule page
function AddTitle() {
return (
<div className="schedule-heading">
<h2>Step 1: Select screening for {filmTitle}</h2>
</div>
)
}
ReactDOM.render(<AddTitle />, document.querySelector('.screenings'));
So far on my second webpage, I'm only getting "Step 1: Select screening for". What changes or additions would I need to make?
there what's wrong with your code:
first: u can't make img alt that saved by function exist when page reloading. so use ReactRouter instead if u wants to move to another page without reloading.
and make everything in pure react.
otherwise the img alt need to be stored in cookie or your server side.
second: react won't rerender without calling useState hook. so for react filmTitle will always blank. or u can also store it in another library for managing react state data like redux or mobx.
note this code snippet doesnt run properly because stackoverflow blocked it.
so u need to run it in your own project.
const {
BrowserRouter, Routes, Route, Outlet,Link,useNavigate} = ReactRouterDOM
function MyMovie(props){
let navigate = useNavigate();
const [data,setData] = React.useState([
{imgsrc:"/images/movie-a.jpg",imgalt:"Movie A",href:"/bookticket"},
{imgsrc:"/images/movie-b.jpg",imgalt:"Movie B",href:"/bookticket"},
{imgsrc:"/images/movie-c.jpg",imgalt:"Movie C",href:"/bookticket"}])
const bookMovie=(v)=>{
props.setFilmTitle(v.imgalt)
navigate(v.href)
}
return <div className="now-showing">
<ul className="movie-items">
{data.map(v=>
<li key={v.imgalt}>
<img src={v.imgsrc} alt={v.imgalt}/>
<button onClick={()=>bookMovie(v)}>BOOK</button>
</li>)}
</ul>
</div>
}
// input movie title to schedule page
function AddTitle(props) {
return ( <div className = "schedule-heading" >
<h2 > Step 1: Select screening for {props.filmTitle} < /h2>
</div>
)
}
function Page(){
return <div>
Welcome!
<Outlet/>
</div>
}
function App(){
const [filmTitle,setFilmTitle] = React.useState([])
return <BrowserRouter>
<Routes>
<Route path="/" element={<Page />}>
<Route index element={ <MyMovie filmTitle={filmTitle} setFilmTitle={setFilmTitle}/>} />
<Route path="bookticket" element={ <AddTitle filmTitle={filmTitle}/>}/>
</Route>
</Routes>
</BrowserRouter>
}
ReactDOM.render( <App /> , document.querySelector('.screenings'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/18.2.0/umd/react.development.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/18.2.0/umd/react-dom.production.min.js"></script>
<script src="https://unpkg.com/history#5/umd/history.development.js" crossorigin></script>
<script src="https://unpkg.com/react-router#6/umd/react-router.development.js" crossorigin></script>
<script src="https://unpkg.com/react-router-dom#6/umd/react-router-dom.development.js" crossorigin></script>
<div class="screenings">
</div>

Javascript attribute - onClick and onClickOverThere for dropDown

I have reactJs app and I have made a custom dropDown with a div that I set an onClick attribute to open dropDown and close it.
but also I want to close it when the user clicks to another part of the site.
<div
onClick={() => setNoneQuote(noneQuote ? false : true)}
className="selected-drop-down"
>
<span className="dropDownText">{selectedQuoteCurrency}</span>
<img
className="dropDownIcon"
src={require("../assets/image/arrow/dropDown-Arrow.png")}
width="15px"
alt="arrow"
/>
</div>
I try onMouseDown instead of onClick according to this answer ==> stackoverflow ,but I don't know why it doesn't work for me :(
const dropdownElement= document.querySelector("choose an id or a specific class")
window.onclick = function(event) {
if (event.target !== dropdownElement) {
dropdownElement.style.display = "none"; // or any other function that you want to call
}
}
this may not be the exact code to fix your problem but you can use the logic
You could do it in different ways, but I will show you solution that I used in one of my projects.
So, I used <header> and <main> tags where I had all my Components. And in those tags i used eventListener with a callback function, like this:
<header onClick={handleClick}>
<Nav />
<Greeting />
<Sidebar />
</header>
and
<main onClick={handleClick}>
<Description />
<Icons />
<Prices />
<Gallery />
<NecessaryInfo />
<Location />
<GalleryModal />
<ConditionsModal />
</main>
In a callback function handleClick I checked where user could click and to do that I used next logic of pure JS:
!e.target.classList.contains("menu-open") &&
!e.target.classList.contains("menu-links") &&
!e.target.parentElement.classList.contains("menu-links") &&
!e.target.parentElement.parentElement.classList.contains("menu-links") &&
closeSidebar();
Function closeSidebar() is simple:
const closeSidebar = () => {
setIsSidebarOpen(false)};
In your code instead of using setNoneQuote(noneQuote ? false : true) you could also use: setNoneQuote(!noneQuote). Exclamation mark before value always will change it to the oposite.

How to render a component outside the component that contains the function that renders the first component?

The situation is a bit complicated:
inside a component called "LeftSectionHeader" I have a div, which when clicked must render a component;
the component to be rendered is called "ProfileMenu", and is basically a div that must be rendered on top of "LeftSectionHeader" itself and another div;
All these components are rendered inside another component called "Main".
The problem is that if I define the function inside "LeftSectionHeader", "ProfileMenu" will be rendered inside, while I need it to not only be rendered outside, but even cover it; that's why you'll see some boolean vars inside "Main", because that is the only way i could render it, but it still doesn't cover the other divs. I'll attach the code of each component and how the final result should look below.
LeftSctionHeader:
function LeftSectionHeader(){
return(
<div class="left-section-header">
<div class="crop" ><img src="./images/profiles/anonimous.png" /></div>
</div>
);
}
The div belonging to the "crop" class is the one that must be clicked to render "ProfileMenu"
ProfileMenu:
function ProfileMenu(){
return(
<div class="profile-side-menu">
//A lot of boring stuff
</div>
);
}
There are some functions related to this component, but they are not important, so I didn't put them, just ignore it
Main:
var p=true;
var m=true;
function Main(){
return(
<div class="main">
<Header />
<div class="left-section">
{m ? <div><LeftSectionHeader /><LangMenu /></div> : <ProfileMenu />}
</div>
{p ? <PostPage /> : <NoPostsMessage />} //Ignore this line
</div>
);
}
Before clicking on the orange div
After clicking
This might help as guidline, hopefully!
function LeftSectionHeader({ onClick }){
return(
<div class="left-section-header" onClick={onClick}>
<div class="crop" ><img src="./images/profiles/anonimous.png" /></div>
</div>
);
}
function Main(){
const [showProfile, setShowProfile] = useState(false);
return(
<div class="main">
<Header />
<div class="left-section">
{!showProfile ? (
<div>
<LeftSectionHeader onClick={() => setShowProfile(true)} />
<LangMenu />
</div>
) : <ProfileMenu />}
</div>
{p ? <PostPage /> : <NoPostsMessage />} //Ignore this line
</div>
);
}
The simplest solution might be to pass a handler into the header component to toggle the menu:
function App () {
const [showMenu, setShowMenu] = useState();
return (
<div>
<Header onMenuToggle={() => setShowMenu(!showMenu)} />
{ showMenu && <Menu /> }
</div>
)
}
function Header ({ onMenuToggle }) {
<div onClick={onMenuToggle}>...</div>
}
Caveat: This will cause the entire App component to re-render when the menu state changes. You can mitigate this by either
A) placing the menu state closer to where it's actually needed, like in the sidebar component instead of at the top, or
B) using a context or other orthogonal state store.
Another approach would be to leave the state handling in the LeftSectionHeader component and then use a React portal to render the menu elsewhere in the DOM.

React - Navigation that will smooth scroll to section of the page

So I'm trying to create a navigation for my single page app that will smooth scroll down to the section of the page.
I want to have my navigation at the top of the page with links that when a user clicks, will smooth scroll them down to the section of the page. I'd also like it so if the user goes directly to the link website.com/about for example, it will smooth scroll to the section the same way as if you clicked about on the navigation component.
I understand how the react-router-dom works for routing pages, but I'm confused on how to make it work for this particular purpose.
Could this be achieved with HashRouter?
Here's the code I currently have:
function Header() {
return (
<>
<Link to="/">Hero</Link>
<Link to="/">About</Link>
<Link to="/">Contact</Link>
</>
);
}
function Hero() {
return (
<section>
<h1>Hero Section</h1>
</section>
);
}
function About() {
return (
<section>
<h1>About Section</h1>
</section>
);
}
function Contact() {
return (
<section>
<h1>Contact Section</h1>
</section>
);
}
export default function App() {
return (
<div className="App">
<BrowserRouter>
<Header />
<Hero />
<About />
<Contact />
</BrowserRouter>
</div>
);
}
I'm also providing a CodeSandBox, forks are appretiated! :)
What you could do is use an anchor tag instead of Link from react-router-dom and have an id on the sections. when the anchor tag is clicked scroll to the corresponding section
<a
href="/"
onClick={e => {
let hero = document.getElementById("hero");
e.preventDefault(); // Stop Page Reloading
hero && hero.scrollIntoView();
}}
>
Hero
</a>
// Rest of Code
function Hero() {
return (
<section id="hero">
<h1>Hero Section</h1>
</section>
);
}
and to scroll to a section using a url path you would have to get the extract the path from url and scroll to the section that has that specific path as an id
useEffect(() => {
let url = window.location.href.split("/");
let target = url[url.length - 1].toLowerCase();
let element = document.getElementById(target);
element && element.scrollIntoView({ behavior: "smooth", block: "start"});
}, []);
CodeSandbox here
Hope This Helps
To allow the link to update in the address bar, use the answer from #Abdullah Abid but change the <a> tags to <link> and the href to to.
See my ammendment to Abdullah's Sandbox Here

Categories