What does ...{} as any in react? - react-router

I was having trouble passing the to to the Link component of react-router-dom in the Tab element of the material-ui core.
I finally came up with this solution:
import * as React from 'react';
import Tabs from '#material-ui/core/Tabs';
import Tab from '#material-ui/core/Tab';
import { Link } from 'react-router-dom';
interface Props {
title?: string;
}
interface State {
value: number;
}
class NavButtons extends React.Component<Props, State> {
constructor(props: Props) {
super(props);
this.state = { value: 0 };
}
public handleOnChange = (event: any, value: number) => {
this.setState({ value })
}
public render() {
const {value} = this.state
return (
<Tabs value={value} onChange={this.handleOnChange} >
<Tab label="Home" component={Link} {...{to:"/"} as any} />
<Tab label="Contact" component={Link} {...{to:"/contact/"} as any} />
</Tabs>
)
}
}
export default NavButtons
The only problem is that I can't seem to find out what ...{} as any does in the documentation of material-ui or react.
Can someone explain this to me? I see a lot of React programmers use it but I have no idea what it does exactly.

typescript. what is mean: (this as any)
Looks like it's a TypeScript syntax. Adding as any could remove the type checking of {to:"/"} so it won't cause any warning/error.

Related

Passing props through a "template" component

I'm building a template component using React with TypeScript but i'm facing an issue i'm unable to solve. I'm posting this in case anyone knows how to approach it.
My project has a MyComp component that invokes TemplateComp using a subcomponent GraphComp and the data that Graph requires.
TemplateComp invokes and stylises the Graph subcomponent plus adds some props that are needed (such as customPropertyA) next to graphData.
GraphComp is requiring certain parameters that graphData needs to render properly.
The issue i'm facing is related to the types definition from GraphComp to MyComp while passing through TemplateComp. It may seem that (because TemplateComp defines graphData as any, as it is unknown to it) MyComp understands that graphData can also be any, but in reality it should be equal to the properties that Graph is requiring as Props (but not all of them).
Is there any way to let MyComp and TemplateComp infer the types that GraphComp is asking for?
Here is my code:
import { Component, ElementType } from 'react'
export default class MyComp extends Component<{}, {}> {
render() {
return (
<div>
<TemplateComp
Graph={GraphComp}
graphData={{
value: 0
}}
/>
</div>
)
}
}
class TemplateComp extends Component<
{
Graph: ElementType
graphData: any
},
{}
> {
customPropertyA = 'hello'
render() {
const { graphData, Graph } = this.props
return (
<div>
<Graph {...graphData} customPropertyA={this.customPropertyA} />
</div>
)
}
}
class GraphComp extends Component<
{
value: number
customPropertyA: string
},
{}
> {
render() {
return <div>my value: {this.props.value}</div>
}
}
I'm perfectly fine with modifying how these components work. However, i still need the 3 layer approach and to be able to define GraphComp's props from within MyComp and TemplateComp separately.
For those wondering how to approach this, i managed to fix it by calling the mounted JSX instead of mounting it in TemplateComp and removed properties from it as they can also be described in MyComp.
import { Component, ElementType } from 'react'
export default class MyComp extends Component<{}, {}> {
render() {
return (
<div>
<TemplateComp
Graph={<GraphComp value={0} customPropertyA={"hello"} />}
/>
</div>
)
}
}
class TemplateComp extends Component<
{
Graph: JSX.Element
},
{}
> {
render() {
const { Graph } = this.props
return (
<div>
{Graph}
</div>
)
}
}
class GraphComp extends Component<
{
value: number
customPropertyA: string
},
{}
> {
render() {
return <div>my value: {this.props.value}</div>
}
}

How to create dynamic routes with react-router-dom?

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 (...)
}

React Native, Navigating a prop from one component to another

