React: how to reference an external function? - javascript

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>

Related

IFrame not found with Next/Link-pagination

I have a page with an embedded iframe script I am using with the Next-script tag.
script-page.tsx
import Script from 'next/script';
import Layout from '~/common/layout/Layout';
const ScriptPage = () => {
return (
<Layout>
<div className="m-0 md:m-12">
<div id="embedded-pressroom" data-publisher="212312" />
<Script
type="text/javascript"
src="my-link"
/>
</div>
</Layout>
);
};
export default ScriptPage;
It is showing the content if I go directly into the href, i.e localhost:3000/p/script-page/ but it is not showing if I go into localhost:3000/p/home-page/ and navigate back to the script page.
This is my navbar:
import Link from 'next/link';
const Navbar = () => {
return (
<div className="px-6 md:pr-0 navbar-content">
<h2>Navigation</h2>
<ul>
<li>
<Link href="/home-page">
<a>Home</a>
</Link>
</li>
<li>
<Link href="/script-page">
<a>Script</a>
</Link>
</li>
</ul>
</div>
);
};
export default Navbar;
What is happening, why do I have to refresh the script-page in order for the content to be shown when navigating internally with Next-Link. It shows if I directly go in to the script page, but if I do:
Script Page
Navigate to Home Page
Go back to Script Page
I get "[iFrameSizer][Host page: embedded-pressroom-iframe] [Window focus] IFrame(embedded-pressroom-iframe) not found" in the console.
What can I do to fix this?

How to replace a word with a link

I need to replace a word and insert a link instead.
An example of how I want to do:
const text = "Edit src/App.js and save to reload."
return (
<div className="App">
<header className="App-header">
<img src={logo} className="App-logo" alt="logo" />
<p>
{text.replace("Edit", <a
className="App-link"
href="https://reactjs.org"
target="_new"
rel="noopener noreferrer"
></a>)}
</p>
</header>
</div>
);
The JS function string.replace() expects two string parameters, but instead of the second string you supply a JSX <a> element.
Note that JSX is definitely not text, it may just look like it if you are not used to it. All JSX tags are translated into calls to React.createElement(), the result of which is a React object and not a string.
To solve it, you need to convert the string into parts, modify what needs modifying (while making sure that the React key requirement for items in a collection is fulfilled), then render the result, as in the following demo:
class Home extends React.Component {
render() {
const logo = null;
const text = "Edit src/App.js and save to reload."
// Create array:
const parts = text.split(" ");
// Modify array to contain JSX elements: one link, the rest Fragments, because every item
// in a collection needs a key to prevent React warnings.
// Also manually inserted back the spaces that went missing due to the split operation.
for (let i = 0; i < parts.length; i++) {
if (parts[i] === "Edit")
parts[i] = <a key={i} className="App-link" href="https://reactjs.org" target="_new" rel="noopener noreferrer">Edit</a>;
else
parts[i] = <React.Fragment key={i}>{" " + parts[i]}</React.Fragment>;
}
return (
<div className="App">
<header className="App-header">
<img src={logo} className="App-logo" alt="logo" />
<p>
{parts}
</p>
</header>
</div>
);
}
}
ReactDOM.render(<Home />, document.getElementById('react'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<div id="react"></div>

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.

Input field value working in one area and not in another

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.

Each child in a list should have a unique key prop problem

Good day,
i'm experiencing a slight problem. i want to have productdetails page where all the info is being displayed of the product. You can do that through clicking on the product link. but i'm getting the following err: Each child in a list should have a unique key prop problem
code homepage:
import React from 'react';
import data from '../data'
import { Link } from 'react-router-dom';
function Homescreen (props){
return <ul className="products" >
{ data.products.map(product =>
<li key={product}>
<div className="product-card">
<Link to={'/product/' + product._id}>
<img className="product-image" src={product.image} alt="product"></img>
</Link>
<Link to={'/product/' + product._id}><h1 className="product-name">{product.name}</h1></Link>
<p className="product-price">€{product.price}</p>
<div className="product.rating">Stars ({product.numReviews})</div>
<form method="post">
<button type="submit">Add to cart</button>
</form>
</div>
</li>
)
}
</ul>
}
export default Homescreen;
code productpage:
import React from 'react';
import data from '../data';
import { Link } from 'react-router-dom';
function productScreen (props){
console.log(props.match.params.id)
const product = data.products.find(x=> x._id === props.match.params.id)
return <div>
<h1>product</h1>
<div>
<Link to="/">Back to products</Link>
</div>
<div className="productdetails">
<div className="details-image">
<img src={product.image} alt="product"></img>
</div>
<h1>{product.name}</h1>
<h4>{product.price}</h4>
<span>{product.rating} Stars ({product.numReviews} Reviews)</span>
</div>
</div>
}
export default productScreen;
As stated on the ReactJS Website
A “key” is a special string attribute you need to include when creating lists of elements.
Therefore, a key can not be an object like the way you are using it here and will need to be a String. If your product does not have a unique identifier, then you can choose to use the index of your map method.
When you don’t have stable IDs for rendered items, you may use the item index as a key as a last resort:
// Example from the ReactjS Website
const todoItems = todos.map((todo, index) =>
// Only do this if items have no stable IDs
<li key={index}>
{todo.text}
</li>
);
You should change:
<li key={product}>
To (assuming your id is unique)
<li key={product._id}>
The best way to pick a key is to use a string that uniquely identifies a list item among its siblings, you can check more here

Categories