I have a JS class called "Cell.js" and I want to redirect to another JS Page called "Detail.js" when the user clicked on a button. But I do not know how to redirect AND pass a variable at the same time.
I am working on a Pokedex (Pokemon List) and when the user clicks on the f.e. first Pokemon which has ID = 1, the ID should get passed to the Detail.js page where it shows more details of the selected Pokemon.
Cell.js code =
import React from 'react';
import './Cell.css';
import {ClassDeclaration as pokemon} from "#babel/types";
function Cell({ pokemon }) {
let id = pokemon.name;
return (
<a href={"Detail.js?id= " + id } onclick="passID()">
<div className="Cell">
<div className="Cell_img">
<img src={pokemon.sprites.front_default} alt="" />
</div>
<div className="Cell_name">
{pokemon.name}
</div>
</div>
</a>
);
}
function passID(){
return(
pokemon.id
);
}
export default Cell;
And here is the target JS page "Detail.js":
import React, {useState} from 'react';
import './Detail.css';
const queryString = window.location.search;
console.log(queryString);
const urlParams = new URLSearchParams(queryString);
const id = urlParams.get('id');
Detail(id)
function Detail(pokemon) {
return (
<div className="Detail">
<div className="Detail_img">
<p>TEST</p>
</div>
<div className="Detail_name">
{pokemon.name}
</div>
</div>
);
}
async function getPoke(id) {
console.log(id);
const res = await fetch(`https://pokeapi.co/api/v2/pokemon/${id}`);
const json = await res.json();
console.log(json);
}
export default Detail;
Detail.js is not done yet, I could test anything cuz I didn't know how to redirect and send the variable. Hope you can help me out
PS: I am very new to JS xD
Navigation in React will not happen that way!. React is intended for Single Page Application(SPA). ReactDOM.render() will load the container in which different views can be switched.
Recommended is react-router, but you could do this way for experiments.
You can pass any info as props to view component (in your case, id)
import React, { useState } from "react";
const App = () => {
const [pageNo, setPageNo] = useState(1);
let id = "Xyz"
return (
<div>
<header>
<span onClick={() => setPageNo(1)}>View1</span>
<span onClick={() => setPageNo(2)}>View2</span>
<span onClick={() => setPageNo(3)}>View3</span>
</header>
{loadView(pageNo, id)}
</div>
);
};
const loadView = (pageNo, id) => {
switch (pageNo) {
case 1:
return <View1 id={id}/>;
case 2:
return <View2 id={id}/>;
case 3:
return <View3 id={id}/>;
}
};
const View1 = ({id}) => <div>View 1 pokeman name = {id}</div>;
const View2 = ({id}) => <div>View 2 pokeman name = {id}</div>;
const View3 = ({id}) => <div>View 3 pokeman name = {id}</div>;
CSS
header {
border-bottom: 1px solid #c4c4c4;
}
header span {
margin: 8px;
cursor: pointer;
text-decoration: underline;
}
Related
So I have these 2 components:
First One MAIN PAGE
`
import {useEffect, useState} from 'react';
import Navbar from "./navbar";
import Modal from "./Modal";
import '../styles/home.css'
import FavoriteCrypto from "./favoriteCrypto";
export default function MainPage() {
const[data, setData] = useState([])
const[input, setInput] = useState("");
const [openModal, setOpenModal] = useState(false)
const [modalArr, setModalArr] = useState([])
const[favorites, setFavorites] = useState([])
const url = "https://api.coingecko.com/api/v3/coins/markets?vs_currency=usd&order=market_cap_desc&per_page=100&page=1&sparkline=false"
useEffect(()=>{
fetch(url)
.then((res)=>{
return res.json()
})
.then((data)=>{
setData(data)
})
},[])
let searchBar = data.filter((e)=>{
return e.id.toLowerCase().includes(input.toLowerCase())
})
// add to favorite
function addToFav(id){
if(!favorites.includes(id)){
setFavorites(favorites.concat(id))
}
}
function openModalFunc(id) {
setOpenModal(true);
if(!modalArr.includes(id)) {
setModalArr(modalArr.concat(id))
}
}
function closeModalFunc(id) {
setOpenModal(false);
setModalArr([]);
}
let modalRender = data.filter(data => modalArr.includes(data.id));
let favoriteRender = data.filter(data => favorites.includes(data.id))
console.log(favoriteRender)
return(
<div>
<Navbar input={input} setInput={setInput}/>
<div className='general-info'>
<h4>Coin</h4>
<h4 className='p'>Price</h4>
<h4 className='c'>Change</h4>
<h4 className='mc'>Market Cap</h4>
<h4 className='s'>Supply</h4>
</div>
<Modal addFavorite = {addToFav} modalArr={modalRender} close = {closeModalFunc} open = {openModal}/>
{searchBar.map((e)=>(
<div
onClick={()=>{
openModalFunc(e.id);
}}
className='all_coins_wrapper'>
<div className='coins-wrapper'>
<div className='coins-label'>
<img src={e.image} alt=""/>
<div className='general_info'>
<div>{e.name}</div>
<div>{e.symbol.toUpperCase()}</div>
</div>
</div>
<p className='price-main'>${e.current_price}</p>
</div>
<div className='left-part'>
<p className='change'>{e.price_change_percentage_24h}</p>
<div className='marcap'>{e.market_cap}</div>
<div className='circ'>{e.circulating_supply}</div>
</div>
</div>
)
)}
</div>
)
}
SECOND ONE :
`
import React from "react";
import Navbar from "./navbar";
import MainPage from "./home";
export default function FavoriteCrypto({favorite}){
return(
<div>
</div>
)
}
I want to import these variable '
let favoriteRender = data.filter(data => favorites.includes(data.id))
from the first component to the second one in order to display on the second page the favoirite coins'
I tried to copy paste the code from the first component to the second component and to import the variable, but that didnt work. I am using react for a week now.So sorry if this question is already ask.But I cant solve this issue.
You don't need to export that variable in order to pass data between components. You can use props in-order to do so.
Here is the link to the docs.
And here is an example of doing so:
// COMPONENT
const MyNameComponent = (props) => <h1>{props.name}</h1>;
// USAGE
const App = () => {
const name = "John Doe";
return <MyNameComponent name={name} />
}
As a solution to your problem could be:
<FavoriteCrypto favourite={favouriteRender} />
and using it inside the component to display it. You can align the data according to your wish. Read the docs for more info 👍.
import React, { useState, useEffect } from 'react'
import { Link } from 'react-router-dom'
import moment from 'moment'
import Avatar from '../../components/Avatar/Avatar'
import { useSelector, useDispatch} from 'react-redux'
import { useParams } from 'react-router-dom'
import { deleteAnswer } from '../../actions/question'
const DisplayAnswer = ( { question, handleShare } ) => {
const User = useSelector((state) => (state.currentUserReducer))
const dispatch = useDispatch()
const { id } = useParams()
const [button, setButton] = useState(false);
const handleDelete = (answerId, noOfAnswers) => {
dispatch(deleteAnswer(id, answerId, noOfAnswers-1))
}
const handleComment = (e) => {
setButton(!button)
alert(e.target.id)
}
return (
<div>
{
question.answer.map( (ans) => (
<div className="display-ans" key={ans._id}>
<p>{ans.answerBody}</p>
<div className="question-actions-user">
<div>
<button type="button" onClick={handleShare}>Share</button>
{
User?.result?._id === ans?.userId && (
<button type='button' onClick={ () => handleDelete(ans._id, question.noOfAnswers) }>Delete</button>
)
}
<div>
</div>
<button id = {ans._id} type='button' onClick = { (e) => handleComment(e) }> Add Comment </button>
{
button &&
(
<div id = {ans._id}>
<textarea rows='5' cols='30'> </textarea> <br />
<button type='button'> Post </button>
</div>
)
}
</div>
<div>
<p>answered { moment(ans.answeredOn).fromNow()}</p>
<Link to={`/Users/${ans.userId}`} className='user-link' style={{color:'#0086d8'}}>
<Avatar backgroundColor="lightgreen" px='8px' py='5px' borderRadius='4px'>{ans.userAnswered.charAt(0).toUpperCase()}</Avatar>
<div>
{ans.userAnswered}
</div>
</Link>
</div>
</div>
</div>
))
}
</div>
)
}
export default DisplayAnswer
I want to add a comment part under every answer
to do that i added a " Add Comment " button under every Answer and i have a button click on that button
and what i want is whenever the button is clicked the addcomment (textbox) should be added under it
but when i click the button the addcomment (textbox) is getting added under every answer
like if 10 answers are their then Addcommment box is getting added under every 10 answers
Currently there is only a single button state that all the mapped answers render a button for. A simple solution would be to instead store the answer id of the answer you want to add a comment for.
Example:
const DisplayAnswer = ({ question, handleShare }) => {
...
const [commentId, setCommentId] = useState(null); // <-- initially null
...
const handleComment = (e) => {
setCommentId(e.target.id); // <-- set answer id here
alert(e.target.id);
};
return (
<div>
{question.answer.map((ans) => (
<div className="display-ans" key={ans._id}>
<p>{ans.answerBody}</p>
<div className="question-actions-user">
<div>
...
<button
id={ans._id}
type="button"
onClick={handleComment}
>
Add Comment
</button>
{commentId === and._id && ( // <-- conditionally render match by id
<div id={ans._id}>
<textarea rows="5" cols="30" />
<br />
<button type="button">Post</button>
</div>
)}
</div>
...
</div>
</div>
))}
</div>
);
};
When the "Post comment" button is clicked and the entered comment is handled don't forget to also set the commentId value back to null to conditionally hide the input.
Each answer must have his own 'state' to display his own textArea, so you have to extract the code of the 'answer' in a new Answer component, and render a new component in the map method.
Each Answer will thus use a "useState" with a "[isTextAreaVisible, setIsTextAreaVisible] = useState(false);" state.
I'm trying to remove a CSS class from a specific item when clicking on that item's button. Removing the CSS class will make a menu appear. How would I go about doing this with React? Here's the code.
import "./Homepage.css"
import React, { useState, useEffect, useRef } from "react"
// import { FontAwesomeIcon } from "#fortawesome/react-fontawesome"
// import { faArrowDown } from "#fortawesome/free-solid-svg-icons"
import { Link } from "react-router-dom"
import useFetch from "./useFetch"
import Axios from "axios"
export default function Homepage() {
const [body, setBody] = useState("")
const [sortedData, setSortedData] = useState("")
const [data, setData] = useState("")
const [css, setCss] = useState("")
const [flash, setFlash] = useState(null)
const posts = useFetch("http://localhost:5000/api/data")
const firstRender = useRef(true)
useEffect(() => {
let test = JSON.parse(window.localStorage.getItem("user"))
console.log(test)
setData(posts)
}, [posts])
useEffect(() => {
if (firstRender.current) {
firstRender.current = false
return
}
data.sort(function (a, b) {
return new Date(b.date) - new Date(a.date)
})
setSortedData(data)
}, [data])
const handleSubmit = (e) => {
e.preventDefault()
Axios.post("http://localhost:5000/api/react-create-post", { text: body }, { withCredentials: true })
.then((res) => {
setSortedData((prevArray) => [res.data.post, ...prevArray])
setFlash("Successfully created post.")
setCss("success-msg")
setBody("")
})
.catch((err) => {
setCss("error-msg")
setFlash("Field cannot be left blank.")
})
}
const handleClick = (e) => {
e.preventDefault()
e.target.parentElement.children[1]
}
return (
<div>
<center>
<div className="create-container">
<div className="posts-title">Create Post</div>
<form id="theForm" onSubmit={(e) => handleSubmit(e)}>
<textarea onChange={(e) => setBody(e.target.value)} value={`${body}`} id="theInput" className="post-input" name="text" type="text"></textarea>
<button className="submit-btn">POST</button>
</form>
</div>
<div id="postsContainer" className="posts-container">
<div className="posts-title">Latest Posts</div>
{flash ? <div className={css}>{flash}</div> : console.log()}
<div id="postInput">
{sortedData &&
sortedData.map((item) => {
return (
<div className="post-container" key={item._id}>
<Link className="a" to={`/user/${item.author}`}>
<h3 className="author">{item.author}</h3>
</Link>
<div className="date">{item.date.toLocaleString()}</div>
<div className="options-cont">
<button onClick={(e) => handleClick(e)} id="optionsBtn" className="options-btn">
<i className="fas fa-ellipsis-v"></i>
</button>
<button data-author={`${item.author}`} data-id={`${item._id}`} data-text={`${item.body}`} id="editBtn" className="edit inside-btn invisible">
Edit
</button>
<button data-author={`${item.author}`} data-id={`${item._id}`} id="deleteBtn" className="delete inside-btn invisible">
Delete
</button>
<br></br>
</div>
<p className="body-text">{item.body}</p>
</div>
)
})}
</div>
</div>
</center>
</div>
)
}
As far as I'm concerned using state as the className would remove or alter the CSS of each item in the "sortedData" array and make the menus for all items appear. I only want the menu for one of the items to appear.
As pilchard said, you probably want to make each of those its own component with its own "showing" state, or at least "showing" prop.
As far as I'm concerned using state as the className would remove or alter the CSS of each item in the "sortedData" array and make the menus for all items appear. I only want the menu for one of the items to appear.
That would be true if you used a single flag in state. But instead, use a set of flags, one flag for each menu, perhaps keyed by item._id.
Assuming you don't do the refactoring pilchard (and I) suggest:
You haven't shown us enough code for me to know whether you're using class components or function components, so I'm going to guess function components with hooks. If so, the initial state would be:
const [showing, setShowing] = useState(new Set());
Then when rendering, you'd assign the class:
<theElement className={showing.has(item._id) ? "class-if-any-to-show-it" : "class-if-any-to-not-show-it" ...
To toggle, in the button pass the ID:
<button onClick={(e) => handleClick(e, item._id)}
and then update state as appropriate:
const handleClick = (e, id) => {
e.preventDefault()
setShowing(showing => {
showing = new Set(showing);
if (showing.has(id)) {
showing.delete(id);
} else {
showing.add(id);
}
return showing;
});
};
Hi I have mapped some json data named "projectsData" and I am trying to "bind" an onClick event with a setState hook. The mapping works except for the "onClick" does not work when clicking the grid item. In my case I want to update filterproject value with the project.id value from that target.
Right now when I click an item it does nothing.
How do I successfully map a function to "onClick" while using functional components?
Below is the parent Component
import React, { useEffect, useState } from "react";
import projectsData from '../data/projectsData';
import Project from './Projects';
const App = (props) => {
const [projects] = useState(() => (projectsData.map((project) => <Project id={project.id} project={project} onClick={() => {setFilterProject(project.id)}}/>)));
const [filterproject, setFilterProject] = useState(null);
return (
<body>
<div id='sepLine'>
<div id="visHolder">
<div id="visContainer" style={{position: "relative", width: "840px", height: "1823px"}} >
{projects}
</div>
</div>
</div>
</body>
);
}
export default App;
And here is the Child Component - "Project"
import React, { useRef } from "react";
const Project = (props) => {
const {projectClick, project} = props;
return (
<div className={`lineDiv gridItem y${project.start}-${project.end} ${project.kind}`} style={{positon: "absolute"}} onClick={projectClick}>
<h5>{project.title}</h5>
<br></br>
<p className="year">
<span className="yearsstart">{project.start}</span> - <span className="yearsend">{project.end}</span>
<br></br>
<span className="kind">{project.kind}</span>
</p>
</div>
)
}
export default Project
below is a screen grab of Console showing one of the mapped projects and it's onClick parameters. I can see it but when I click nothing happens. Any help would be great!
You pass click handler to a prop called onClick when setting initial state
const [projects] = useState(() => projectsData.map((project) => (
<Project
id={project.id}
project={project}
onClick={() => {setFilterProject(project.id)}}
/>
));
but access it as projectClick in the component
const { projectClick, project } = props;
...
<div
className={`lineDiv gridItem y${project.start}-${project.end} ${project.kind}`}
style={{positon: "absolute"}}
onClick={projectClick}
>
...
</div>
Fix by accessing the correct prop
const { onClick, project } = props;
...
<div
className={`lineDiv gridItem y${project.start}-${project.end} ${project.kind}`}
style={{positon: "absolute"}}
onClick={onClick}
>
...
</div>
Lets say I have component "Post" which holds multiple components "Comment". I want to make that application scrolls down on comment with that anchor when I enter URL like this:
/post/:postId/#commentId
I have already working postId route /post/:postId
I tried to implement it with react-hash-link npm package but it's not working as intended.
Every comment has it's own ID which is set on component, like this:
<div class="post">
<div class="post-header">
<div class="post-header-avatar">
SOME TEXT
</div>
<div class="post-header-info">
SOME TEXT
</div>
</div>
<div class="post-content">
<span>POST CONTENT</span>
</div>
<div class="post-likers-container">
<div class="post-likers-header label">People who like this post</div>
<div class="post-likers">
SOME TEXT
</div>
</div>
<div class="post-comments">
<div class="comments ">
<div class="comments-all label">Comments</div>
<div class="comments">
<div class="comment" id="5d27759edd51be1858f6b6f2">
<div class="comment-content">
COMMENT 1 TEXT
</div>
</div>
<div class="comment" id="5d2775b2dd51be1858f6b720">
<div class="comment-content">
COMMENT 2 TEXT
</div>
</div>
<div class="comment" id="5d2775ecdd51be1858f6b753">
<div class="comment-content">
COMMENT 3 TEXT
</div>
</div>
</div>
</div>
</div>
</div>
So for example if I open URL like:
/post/postId/#5d2775ecdd51be1858f6b753
I want to open page of post and that it scrolls down to the comment with # anchor.
Is there any way to implement this?
I managed to find simple solution for my use case, without creating refs for comments, passing them, etc. Since my hierarchy of components is like this:
Post --> render component Comments
Comments --> render
multiple components Comment with props data passed from Post
In Post component I created function:
scrollToComment= () => {
let currentLocation = window.location.href;
const hasCommentAnchor = currentLocation.includes("/#");
if (hasCommentAnchor) {
const anchorCommentId = `${currentLocation.substring(currentLocation.indexOf("#") + 1)}`;
const anchorComment = document.getElementById(anchorCommentId);
if(anchorComment){
anchorComment.scrollIntoView({ behavior: "smooth" });
}
}
}
Then I render Comments like this:
<Comments limit={limit} post={post} scrollToComment={this.scrollToComment} />
In Comments I generate comments after some sorting like this:
{sortedComments.map((comment, i) => <Comment key={i} {...comment} scrollToComment={this.props.scrollToComment}/> )}
and finally in Comment component I execute scrollToComment in ComponentDidMount():
if(this.props.scrollToComment)
this.props.scrollToComment(this.props._id);
After that when I go to some URL I get nice smooth scrolling to the comment specified in hash part of URL.
I tried #Christopher solution but it didn't worked for me.
I really liked your solution #SaltyTeemooo. Inspired by it I found an even simpler way without any callbacks.
My setup is very similar so lets say I am dealing with posts and comments.
In Post I create the Comments (simpified) like this and pass the anchorId:
<Comments anchorId={window.location.href.slice(window.location.href.indexOf("#") + 1)} props... />
In Comments I pass the anchor id along into Comment.js
<Comment anchorId={props.anchorId} props.../>
And then in the Comment, I scroll the current element into view, if it is the linked one
import React, { useRef, useEffect } from 'react';
function Comment () {
const comment = useRef(null); //to be able to access the current one
useEffect(() => {
if(props.anchorId === props.commentData.id)
{
comment.current.scrollIntoView({ behavior: "smooth" });
}
}, []) //same as ComponentDidMount
return(
<div id={props.commentData.id} ref={comment}> //here is where the ref gets set
...
</div>
)
}
Took a pretty solid amount of time but try this sandbox: https://codesandbox.io/s/scrollintoview-with-refs-and-redux-b881s
This will give you a ton of insight on how to scroll to an element using a URL param.
import React from "react";
import { connect } from "react-redux";
import { getPost } from "./postActions";
class Post extends React.Component {
constructor(props) {
super(props);
this.state = {
activeComment: null
};
this._nodes = new Map();
}
componentDidMount() {
this.props.getPost(this.props.match.params.id);
const path = window.location.href;
const commentId = path.slice(path.indexOf("#") + 1);
if (commentId) {
this.setState({
activeComment: commentId
});
}
}
componentDidUpdate(prevProps, prevState) {
if (this.state.activeComment !== prevState.activeComment) {
this.scrollToComment();
}
}
scrollToComment = () => {
const { activeComment } = this.state;
const { comments } = this.props.posts.post;
const nodes = [];
//Array.from creates a new shallow-copy of an array from an array-like or iterable object
Array.from(this._nodes.values()) //this._nodes.values() returns an iterable-object populated with the Map object values
.filter(node => node != null)
.forEach(node => {
nodes.push(node);
});
const commentIndex = comments.findIndex(
comment => comment.id == activeComment
);
if (nodes[commentIndex]) {
window.scrollTo({
behavior: "smooth",
top: nodes[commentIndex].offsetTop
});
}
};
createCommentList = () => {
const { post } = this.props.posts;
const { activeComment } = this.state;
if (post) {
return post.comments.map((comment, index) => {
return (
<div
key={comment.id}
className={
"comment " + (activeComment == comment.id ? "activeComment" : "")
}
ref={c => this._nodes.set(comment.id, c)}
>
{comment.text}
</div>
);
});
}
};
displayPost = () => {
const { post } = this.props.posts;
if (post) {
return (
<div className="post">
<h4>{post.title}</h4>
<p>{post.text}</p>
</div>
);
}
};
render() {
return (
<div>
<div>{this.displayPost()}</div>
<div>{this.createCommentList()}</div>
</div>
);
}
}
const mapStateToProps = state => {
return {
posts: state.posts
};
};
const mapDispatchToProps = dispatch => {
return {
getPost: postId => {
dispatch(getPost(postId));
}
};
};
export default connect(
mapStateToProps,
mapDispatchToProps
)(Post);
In my simple case where there is no async content loading, I got the desired scrolling behavior by just adding this at the top of the page:
useEffect(() => {
const href = window.location.href
if (href.includes("#")) {
const id = `${href.substring(href.indexOf("#") + 1)}`
const anchor = document.getElementById(id)
if(anchor){
anchor.scrollIntoView({ behavior: "smooth" })
}
}
}, [])
FYI, this was for some FAQ pages consisting of a bunch of FaqEntry objects, each with a question and answer. The code below allows linking to individual entries that initialize with the answer open.
export default function FaqEntry({title, history, children}) {
if(!history) console.log("OOPS, you forgot to pass history prop", title)
const createName = title => title.toLowerCase().replace(/[^\sa-z]/g, "").replace(/\s\s*/g, "_")
const id = createName(title)
const href = window.location.href
const isCurrent = href.includes("#") && href.substring(href.indexOf("#") + 1) === id
const [open, setOpen] = useState(isCurrent)
function handleClick() {
setOpen(!open)
if (history && !open) {
const pathname = window.location.pathname + "#" + id
history.replace(pathname)
}
}
return <div id={id} className={`faqEntry ${open ? "open" : "closed"}`}>
<div className="question" onClick={handleClick}>{title}</div>
<div className="answer">{children}</div>
</div>
}
I pass the history object from React Router so that I can update the browser history without triggering a page reload.
Mensure...
import React, { useEffect } from 'react';
const MainApp = () => {
const MyRef = React.createRef();
useEffect(() => { // Same like ComponentDidMount().
scrollTo();
})
const scrollTo = () => {
window.scrollTo({
top:myRef.offsetTop,
behavior: "smooth" // smooth scroll.
});
}
return (
<div ref={MyRef}>My DIV to scroll to.</div>
)
}