React Router id as parameter - reactjs

In my app.js component i have a array called "recipes", it have to elements i like to render these elements in the router thought a id. The App component shound render it thouth the recipe component.
I have some code here, but it does not work properly. I have tried all night long, but i cant find the error. I am new to react so maybe you can see the mistake i cant.
App.js
import React, { Component } from "react";
import "./App.css";
import Recipes from "./components/Recipes";
import { Router } from "#reach/router";
import Recipe from "./components/Recipe ";
import Nav from "./components/Nav";
import About from "./components/About";
class App extends Component {
constructor(props) {
super(props);
this.state = {
recipes: [
{
id: 1,
title: "Drink",
image: "https://picsum.photos/id/395/200/200"
},
{ id: 2, title: "Pasta", image: "https://picsum.photos/id/163/200/200" }
]
};
}
getRecipe(id) {
//Number(id)
return this.state.recipes.find(e => e.id === Number(id));
}
render() {
return (
<React.Fragment>
Recipes
{/*Sending the props from this component to the recipes component so it can be rendered there. And shown here
<Recipes recipes={this.state.recipes}></Recipes>
*/}
<Nav></Nav>
<Router>
<About path="/about"></About>
<Recipe
path="/recipe/:id"
loadRecipe={id => this.getRecipe(id)}
></Recipe>
</Router>
</React.Fragment>
);
}
}
export default App;
Recipe.js
import React, { Component } from "react";
class Recipe extends Component {
constructor(props) {
super(props);
this.state = {
// See App.js for more details. loadRecipe is defined there.
recipe: this.props.loadRecipe(this.props.id)
};
}
render() {
// In case the Recipe does not exists, let's make it default.
let title = "Recipe not found";
// If the recipe *does* exists, make a copy of title to the variable.
if (this.state.recipe) {
title = this.state.recipe.title;
}
return (
<React.Fragment>
<h1>The recipe:</h1>
<p>{title}</p>
{/* TODO: Print the rest of the recipe data here */}
</React.Fragment>
);
}
}
export default Recipe;
I have these two components, i dont know whats wrong, i dont get any error.

We need to add a Route component somewhere to get the functionality that you are expecting. You need to do one of two things. Either make the recipe component a Route component, or leave it as is and wrap it in a Route Component and use the render prop.
const Recipe = (props) => {
<Route {...props}>
// This is where your actual recipe component will live
</Route>
}
Then you will be able to
<Recipe
path="/recipe/:id"
loadRecipe={this.getRecipe}>
</Recipe>
for the loadRecipe portion, you may want to just pass the function down and then use it in the Recipe component. You should get the id from the Route component.
or
<Route path="/recipe/:id" render={()=> <Recipe passDownSomething={this.state} />} />
Once you make this change, you be able to use the trusty console.log to figure out what you are getting props wise and make the necessary adjustments.

Related

Catch Data from URL params in react class Compoent

First of all I like to convey thanks all the wise programmer. After updating react react-router-dom i am facing this problem. Here i want to mention one thing that, i am a "class component" lover.
However, This is my base component in react.
import React, { Fragment, Component } from 'react'
import axios from 'axios'
import { Col , Row} from 'react-bootstrap'
import { Link } from 'react-router-dom'
export default class Blog extends Component {
constructor(props) {
super(props)
this.state = {
data:[]
}
}
componentDidMount()
{
axios.get("https://jsonplaceholder.typicode.com/posts")
.then((response)=>{
if(response.status===200)
{
this.setState({
data:response.data
})
}
})
.catch((error)=>{})
}
render() {
const allData = this.state.data;
const blogFull = allData.map((val)=>{
var title = val.title;
var body = val.body;
var id = val.id;
return(
<Col key={id} lg={4}>
<Link to={"/post/"+id}><h1>{title}</h1></Link>
<p>{body}</p>
</Col>
)
})
return (
<Fragment>
<Row>
{blogFull}
</Row>
</Fragment>
)
}
}
and this is my next component
import axios from 'axios'
import React, { Component, Fragment } from 'react'
import { useParams } from 'react-router'
export default class Post extends Component {
constructor(props) {
super(props)
this.state = {
mydata:[],
}
}
componentDidMount()
{
axios.get("https://jsonplaceholder.typicode.com/posts/")
.then((response)=>{
if(response.status===200)
{
this.setState({
mydata:response.data
})
}
})
.catch((error)=>{
})
}
render() {
const dataAll = this.state.mydata;
return (
<Fragment>
data retriving
<h1>{dataAll.title}</h1>
<p>{dataAll.body}</p>
</Fragment>
)
}
}
My Route is here :
<Routes>
<Route exact path="/" element={<Blog/>}/>
<Route exact path="/post/:id" element={<Post/>}/>
</Routes>
Can anyone tell me that how can i get data in post component from base component via its url parameter? The "match" object is not working in current update of react-router-dom. I want help for class component.
Issue
In react-router-dom v6 the Route components no longer have route props (history, location, and match), and the current solution is to use the React hooks "versions" of these to use within the components being rendered. React hooks can't be used in class components though.
To access the match params with a class component you must either convert to a function component, or roll your own custom withRouter Higher Order Component to inject the "route props" like the withRouter HOC from react-router-dom v5.x did.
Solution
I won't cover converting a class component to function component. Here's an example custom withRouter HOC:
const withRouter = WrappedComponent => props => {
const params = useParams();
// etc... other react-router-dom v6 hooks
return (
<WrappedComponent
{...props}
params={params}
// etc...
/>
);
};
And decorate the component with the new HOC.
export default withRouter(Post);
This will inject a params prop for the class component.
this.props.params.id