handleShowMatchFacts = id => {
// console.log('match', id)
return fetch(`http://api.football-api.com/2.0/matches/${id}?Authorization=565ec012251f932ea4000001fa542ae9d994470e73fdb314a8a56d76`)
.then(res => {
// console.log('match facts', matchFacts)
this.props.navigator.push({
title: 'Match',
component: MatchPage,
passProps: {matchInfo: res}
})
// console.log(res)
})
}
I have this function above, that i want to send matchInfo to matchPage.
I take in that prop as follows below.
'use strict'
import React from 'react'
import { StyleSheet, View, Component, Text, TabBarIOS } from 'react-native'
import Welcome from './welcome.js'
import More from './more.js'
export default class MatchPage extends React.Component {
constructor(props) {
super(props);
}
componentWillMount(){
console.log('mathc facts ' + this.props.matchInfo._bodyInit)
}
render(){
return (
<View>
</View>
)
}
}
All the info I need is in that object - 'this.props.matchInfo._bodyInit'. My problem is that after '._bodyInt', I'm not sure what to put after that. I've tried .id, .venue, and .events, they all console logged as undefined...
You never change props directly in React. You must always change the state via setState and pass state to components as props. This allows React to manage state for you rather than calling things manually.
In the result of your api call, set the component state:
this.setState({
title: 'Match',
component: MatchPage,
matchInfo: res
}
Then pass the state as needed into child components.
render() {
return(
<FooComponent title={this.state.title} matchInfo={this.state.matchInfo} />
);
}
These can then be referenced in the child component as props:
class FooComponent extends Component {
constructor(props) {
super(props);
}
componentWillMount() {
console.log(this.props.title);
console.log(this.props.matchInfo);
// Etc.
}
}
If you need to reference these values inside the component itself, reference state rather than props.
this.state.title;
this.state.matchInfo;
Remember components manage their own state and pass that state as props to children as needed.
assuming you are receiving json object as response , you would need to parse the response before fetching the values.
var resp = JSON.parse(matchInfo);
body = resp['_bodyInit'];

Testing href value from a link component while having dynamic props

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!

React-router transitionTo is not a function

import React from 'react';
import { Router, Link, Navigation } from 'react-router';
export default class ResourceCard extends React.Component {
render() {
return (
<div onClick={this.routeHandler.bind(this)}>
LINK
</div>
);
}
routeHandler(){
this.transitionTo('someRoute', {objectId: 'asdf'})
}
}
I can't get it, what's wrong?
I'm receiving an error:
Uncaught TypeError: this.transitionTo is not a function
I've tried everything I've find in docs or in gitHub issues:
this.transitionTo('someRoute', {objectId: 'asdf'})
this.context.transitionTo('someRoute', {objectId: 'asdf'})
this.context.route.transitionTo('someRoute', {objectId: 'asdf'})
etc.
the route and the param is correct, it works fine in this case:
<Link to="'someRoute" params={{objectId: 'asdf}}
p.s. react-router, react and other libraries is up to date
The Navigation component is a Mixin and needs to be added to the component accordingly. If you want to bypass the Mixin (which I feel is the direction React-Router is going) you need to set the contextTypes on the component like so:
var ResourceCard = React.createClass({
contextTypes: {
router: React.PropTypes.func
}, ...
then you can call this.context.router.transitionTo.
This works with react 0.14.2 and react-router 1.0.3
import React from 'react';
import { Router, Link } from 'react-router';
export default class ResourceCard extends React.Component {
constructor(props,) {
super(props);
}
render() {
return (
<div onClick={this.routeHandler.bind(this)}>
LINK
</div>
);
}
routeHandler(){
this.props.history.pushState(null, '/');
}
}
As there's no mixin support for ES6 as of now , you need to change a few things to make it work .router is an opt-in context type so you will have to explicitly define contextTypes of the class . Then in your constructor You will have to pass context and props to super class. And while calling transitionTo you'll have to use this.context.router.transitionTo . and you don't need to import Navigation.
import React from 'react';
import { Router, Link } from 'react-router';
export default class ResourceCard extends React.Component {
constructor(props, context) {
super(props, context);
}
render() {
return (
<div onClick={this.routeHandler.bind(this)}>
LINK
</div>
);
}
routeHandler(){
this.context.router.transitionTo('someRoute', {objectId: 'asdf'})
}
}
ResourceCard.contextTypes = {
router: function contextType() {
return React.PropTypes.func.isRequired;
}
};