React Router Access Object Details - javascript

I'm trying to display the details of a clickable object on a new page. I've tried a few examples from React Router Pass Param to Component with limited success.
The only one that "kind of" worked was Alexander Luna's suggestion to access via ID in component. However, while this returns the id number, I can't access any other values, like "title".
I have tried globalStore, however, the error message told me that it isn't defined. I'm not sure that's my best option or not.
Ultimately I want the whole object back as I plan to use context with See 'D' below.
App) I have commented out my previous attempts
class App extends Component {
render() {
return (
<React.Fragment>
<Navbar />
<Switch>
<Route exact path="/" component={ProductList} />
<Route path="/cart" component={Cart} />
<Route exact path="/details/:id" component={Details} />
{/* <Route exact path="/details/:id" render={(props) => <Details globalStore={globalStore}
{...props} /> } /> */}
{/* <Route exact path="/details/:id" render={(props)=>{ <Details id={props.match.params.id}/>}}
/> */}
<Route component={Default} />
Details page I want to render in.
import React, { Component } from "react";
export default class Details extends Component {
render() {
return(
<div>
<h2>{this.props.match.params.id}</h2>
<h2>{this.props.match.params.title}</h2>
</div>
The product page, I'm using this link to click through to details.
xport default class Product extends Component {
render() {
const { id, title, img, price, inCart } = this.props.product;
return (
<ProductWrapper className="col-9 mx-auto col-md-6 col-lg-3 my-3">
<div className="card">
<div className="img-container" onClick={() => console.log('you clicked container')}
>
<Link to={`/details/${ this.props.product.id }`} >
<img src={img} alt="product" className="card-img-top" />
</Link>
D - This is how the original code looked, I want to use the {title} tags but I don't know if I need "value => " etc.
<ProductConsumer>
{value => {
const {
id,
company,
img,
info,
price,
title,
size,
} = value.Product;
return (
<div className="container py-5">
{/* title */}
<div className="row">
<div className="col-10 mx-auto text-center text-slanted text-blue my-5">
<h1>{title}</h1>
</div>
</div>

You need an extra parameter
<Route exact path="/details/:id/:title" component={Details} />
export default class Details extends Component {
render() {
return(
<div>
<h2>{this.props.match.params.id}</h2>
<h2>{this.props.match.params.title}</h2>
</div>
);
}
}
// In Product component
<Link to={`/details/${ this.props.product.id }/${this.props.product.title}`} >
<img src={img} alt="product" className="card-img-top" />
</Link>

Try to read the params in the constructor like this:
constructor(props)
{
super(props)
const { match: { params } } = this.props;
var id = params.id
this.state = {
id : id,
}
}
and then read the id from the state.
If you want to pass the whole object you can send it through the url in base64 like this :
<Link to={`/details/+btoa( this.props.product )} >
<img src={img} alt="product" className="card-img-top" />
</Link>
And recieving it in the constructor like the previous snippet en parse it to string with the function atob() and then to json.

Related

Cannot render data from API being passed from parent to child (ReactJS)

I just tried to follow this question with no results for me.
The point is that I'm trying to get a selected pokemon from a component and pass down to another component. The data is correctly passed, some data are displayed, but with other (for example, the sprites) I got an error. I'm using Context by the way.
PokemonContext.js
import React, { useState } from 'react';
const Context = React.createContext({});
export function PokemonsContextProvider({ children }) {
const [favorites, setFavorites] = useState([]);
return <Context.Provider value={{ favorites, setFavorites }}>{children}</Context.Provider>;
}
export default Context;
App.js
<PokemonsContextProvider>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/pokemons" element={<PokemonsList />} />
<Route path="/favorites" element={<Favorite />} />
<Route path="/pokemon/:name" element={<PokemonDetails />} />
<Route path="*" element={<NotFound />} />
</Routes>
</PokemonsContextProvider>
PokemonsDetails.js
const PokemonDetails = () => {
const { name } = useParams();
const [pokemon, setPokemon] = useState('');
useEffect(() => {
pokemonService.pokemonDetail(name).then((res) => {
setPokemon(res);
});
}, []);
return (
<div>
<h1 className="text-center mb-4">Details for {name.toUpperCase()}</h1>
<PokemonCard pokemon={pokemon} />
<div className="text-center">
<Link className="btn btn-success mx-2 my-4" to="/favorites">
Go back
</Link>
</div>
</div>
);
};
PokemonCard.js
const PokemonCard = ({ pokemon }) => {
return (
<div className="card text-center bg-primary">
<div className="card-body">
<h5 className="card-title">{pokemon.name}</h5>
</div>
<ul className="list-group list-group-flush">
<li className="list-group-item">Weight: {pokemon.weight}</li>
<li className="list-group-item">Height: {pokemon.height}</li>
</ul>
</div>
);
};
export default PokemonCard;
When I add this line <img className="card-img-top" src={pokemon.sprites.front_default} alt="Card image cap" /> to the PokemonCard component I got this error:
The data is correctly displayed when I console log it, so, I don't know why I'm having this error. If I inspect the React Dev Tools the data is in the component!
The PokemonCard.js component is being rendered before the result of the request.
You can resolve this problem checking if there is a pokemon before use his props.
const PokemonCard = ({ pokemon }) => {
return pokemon && (
<div className="card text-center bg-primary">
<img className="card-img-top" src={pokemon.sprites?.front_default} alt="Card image cap" />
<div className="card-body">
<h5 className="card-title">{pokemon.name}</h5>
</div>
<ul className="list-group list-group-flush">
<li className="list-group-item">Weight: {pokemon.weight}</li>
<li className="list-group-item">Height: {pokemon.height}</li>
</ul>
</div>
);
};
export default PokemonCard;

How to change props of header dynamically on every page in React?

I am very new to React, I have got a multipage app with a header bar component that is reused on every page.
class Header extends Component {
render() {
if (!this.props.authenticated) {
return null;
}
return (
<header className="topHeader">
<h1>{this.props.title}</h1>
</header>
);
}
}
export default Header;
I then pass this to App.js (with the title hard coded as "hi" for now):
...return (
<div className="masterContainer">
<Router>
<Header authenticated={this.state.authenticated} title="hi" />
<Switch>
<Route
exact
path="/login"
component={() => (
<LoginPage
updateUser={() => this.loadCurrentlyLoggedInUser()}
/>
)}
/> ...
In the page component itself, I have a blank page for now:
class Messages extends Component {
render() {
return (
<div className="messages">
<h2>Test page - Messages</h2>
<br />
<h2>This is a private page</h2>
<p>
You could only access this page after logged in.
<br />
Changes to the token or user logged out will redirect the user to the
login page.
</p>
<br />
</div>
);
}
}
export default Messages;
Now, I want to set a title on the blank page (as title="Messages") but I only know how to pass one prop to another. But I do not know how to have the prop from the blank page, to App.js, then back to the Header component.
Any tips on how I could do that?
You can pass a method handle to your page component as props. And you Page component can callback the handle component.
There are other advanced ways also like Context API, Redux etc.
App.js
state = { title:'' };
setHeaderTitle = (title) => {
this.setState({ title });
}
...return (
<div className="masterContainer">
<Router>
<Header authenticated={this.state.authenticated} title={this.state.title} />
<Switch>
<Route
exact
path="/login"
component={() => (
<LoginPage setHeaderTitle={this.setHeaderTitle}
updateUser={() => this.loadCurrentlyLoggedInUser()}
/>
)}
/> ...
Page component
class Messages extends Component {
componentDidMount(){
this.props.setHeaderTitle("Page Title");
}
render() {
return (
<div className="messages">
<h2>Test page - Messages</h2>
<br />
<h2>This is a private page</h2>
<p>
You could only access this page after logged in.
<br />
Changes to the token or user logged out will redirect the user to the
login page.
</p>
<br />
</div>
);
}
}
export default Messages;

Rendering nested router view without parent router view - React.js

Hello community :) My first Q here.
(There were couple of similar questions but they didn't answer my particular code issue and I tried them to apply but without success.)
So I would like to render the child component in nested route without the parent one showing in the view
See the picture at the end --->
import React from 'react';
import {BrowserRouter, Route, Switch, Link} from "react-router-dom";
import 'bootstrap/dist/css/bootstrap.min.css';
import Routing from "./components/Routings";
export default class Browserrouting extends React.Component {
render () {
return (
<BrowserRouter>
<Routing/>
</BrowserRouter>
)
}
}
Here is the Routing component :
import About from "../views/About";
import HomeBackground from "../views/Background";
import ShelterApp from '../views/ShelterApp';
export default (props) => (
<div className="flexColumn">
<div> <ul className="flexRow center">
<li className="homeLink"><Link className="colorBrown" to="/">Home</Link></li>
<li className="homeLink"><Link className="colorBrown" to="/shelterApp">Shelter App</Link></li>
<li className="homeLink"><Link className="colorBrown" to="/about">About our site and shelter</Link></li>
</ul></div>
<Switch>
<Route exact path="/" component={() => <HomeBackground />} />
<Route path="/about" component={() => <About />} />
<Route path="/shelterApp" component={() => <ShelterApp />} />
</Switch>
</div>
)
And in ShelterApp component I have some text and imported another component which contains the links and nested routes I would like to display without showing the parent component ShelterApp:
class ShelterApp extends React.Component {
render() {
return (
<div className="flex center">
<div className="card center" style={{ "width": "25em", "height":"25em" }}>
<div className="card-body textCenter">
<h5 className="card-title paddingTitle">Welcome to our site</h5>
<h6 className="card-subtitle mb-2 text-muted"> Login or register if it's your first time</h6>
</div>
<LoginRouting match={this.props.match} />
</div>
</div>)
}
}
export default ShelterApp;
and the final child componet with the "lowest" routes in hierarchy :
class LoginRouting extends React.Component {
constructor(props) {
super(props)
this.state = {
users: []
}
}
static propTypes = {
match: PropTypes.object.isRequired,
location: PropTypes.object.isRequired,
history: PropTypes.object.isRequired
};
render() {
const { match, location, history } = this.props;
return (
<div >
<div className="flexRow center">
<Button className={" loginRouting"} type={"button"} bootstrapClass={"btn btn-light"} child={<Link to="/shelterApp/login">Login form</Link>} />
<Button className={" loginRouting"} type={"button"} bootstrapClass={"btn btn-light"} child={<Link to="/shelterApp/register">Register form</Link>} />
</div>
<div>
<Route path="/shelterApp/login" render={() => <Login />} />
<Route path="/shelterApp/register" render={() => <Register />} />
</div>
</div>
)
}
}
export default withRouter( LoginRouting)
enter image description here
IMAGE with the view :
I will be thankful for any advises !
On your ShelterApp component you can create a new state called hideInfo, or something, that tracks if the user clicked on "Login form" or "Register form".
Then you can pass a props to your <LoginRouting> component.
When the user clicks on "Login form" or "Register form" you change this.hideInfo.
<LoginRouting
onShowForm={() => this.setState({ hideInfo: !hideInfo})}
match={this.props.match}
/>

Passing Routeing related props to component not rendered by <Route>

i have a Dashboard Component:
class DashboardPage extends Component {
....
render() {
const currentLocationPath = this.props.location.pathname;
const isAuthenticated = true;
// redirecting to /dashboard/main and saving current state to next path by pushing it to history object
if (currentLocationPath === '/dashboard/' || currentLocationPath === '/dashboard') {
this.props.history.push('/dashboard/main');
}
const { match } = this.props;
if (isAuthenticated) {
return (
<div className="DashboardPage d-flex flex-column flex-grow">
{/* email confirmation message for loggedin user to verify email ID*/}
{!this.state.isEmailVerified ? <div className="email-confirmation">
<span className="email-confirmation__desktop">
Please check your inbox to confirm your email <strong>{'loggedin user\'s email'}</strong>.
Didn’t receive an email? </span>
<span className="email-confirmation__mobile">Confirm your email. </span>
<a className="js-resend-email" href="#" onClick={this.resendMail}>Resend</a>
</div> : null}
<div className="DasboardPageMain d-flex flex-grow">
{/* sidebar with slide-in effect from left on toggle */}
<SidebarMenu currentLocationPath={currentLocationPath} channels={this.state.channels} />
<div className="DashboardPage__overlay" />
<main className="DashboardPage__Content d-flex flex-grow">
{/* swapping DashboardPage's Child Components based on current url set by <Router/> */}
<Route path="/dashboard/main" render={() => <Dashboard toggleModal={this.toggleModal} />} />
<Route path="/dashboard/channels/:id/:channelName" component={VideoStats} />
<Route path="/dashboard/edit_video" component={EditVideo} />
<Route path="/dashboard/account" component={Account} />
<Route path="/dashboard/socialMedia" component={SocialMedia} />
<Route path="/dashboard/media-library" component={MediaLibrary} />
<Route path="/dashboard/shares" component={Shares} />
{/* <Route path="/dashboard/platform/:data" component={Platforms} /> */}
{/* <Route exact path="/dashboard/channels/:id/:channelName" component={VideosList} /> */}
</main>
{/* Modal With Nested Form to add Chaanel's info. */}
<Modal className="addChannelModal" isOpen={this.state.modal} toggle={this.toggleModal} >
<ModalBody>
<AddChannelForm toggleModal={this.toggleModal} notifySuccess={this.notifySuccess} notifyError={this.notifyError} />
</ModalBody>
</Modal>
</div>
<Footer />
{/* React toastify for toast notifications */}
<ToastContainer
className={{ textAlign: 'center' }}
progressClassName={css({ background: '#007aff' })} />
</div>
);
} else {
return <Redirect to={'/'} />;
}
}
}
export default withRouter(DashboardPage);
Now i want to access the router url params inside <SidebarMenu> component but inside <SidebarMenu> console.log(this.props.match.params) is showing empty object.
But if i do console.log(this.props.match.params) inside any of components being rendered by <Route/> for example:
<Route path="/dashboard/account" component={Account} />
I easily get all params in url.
Is there way to access all these router related props in <SidebarMenu>
?
currently i am conditionally rendering JSX inside <SidebarMenu> like so:
const currentLocationPath = this.props.currentLocationPath;
const pathArray = window.location.pathname.split('/');
{ pathArray.length > 3 &&
<Media tag="li" className={`${pathArray.length === 3 ? 'li-active' : null} li-channels align-items-center mb-1 py-1`}>
{/* Chevron Icon */}
<Media left middle tag="span" className="media-chevron-right ml-5 mr-1">
<FontAwesomeIcon size="xs" icon={currentLocationPath == '/dashboard/main' ? faChevronRight : faChevronDown} className="" />
</Media>
{/* channel's main body */}
<Media body className="text-center text-sm-left mb-3 mb-sm-0">
<Media heading tag="h6" className="m-0">
Channels
</Media>
</Media>
</Media> }
You are not passing match to <SidebarMenu> inside <DashboardPage> render.
You could either pass it as a prop inside your DashboardPage render:
<SidebarMenu match={match} currentLocationPath={currentLocationPath} channels={this.state.channels} />
Or wrap it with withRouter.
Try passing props like this in component
<Route path="/dashboard/account" render={ (props) => <Account {...props} /> }/>

how come event.target.value is only capturing the first letter in string input

Once I started passing props from parent to child to child I have been getting this problem where the getQuestion function only gets me the first letter typed, In addition in the input field nothing shows up.
Before when my code was just Parent to child it worked.
I want to know what exactly is going on because I have tried debugging by console logging and all I know is certain is that it only registers the first letter.
This question did not help because I have not misspelled onChange.
Can't type in React input text field
App.js
class App extends Component {
constructor(props){
super(props);
this.getPostId = this.getPostId.bind(this);
this.getQuestion = this.getQuestion.bind(this);
this.makePost = this.makePost.bind(this);
this.getBody = this.getBody.bind(this);
this.getPostType = this.getPostType.bind(this);
this.getImgSrc = this.getImgSrc.bind(this);
this.submitPost = this.submitPost.bind(this);
this.formOpen = this.formOpen.bind(this);
this.formClose = this.formClose.bind(this);
this.back = this.back.bind(this);
this.showPost = this.showPost.bind(this);
this.renderPosts = this.renderPosts.bind(this);
//Answer/Response methods
this.makeAnswer = this.makeAnswer.bind(this);
this.getAnswer = this.getAnswer.bind(this);
this.submitAnswer = this.submitAnswer.bind(this);
this.state = {
posts: [],
answers: [],
question: '',
body: '',
postType: 'Question',
imgSrc: '',
form: false,
openedPost: null,
answer: '',
favorited: false,
//sign up
email:'',
password: '',
user: null
}
}
getQuestion(event) {
event.preventDefault();
this.setState({ question:event.target.value });
}
render() {
return (
<Router>
<div className="container">
<Route
exact path={"/"}
component={() => <Home />}
/>
<Route
exact path={"/home"}
component={() => <Home />}
/>
<Route
exact path={"/signup"}
component={() => <SignUp />}
/>
<Route
exact path={`/dashboard`}
component={() =>
<Dashboard back={this.back}
form={this.state.form}
openedPost={this.state.openedPost}
renderPosts={this.renderPosts}
formClose={this.formClose}
formOpen={this.formOpen}
posts={this.state.posts}
getPostId={this.getPostId}
getQuestion={this.getQuestion}
makePost={this.makePost}
getBody={this.getBody}
getPostType={this.getPostType}
getImgSrc={this.getImgSrc}
submitPost={this.submitPost}
test={this.test}
question={this.state.question}
/>}
/>
<Route
exact path={`/dashboard/post${this.state.openedPost}`}
component={() =>
<SinglePost posts={this.state.posts}
openedPost={this.state.openedPost}
getAnswer={this.getAnswer}
makeAnswer={this.makeAnswer}
submitAnswer={this.submitAnswer}
showAnswers={this.showAnswers}
renderAnswers={this.renderAnswers}
renderFavorite={this.renderFavorite}
userFavorited={this.userFavorited}
back={this.back}
/>
}
/>
</div>
</Router>
);
}
Dashboard.js
import React, { Component } from 'react';
import Navagationbar from '../../components/Navigation/Navagationbar';
import Header from '../../components/Header/Header';
import SignUpButton from '../../components/SignUp/SignUpButton';
import AddPostForm from './AddPostForm';
import './styles.css';
import {
Link
} from 'react-router-dom'
class Dashboard extends Component {
render() {
let renderedPosts = null;
let createPostButton = <div className="container" ><button className="button-primary" onClick={this.props.formOpen}> Create Post </button> </div>;
if(this.props.openedPost) {
renderedPosts = null;
createPostButton = null;
}
else {
renderedPosts = this.props.renderPosts();
}
let createPostForm = null;
const openedForm = this.props.form;
if(openedForm) {
createPostForm =
<AddPostForm
formClose={this.props.formClose}
posts={this.props.posts}
getPostId={this.props.getPostId}
getQuestion={this.props.getQuestion}
makePost={this.props.makePost}
getBody={this.props.getBody}
getPostType={this.props.getPostType}
getImgSrc={this.props.getImgSrc}
submitPost={this.props.submitPost}
question={this.props.question}
/>
createPostButton = null;
}
console.log("OPENED FORM IS " + openedForm)
return (
<div >
<SignUpButton />
<Header />
<button onClick={this.props.test}/>
{this.props.openedPost ? null : <Navagationbar />}
{createPostForm}
<div className="row">
<div>
{createPostButton}
</div>
</div>
<div className="row">
</div>
<div className="row">
<div className="twelve columns">
{renderedPosts}
</div>
</div>
</div>
);
}
}
export default Dashboard;
AddPostForm.js
import React, { Component } from 'react';
import './styles.css';
class AddPostForm extends Component {
render() {
return(
<div className="container">
<div className="row">
<div className="six columns">
<label>Post Title</label>
<input onChange={this.props.getQuestion} value={this.props.question} className="u-full-width" type="search" placeholder="title" id="exampleEmailInput"/>
</div>
<div className="six columns">
<label>Post Type</label>
<select value={this.props.type} onChange={this.props.getPostType} className="u-full-width">
<option value="Question">Question</option>
<option value="Discussion">Discussion</option>
</select>
</div>
</div>
<div className="row">
<div className="twelve columns">
<label>Post</label>
<textarea onChange={this.props.getBody} className="u-full-width" placeholder="get some clout" id="postMessage"></textarea>
<label>
<span>Image Link</span> <br />
<input type="search" onChange={this.props.getImgSrc}/>
</label>
<input className="button-primary" type="button" value="submit" onClick={this.props.submitPost}/>
<button onClick={this.props.formClose}>Cancel </button>
</div>
</div>
</div>
);
}
}
export default AddPostForm;
edit:
After removing event.preventDefault() from getQuestion I can type but why does the input field unfocus after typing a single letter.
Is it because after every time I type the input field re-renders?
edit: Added majority of the code as requested.
This is sufficient in my opinion let me know if you want the rest of the functions.
Having gone through the code, I noticed all methods come from the root level component App. In which case when you type in the post title input field it immediately calls the parent getQuestion method which set state there by causing a re-render of the page which in turn causing the input field to lose focus.
Method 1:
To fix this I'd suggest you maintain state for the AddPostForm by allowing it manage its own state.
import React, { Component } from 'react';
class AddPostForm extends Component {
state = {
question: ""
}
setQuestion = (event) => {
this.setState({
question: event.target.value
});
}
render() {
return (
<div className="container">
<div className="row">
<div className="six columns">
<label>Post Title</label>
<input
onChange={this.setQuestion} // note change
value={this.state.question} // note change
className="u-full-width"
type="search"
placeholder="title"
id="exampleEmailInput"
/>
</div>
...
</div>
</div>
);
}
}
export default AddPostForm;
Method 2:
In App.js render method I made few changes that will allow you pass down props to the child components without the text fields loosing focus.
render() {
return (
<Router>
<div className="container">
<Route
exact
path="/"
component={Home}
/>
<Route
exact
path="/home"
component={Home}
/>
<Route
exact
path="/signup"
component={SignUp}
/>
<Route
exact
path="/dashboard"
render={(props) =>
<Dashboard
{...props}
back={this.back}
body={this.state.body}
form={this.state.form}
openedPost={this.state.openedPost}
renderPosts={this.renderPosts}
formClose={this.formClose}
formOpen={this.formOpen}
posts={this.state.posts}
getPostId={this.getPostId}
getQuestion={this.getQuestion}
makePost={this.makePost}
getBody={this.getBody}
getPostType={this.getPostType}
getImgSrc={this.getImgSrc}
submitPost={this.submitPost}
test={this.test}
question={this.state.question}
/>
}
/>
<Route
exact
path={`/dashboard/post${this.state.openedPost}`}
render={(props) =>
<SinglePost
{...props}
posts={this.state.posts}
openedPost={this.state.openedPost}
getAnswer={this.getAnswer}
makeAnswer={this.makeAnswer}
submitAnswer={this.submitAnswer}
showAnswers={this.showAnswers}
renderAnswers={this.renderAnswers}
renderFavorite={this.renderFavorite}
userFavorited={this.userFavorited}
back={this.back}
/>
}
/>
</div>
</Router>
);
}
In the Routes for dashboard I changed from using component prop of Route to using render prop instead. This fixes the issue.
To improve it you can clear the form after the state data is send to the parent component.
handleChange(e) {
let { name, value } = e.target;
// clone current state
let clonedState = Object.assign({}, this.state);
clonedState.data[name] = value;
this.setState({
data: clonedState.data,
});
}
handleSubmit(e) {
e.preventDefault();
this.props.getQuestion(this.state.data)
// clear state data
this.setState({ data: {} });
}
It would be better to track changes in the component the user is actually typing in. Then onSubmit, call your getQuestion(this.state).
This would be in any form component a user is typing in.
handleChange(e) {
let { name, value } = e.target;
this.setState({
[name]: value,
});
}
handleSubmit(e) {
e.preventDefault();
this.props.getQuestion(this.state)
}

Categories