Reactjs - how to pass props to Route?

I’m learning React Navigation using React-Router-Dom. I have created a simple app to illustrate the problem:
Inside App.js I have a Route, that points to the url “/” and loads the functional Component DataSource.js.
Inside DataSource.js I have a state with the variable name:”John”. There is also a buttonwith the onclick pointing to a class method that’s supposed to load a stateless component named ShowData.js using Route.
ShowData.js receives props.name.
What I want to do is: when the button in DataSource.js is clicked, the url changes to “/showdata”, the ShowData.js is loaded and displays the props.name received by DataSource.js, and DataSource.js goes away.
App.js
import './App.css';
import {Route} from 'react-router-dom'
import DataSource from './containers/DataSource'
function App() {
return (
<div className="App">
<Route path='/' component={DataSource}/>
</div>
);
}
export default App;
DataSource.js
import React, { Component } from 'react';
import ShowData from '../components/ShowData'
import {Route} from 'react-router-dom'
class DataSource extends Component{
state={
name:' John',
}
showDataHandler = ()=>{
<Route path='/showdata' render={()=><ShowData name={this.state.name}/>}/>
}
render(){
return(
<div>
<button onClick={this.showDataHandler}>Go!</button>
</div>
)
}
}
export default DataSource;
ShowData.js
import React from 'react';
const showData = props =>{
return (
<div>
<p>{props.name}</p>
</div>
)
}
export default showData;
I have tried the following, but, even though the url does change to '/showdata', the DataSource component is the only thing being rendered to the screen:
DataSource.js
showDataHandler = ()=>{
this.props.history.push('/showdata')
}
render(){
return(
<div>
<button onClick={this.showDataHandler}>Go!</button>
<Route path='/showdata' render={()=>{<ShowData name={this.state.name}/>}}/>
</div>
)
}
I also tried the following but nothing changes when the button is clicked:
DataSource.js
showDataHandler = ()=>{
<Route path='/showdata' render={()=>{<ShowData name={this.state.name}/>}}/>
}
render(){
return(
<div>
<button onClick={this.showDataHandler}>Go!</button>
</div>
)
}
How can I use a nested Route inside DataSource.js to pass a prop to another component?
Thanks.
EDIT: As user Sadequs Haque so kindly pointed out, it is possible to retrieve the props when you pass that prop through the url, like '/showdata/John', but that's not what I'd like to do: I'd like that the url was just '/showdata/'.
He also points out that it is possible to render either DataSource or ShowData conditionally, but that will not change the url from '/' to '/showdata'.
There were multiple issues to solve and this solution worked as you wanted.
App.js should have all the routes. I used Route params to pass the props to ShowData. So, /showdata/value would pass value as params to ShowData and render ShowData. And then wrapped the Routes with BrowserRouter. And then used exact route to point / to DataSource because otherwise DataSource would still get rendered as /showdata/:name has /
DataSource.js will simply Link the button to the appropriate Route. You would populate DataSourceValue with the appropriate value.
ShowData.js would read and display value from the router prop. I figured out the object structure of the router params from a console.log() of the props object. It ended up being props.match.params
App.js
import { BrowserRouter as Router, Route } from "react-router-dom";
import DataSource from "./DataSource";
import ShowData from "./ShowData";
function App() {
return (
<div className="App">
<Router>
<Route exact path="/" component={DataSource} />
<Route path="/showdata/:name" component={ShowData} />
</Router>
</div>
);
}
export default App;
DataSource.js
import React, { Component } from "react";
import ShowData from "./ShowData";
class DataSource extends Component {
state = {
name: " John",
clicked: false
};
render() {
if (!this.state.clicked)
return (
<button
onClick={() => {
this.setState({ name: "John", clicked: true });
console.log(this.state.clicked);
}}
>
Go!
</button>
);
else {
return <ShowData name={this.state.name} />;
}
}
}
export default DataSource;
ShowData.js
import React from "react";
const ShowData = (props) => {
console.log(props);
return (
<div>
<p>{props.name}</p>
</div>
);
};
export default ShowData;
Here is my scripts on CodeSandbox. https://codesandbox.io/s/zen-hodgkin-yfjs6?fontsize=14&hidenavigation=1&theme=dark
I figured it out. At least, one way of doing it, anyway.
First, I added a route to the ShowData component inside App.js, so that ShowData could get access to the router props. I also included exact to DataSource route, so it wouldn't be displayed when ShowData is rendered.
App.js
import './App.css';
import {Route} from 'react-router-dom'
import DataSource from './containers/DataSource'
import ShowData from './components/ShowData'
function App() {
return (
<div className="App">
<Route exact path='/' component={DataSource}/>
{/* 1. add Route to ShowData */}
<Route path='/showdata' component={ShowData}/>
</div>
);
}
export default App;
Inside DataSource, I modified the showDataHandler method to push the url I wanted, AND added a query param to it.
DataSource.js
import React, { Component } from 'react';
class DataSource extends Component{
state={
name:' John',
}
showDataHandler = ()=>{
this.props.history.push({
pathname:'/showdata',
query:this.state.name
})
}
render(){
return(
<div>
<button onClick={this.showDataHandler}>Go!</button>
</div>
)
}
}
export default DataSource;
And, finally, I modified ShowData to be a Class, so I could use state and have access to ComponentDidMount (I guess is also possible to use hooks here, if you don't want to change it to a Class).
Inside ComponentDidMount, I get the query param and update the state.
ShowData.js
import React, { Component } from 'react';
class ShowData extends Component{
state={
name:null
}
componentDidMount(){
this.setState({name:this.props.location.query})
}
render(){
return (
<div>
<p>{this.state.name}</p>
</div>
)
}
}
export default ShowData;
Now, when I click the button, the url changes to '/showdata' (and only '/showdata') and the prop name is displayed.
Hope this helps someone. Thanks.

React-router custom prop not passing to component. ternary operator not working correctly

In React i have my App.js page where i keep my states. I'm importing user1.js component to App.js, and in user1.js component i have a link button that takes me to path /user2.
When i click the button, React will set state property called testValue to true and in user2.js page ternary operator should choose the first value - test works because of that. But for some reason it does not work.
Any help?
APP.JS
import React, { Component } from 'react';
import './App.css';
import User1 from './components/user1';
class App extends Component {
constructor(props){
super(props);
this.state = {
testValue:false
};
}
change = () => {
this.setState({
testValue:true
},() => {
console.log(this.state.testValue)
});
}
render() {
return (
<div className="App">
<User1 change={this.change}/>
</div>
);
}
}
export default App;
USER1.JS
import React from 'react';
import { BrowserRouter, Route, Switch, Link } from 'react-router-dom';
import User2 from './user2.js';
const User1 = (props) => {
return(
<BrowserRouter>
<div>
<Link to ="/user2">
<button onClick={props.change}>Next page</button>
</Link>
<Switch>
<Route path="/user2" exact component={User2}/>
</Switch>
</div>
</BrowserRouter>
); // end of return
};
export default User1;
USER2.JS
import React from 'react';
const User2 = (props) => {
console.log(props)
return(
<div>
{props.testValue ?
<p>test works</p>
:
<p>test does not work</p>
}
</div>
);
};
export default User2;
This is what i expected - test works
This is what i got - test does not work
You want to pass a custom property through to a component rendered via a route. Recommended way to do that is to use the render method.
<Route path="/user2" exact render={(props) => <User2 {...props} testValue={true} />} />
I think a valid inquiry here would be what are you wanting to pass through as an extra prop? whats the use case here? You may be trying to pass data in a way you shouldn't (context would be nice :D).

React Router. I want to loop through various links adding components

Please see the image below for the code.
I am trying to create an object within my state of various pages to load with the Router. Everything is fine apart from the render, as you can see on the image is what im trying to load. But it's not allowing me to add a variable? if i use ${item.name} it takes the variable name but all of it as a string eg "". How can i pass this as a variable so when i extend my state I can access different pages with other relative urls.
import React, { Component } from 'react';
import PageTwo from '../Content/PageTwo/PageTwo';
import { BrowserRouter, Route, Switch } from 'react-router-dom';
class Routes extends Component {
constructor(props) {
super();
this.state = {
items: [
{
name: PageTwo,
url: '/test'
}
]
};
}
render() {
return (
<ul>
{this.state.items.map((item, index) => (
<Route
exact
name={item.name}
path={item.url}
render={(props) => (
<{item.name}/>
)}
/>
))}
</ul>
);
}
}
export default Routes;
As i can make out, you want to render component <PageTwo /> for path '/test'. However you cant use components name as you tried with <{item.name} />.
Instead you could store a mapping of the path and component name in object and use that as follows:
const Components = {
test: PageTwo
};
const MyComponent = Components[item.url];
return <MyComponent />;

ReactJS - JSON objects in arrays

I am having a little problem and can't seem to understand how to fix it. So I am trying to create a pokemon app using pokeapi. The first problem is that I can't get my desired objects to display. For example I want to display {pokemon.abilities[0].ability}, but it always shows Cannot read property '0' of undefined but just {pokemon.name} or {pokemon.weight} seems to work. So the problem appears when there is more than 1 object in an array.
import React, {Component} from 'react';
export default class PokemonDetail extends Component{
constructor(props){
super(props);
this.state = {
pokemon: [],
};
}
componentWillMount(){
const { match: { params } } = this.props;
fetch(`https://pokeapi.co/api/v2/pokemon/${params.id}/`)
.then(res=>res.json())
.then(pokemon=>{
this.setState({
pokemon
});
});
}
render(){
console.log(this.state.pokemon.abilities[0]);
const { match: { params } } = this.props;
const {pokemon} = this.state;
return (
<div>
{pokemon.abilities[0].ability}
<img src={`https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/${params.id}.png`} />
</div>
);
}
}
And also some time ago I added the router to my app, so I could pass id to other components, but the thing is I want to display pokemonlist and pokemondetail in a single page, and when you click pokemon in list it fetches the info from pokeapi and display it in pokemondetail component. Hope it makes sense.
import React, { Component } from 'react';
import { BrowserRouter as Router, Route} from 'react-router-dom';
import './styles/App.css';
import PokemonList from './PokemonList';
import PokemonDetail from './PokemonDetail';
export default class App extends Component{
render(){
return <div className="App">
<Router>
<div>
<Route exact path="/" component={PokemonList}/>
<Route path="/details/:id" render={(props) => (<PokemonDetail {...props} />)}/>
</div>
</Router>
</div>;
}
}
In case componentWillMount(), An asynchronous call to fetch data will not return before the render happens. This means the component will render with empty data at least once.
To handle this we need to set initial state which you have done in constructor but it's not correct. you need to provide default values for the abilities which is an empty array.
So change it to
this.state = {
pokemon: {
abilities: []
}
}
And then inside render method before rendering you need to verify that it's not empty
render() {
return (
(this.state.pokemon.abilities[0]) ?
<div>
{console.log(this.state.pokemon.abilities[0].ability)}
<img src={`https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/1.png`} />
</div> :
null
);
}
It is common in React that you always need to safe-check for existence of data before rendering, especially when dealing with API data. At the time your component is rendered, the state is still empty. Thus this.state.pokemon.abilities is undefined, which leads to the error. this.state.pokemon.name and this.state.pokemon.weight manage to escape same fate because you expect them to be string and number, and don't dig in further. If you log them along with abilities, they will be both undefined at first.
I believe you think the component will wait for data coming from componentWillMount before being rendered, but sadly that's not the case. The component will not wait for the API response, so what you should do is avoid accessing this.state.pokemon before the data is ready
render(){
const { match: { params } } = this.props;
const {pokemon} = this.state;
return (
<div>
{!!pokemon.abilities && pokemon.abilities[0].ability}
<img src={`https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/${params.id}.png`} />
</div>
);
}

Resources