I learn react and know, how to create static routes, but can't figure out with dynamic ones. Maybe someone can explain, I'll be very grateful. Let there be two components, one for rendering routes, and another as a template of a route. Maybe something wrong in the code, but hope You understand..
Here is the component to render routes:
import React, { Component } from 'react';
import axios from 'axios';
import Hero from './Hero';
class Heroes extends Component {
constructor(props) {
super(props);
this.state = {
heroes: [],
loading: true,
error: false,
};
}
componentDidMount() {
axios.get('http://localhost:5555/heroes')
.then(res => {
const heroes = res.data;
this.setState({ heroes, loading: false });
})
.catch(err => { // log request error and prevent access to undefined state
this.setState({ loading: false, error: true });
console.error(err);
})
}
render() {
if (this.state.loading) {
return (
<div>
<p> Loading... </p>
</div>
)
}
if (this.state.error || !this.state.heroes) {
return (
<div>
<p> An error occured </p>
</div>
)
}
return (
<div>
<BrowserRouter>
//what should be here?
</BrowserRouter>
</div>
);
}
}
export default Heroes;
The requested JSON looks like this:
const heroes = [
{
"id": 0,
"name": "John Smith",
"speciality": "Wizard"
},
{
"id": 1,
"name": "Crag Hack",
"speciality": "Viking"
},
{
"id": 2,
"name": "Silvio",
"speciality": "Warrior"
}
];
The route component (maybe there should be props, but how to do it in the right way):
import React, { Component } from 'react';
class Hero extends Component {
render() {
return (
<div>
//what should be here?
</div>
);
}
}
export default Hero;
I need something like this in browser, and every route url should be differentiaie by it's id (heroes/1, heroes/2 ...):
John Smith
Crag Hack
Silvio
Each of them:
John Smith.
Wizard.
and so on...
Many thanks for any help!)
Use Link to dynamically generate a list of routes.
Use : to indicate url params, :id in the case
Use the match object passed as props to the rendered route component to access the url params. this.props.match.params.id
<BrowserRouter>
/* Links */
{heroes.map(hero => (<Link to={'heroes/' + hero.id} />)}
/* Component */
<Route path="heroes/:id" component={Hero} />
</BrowserRouter>
class Hero extends Component {
render() {
return (
<div>
{this.props.match.params.id}
</div>
);
}
}
Update so this works for React Router v6:
React Router v6 brought some changes to the general syntax:
Before: <Route path="heroes/:id" component={Hero} />
Now: <Route path="heroes/:id" element={<Hero />} />
You can't access params like with this.props.match anymore:
Before: this.props.match.params.id
Now: import {useParams} from "react-router-dom";
const {id} = useParams();
You can now just use id as any other variable.
To do this you simply add a colon before the url part that should be dynamic. Example:
<BrowserRouter>
{/* Dynamic Component */}
<Route path="heroes/:id" component={Hero} />
</BrowserRouter>
Also you can use the useParams hook from react-router-dom to get the dynamic value for use in the page created dynamically. Example:
import { useParams } from "react-router-dom";
const Hero = () => {
const params = useParams();
// params.id => dynamic value defined as id in route
// e.g '/heroes/1234' -> params.id equals 1234
return (...)
}
Related
In v5 i have such structure
{
path: '/someurl/:id',
exact: true,
render: ({ params }) => (<Redirect to={`/someurl/extraurl/${params.id}`} />),
}
How to refactor this to V6?
react-router-dom v6 no longer has route props, so you'll need to create a new component to gather the "props", or match.params in this case, and render the redirect as a Navigate component.
const MyRedirect = () => {
const { id } = useParams();
return <Navigate to={`/someurl/extraurl/${id}`} replace />;
};
...
{
path: '/someurl/:id',
element: <MyRedirect />,
}
...
<Route path={obj.path} element={obj.element} />
The accepted answer will work but I'll add my solution too, since it's a bit more dynamic. You can set up a function component that will make use of the useParams hook and the generatePath function so your intended destination gets the params from the initial route (whatever they may be):
import React, { FunctionComponent } from 'react';
import { generatePath, Navigate, useParams } from 'react-router-dom';
interface IProps {
to: string;
replace?: boolean;
state?: any;
}
const Redirect: FunctionComponent<IProps> = ({ to, replace, state }) => {
const params = useParams();
const redirectWithParams = generatePath(to, params);
return (
<Navigate to={redirectWithParams} replace={replace} state={state} />
);
};
export default Redirect;
Using this should work with your first example (and any other routes / redirects with dynamic params).
I'm simply trying to pass props through components and render it in jsx but somehow that wouldn't work. I was searching for the problem but just cannot find it.
I'm trying pass props from this component:
import React from "react";
import "../styles/Products.css";
import ProductItem from "../items/ProductItem";
class Products extends React.Component {
constructor(props) {
super(props);
this.state = {
data: []
};
}
componentDidMount() {
fetch("../products.json")
.then(response => response.json())
.then(response => this.setState({ data: response.products }));
}
render() {
return (
<div className="products-container">
<ProductItem data={this.state.data[0]} />
</div>
);
}
}
export default Products;
to this component:
import React from "react";
import "../styles/ProductItem.css";
const ProductItem = props => {
console.log(props.data, "current");
return (
<div className="product-item">
<img src="" alt="" className="bike-image" />
<div className="active-product" />
<div className="view-details">Compare</div>
<h2>Bike</h2>
<h4>downhill bike</h4>
<p>3500 PLN</p>
</div>
);
};
export default ProductItem;
And the problem is when I'm looking in my react dev tools, props has passed properly, but when I'm trying to get to attributes of the object like props.data.id, I get an error:
Cannot read property 'id' of undefined
fetch needs some time to get the response and populate the this.state.data array. So you need to check if the this.state.data[0] value is really available or not. You can try this-
render() {
return (
<div className="products-container">
{this.state.data && this.state.data.length > 0 && <ProductItem data={this.state.data[0]} />}
</div>
);
}
I want to route to a specific page and show a specific JSON Object. If JSON Object contains ID: 1 then it should go to localhost:300/users/1 and show the data of Id 1. I have also mapped another JSON which contains addresses of users and this is I have done so far.
User.jsx
import React from "react";
import users from "../data/PersonData.json";
import adresses from "../data/StreetAddress.json";
class Users extends React.Component {
constructor(props) {
super(props);
this.state = {
Users: []
};
}
render() {
const result = {
Users: users.User.map(user => ({
...user,
adresses: adresses.StreetAddress.filter(
adr => adr.PersonId === user.Id
).map(({ PersonId, ...rest }) => rest)
}))
};
return <div id="users"> {JSON.stringify(result)} </div>;
}
}
export default Users;
and it results into whole JSON Object mapped with address, How can I route it to specific ID i.e. Id which is in
PersonData.json
{
"User": [
{
"BossId": 3,
"DateOfBirth": "1966-09-27T00:00:00",
"FamilyName": "Montejano",
"Gender": "Unspecified",
"GivenName": "Trinh",
"Id": 8,
"Title": "Tech Manager"
},
]
}
So when a user is routed to localhost:3000/users/8 it should show only specific data of user 8.
I solved this in the following way, I took the URL Parameters and got the ID from there and matched it with JSON Object which is looping.
In App.js
import React from "react";
import { BrowserRouter as Router, Route, Switch } from "react-router-dom";
import Users from "./Components/Users";
import User from "./Components/User";
class App extends React.Component {
render() {
return (
<div className="App">
<Router>
<>
<Switch>
<Route path="/users" name="users" component={Users} />
<Route path="/user/:id" name="user" component={User} />
</Switch>
</>
</Router>
</div>
);
}
}
export default App;
and in User.jsx
import React from "react";
import users from "../data/PersonData.json";
import adresses from "../data/StreetAddress.json";
class User extends React.Component {
constructor(props) {
super(props);
this.state = {
Users: []
};
}
render() {
const result = {
Users: users.User.map(user => ({
...user,
adresses: adresses.StreetAddress.filter(
adr => adr.PersonId === user.Id
).map(({ PersonId, ...rest }) => rest)
}))
};
const params = this.props.match.params.id;
return JSON.stringify(result.Users[params]);
}
}
export default User;
I have reused the same code in the question.
I've got the following code which is looping through an JSON file from an API and loops through some posts.
import React, { Component } from 'react';
import logo from './logo.svg';
import './App.css';
//https://alligator.io/react/axios-react/
import axios from 'axios';
export default class PostList extends React.Component {
state = {
posts: []
}
componentDidMount() {
axios.get(`https://jsonplaceholder.typicode.com/users`)
.then(res => {
const posts = res.data;
this.setState({ posts });
})
}
render() {
return (
<div className="App">
<header className="App-header">
<img src={logo} className="App-logo" alt="logo" />
<h1 className="App-title">Welcome to React</h1>
</header>
<p className="App-intro">
Pulls in post slugs from Domain
</p>
<ul>
{ this.state.posts.map(post => <li>{post.name} - {post.username} </li>)}
</ul>
</div>
)
}
}
This works fine, and gets the information which was needed.
Now, in my test JSON file, the format is as follows:
https://jsonplaceholder.typicode.com/users
But in my actual JSON file from WordPress Rest API, we have another item, named core_layout:
JSON image
My issue is, trying to use the same code such as {post.name}does not get the information needed such as core_layout->image->name.
Is there an easy way around this?
Thanks all!
EDIT:
Tried the answers below, but still no luck, get the error TypeError: Cannot read property 'map' of undefined:
import React, { Component } from 'react';
import logo from './logo.svg';
import './App.css';
//https://alligator.io/react/axios-react/
import axios from 'axios';
export default class PostList extends React.Component {
state = {
posts: [],
coreLayout: {}
}
componentDidMount() {
axios.get(`https://jsonplaceholder.typicode.com/users`)
.then(res => {
// const posts = res.data;
//this.setState({ posts });
const { posts, core_layout: coreLayout } = res.data;
this.setState({ posts, coreLayout });
})
}
render() {
return (
<div className="App">
<header className="App-header">
<img src={logo} className="App-logo" alt="logo" />
<h1 className="App-title">Welcome to React</h1>
</header>
<p className="App-intro">
Pulls in post slugs from domain
</p>
<ul>
{ this.state.posts.map(post => <li>{post.name} - {post.core_layout.image.name}</li>)}
</ul>
</div>
)
}
}
EDIT 2:
Tried the below: This gets the title, but again, not the actual corelayout I need.
import React, { Component } from 'react';
class App extends Component {
constructor() {
super();
this.state = {
movies: []
}
}
componentDidMount() {
let dataURL = "http://zinsseruk.com/wp-json/wp/v2/posts?per_page=1";
fetch(dataURL)
.then(res => res.json())
.then(res => {
this.setState({
movies: res
})
})
}
render() {
let movies = this.state.movies.map((movie, index) => {
return <div key={index}>
<p><strong>Title:</strong> {movie.title.rendered}</p>
<p><strong>Title:</strong> {movie.core_layout.acf_fc_layout}</p>
</div>
});
return (
<div>
<h2>Star Wars Movies</h2>
{movies}
</div>
)
}
}
export default App;
Replace const posts = res.data; with const posts = res.data.core_layout;. Then you'll get an array similar to what you have in your test file.
I think you need to understand the JSON structure you receive from the API. Where is located core_layout property? Inside each post property as a children?
So in the posts loop you can use post.core_layout.image.name for image name, for example (and so on with other properties).
If core_property is at the root of the data you receive, you can load it inside your state like so:
state = {
posts: [],
coreLayout: {}
}
componentDidMount() {
axios.get(`https://jsonplaceholder.typicode.com/users`)
.then(res => {
// This is equivalent of doing
// const posts = res.data.posts
// const coreLayout = res.data.core_layout
const { posts, core_layout: coreLayout } = res.data;
this.setState({ posts, coreLayout });
})
}
Then use it in your code by using local component state:
render() {
...
// For example image name:
console.log('image name', this.state.coreLayout.image.name)
...
}
I am looking for a solution in order to still be able to use Link from react-router instead of a when testing href attribute value.
Indeed, I have some components which change of route according to the context. However, when I am testing the href attribute value, the only thing returned is null.
However, when I use an a, it returns me the expected value.
Here is an failing test:
import React from 'react';
import {Link} from 'react-router';
import TestUtils from 'react-addons-test-utils';
import expect from 'must';
const LINK_LOCATION = '/my_route';
class TestComponent extends React.Component {
render() {
return (
<div>
<Link className='link' to={LINK_LOCATION}/>
<a className='a' href={LINK_LOCATION}/>
</div>
);
}
}
describe('Url things', () => {
it('should return me the same href value for both link and a node', () => {
const test_component = TestUtils.renderIntoDocument(<TestComponent/>);
const link = TestUtils.findRenderedDOMComponentWithClass(test_component, 'link');
const a = TestUtils.findRenderedDOMComponentWithClass(test_component, 'a');
expect(link.getAttribute('href')).to.eql(a.getAttribute('href'));
});
});
Output: AssertionError: null must be equivalent to "/my_route"
knowbody from React-router answered to see how they test Link, but they do not have dynamic context which can change value of the href attribute.
So I have done something like that:
class ComponentWrapper extends React.Component {
constructor(props) {
super(props);
this.state = {};
}
set_props(props) {
this.setState({props});
}
render() {
if (this.state.props) {
return <Component {...this.state.props}/>;
}
return null;
}
}
But now, from my component helper:
render_into_document() {
const full_component_props = {
location: this.location,
widget_categories: this.widget_categories
};
node = document.createElement('div');
this.component = render((
<Router history={createHistory('/')}>
<Route path='/' component={ComponentWrapper} />
</Router>
));
this.component.set_props(full_component_props);
return this;
}
I am not able to lay hand on this.component in order to changes props. How could I do that?
I just looked at how react-router tests <Link /> and came up with this for my case:
import test from 'ava'
import React from 'react'
import { render } from 'enzyme'
import { Router, Route } from 'react-router'
import createHistory from 'history/lib/createMemoryHistory'
import SkipToXoom from '../skip-to-xoom'
test('the rendered button redirects to the proper URL when clicked', t => {
const toCountryData = { countryName: 'India', countryCode: 'IN' }
const div = renderToDiv({ toCountryData, disbursementType: 'DEPOSIT', userLang: 'en_us' })
const { attribs: { href } } = div.find('a')[0]
t.true(href.includes(encodeURIComponent('receiveCountryCode=IN')))
t.true(href.includes(encodeURIComponent('disbursementType=DEPOSIT')))
t.true(href.includes(encodeURIComponent('languageCode=en')))
})
/**
* Render the <SkipToXoom /> component to a div with the given props
* We have to do some fancy footwork with the Router component to get
* the Link component in our SkipToXoom component to render out the href
* #param {Object} props - the props to apply to the component
* #returns {Element} - the div that contains the element
*/
function renderToDiv(props = {}) {
return render(
<Router history={createHistory('/')}>
<Route path="/" component={() => <SkipToXoom {...props} userLang="en" />} />
</Router>
)
}
I hope that's helpful!