Passing function prop to child component (via Link and Route components) - javascript

I'm using pure React with a Rails API backend.
I am fetching data from API and storing state in my Trips component. I have a Link component where I am able to pass the state to my NewTrip component, however <Link> does not allow me to pass functions.
I am able to pass functions to NewPage via render method on the Route component located at './routes/Index'.
But how do I pass the function from my Trips component? It's so much easier when passing as props to the component, the Router seems to be in the way!
'routes/Index.js'
export default (
<Router>
<Switch>
<Route path="/" exact component={Home} />
<Route path="/trips" exact component={Trips} />
<Route path="/trip" render={(routeProps)=><NewTrip {...routeProps} />}/>
<Route path="/trip/:id" exact component={Trip} />
<Route path="/trip/:id/cost" exact component={NewCost} />
<Route path="/trip/:id/edit" exact component={EditTrip} />
</Switch>
</Router>
);
'components/Trips'
class Trips extends React.Component {
constructor(props) {
super(props);
this.state = {
trips: []
}
this.addTrip = this.addTrip.bind(this);
}
addTrip(trip) {
const {trips} = this.state;
trips.push(trip);
this.setState({ trips: trips});
}
render(){
return(
<Link
to={{
pathname: "/trip",
state: {trips: trips, onAddTrip={this.addTrip}} // not allowed,
// but I want to pass this function to the
// Component which is rendered by the Route in Index.js
}}
className="btn custom-button">
Create New Trip
</Link>
)
}
}

I think you should lift state up of the Trips component and have it in your 'routes/Index.js'. (it will need to be a component now, not just an export).
'routes/Index.js'
export default class Routes extends React.Component {
constructor(props) {
super(props);
this.state = {
trips: []
}
this.addTrip = this.addTrip.bind(this);
}
addTrip(trip) {
const {trips} = this.state;
trips.push(trip);
this.setState({ trips: trips});
}
render() {
return (
<Router>
<Switch>
<Route path="/" exact component={Home} />
<Route path="/trips" exact component={Trips} />
<Route path="/trip" render={(routeProps)=>
<NewTrip addTrip={this.addTrip} trips={this.state.trips} {...routeProps} />
}/>
<Route path="/trip/:id" exact render={(routeProps)=>
<Trip addTrip={this.addTrip} trips={this.state.trips} {...routeProps} />
}/>
<Route path="/trip/:id/cost" exact component={NewCost} />
<Route path="/trip/:id/edit" exact component={EditTrip} />
</Switch>
</Router>
);
}
}
'components/Trips'
class Trips extends React.Component {
render() {
const trips = this.props.trips
return (
<Link
to={{
pathname: "/trip",
}}
className="btn custom-button">
Create New Trip
</Link>
)
}
}
It might be better to have the state even higher up in the App component, but you didn't provide that, so this has to do :)

You can pass functions using state in react router Link.
<Link
to={{
pathname: "/trip",
state: {trips: trips, onAddTrip: this.addTrip}
}}
className="btn custom-button">
Create New Trip
</Link>
And then in /trip, you retrieve and use the function like this:
this.props.location.state.addTrip();

Related

Why am I getting "Objects are not valid as a React child" error?

I have a lot of files, but I think the problem is coming from my authentication component in React. I basically want to only display a page if the user is logged in otherwise I want to the user to be redirected.
react-dom.development.js:14887 Uncaught Error: Objects are not valid as a React child (found: object with keys {$$typeof, type, compare, WrappedComponent}). If you meant to render a collection of children, use an array instead.
requireAuth.js
// function that can wrap any component to determine if it is authenticated
import React, { Component } from "react";
import { connect } from "react-redux";
import PropTypes from "prop-types";
import { push } from "#lagunovsky/redux-react-router"; // not sure if this correct
export default function requireAuth(Component) {
class AuthenticationComponent extends React.Component {
constructor(props) {
super(props);
this.checkAuth();
}
componentDidUpdate(prevProps, prevState) {
this.checkAuth();
}
checkAuth() {
// if not authenticated then it is redirected
if (!this.props.isAuthenticated) {
const redirectAfterLogin = this.props.location.pathname;
this.props.dispatch(push(`/login?next=${redirectAfterLogin}`));
}
}
// if authenticated then renders the component
render() {
return (
<div>
{this.props.isAuthenticated === true ? (
<Component {...this.props} />
) : null}
</div>
);
}
}
AuthenticationComponent.propTypes = {
isAuthenticated: PropTypes.bool.isRequired,
location: PropTypes.shape({
pathname: PropTypes.string.isRequired,
}).isRequired,
dispatch: PropTypes.func.isRequired,
};
// checks isAuthenticated from the auth store
const mapStateToProps = (state) => {
return {
isAuthenticated: state.auth.isAuthenticated,
token: state.auth.token,
};
};
return connect(mapStateToProps)(AuthenticationComponent);
}
App.js
class App extends Component {
render() {
return (
<div>
<Root>
<ToastContainer hideProgressBar={true} newestOnTop={true} />
<Routes>
<Route exact path="/" element={<Home />} />
<Route path="/closet" element={requireAuth(Closet)} />
<Route path="*" element={<NotFound />} />
</Routes>
</Root>
</div>
);
}
}
I have done some digging but I can't find a problem like this.
The error is because on this line:
<Route path="/closet" element={React.createComponent(requireAuth(Closet))} />
You're passing the actual class definition to the element prop and not an instance of the class (which would be the React component). To fix this, you can use React.createElement:
class App extends Component {
render() {
return (
<div>
<Root>
<ToastContainer hideProgressBar={true} newestOnTop={true} />
<Routes>
<Route exact path="/" element={<Home />} />
<Route path="/closet" element={React.createElement(requireAuth(Closet))} />
<Route path="*" element={<NotFound />} />
</Routes>
</Root>
</div>
);
}
}
Because Route's element props need a ReactNode,But requireAuth(Closet)'s type is () => React.ReactNode, you can change your App.js like this:
const AuthComponent = requireAuth(Closet);
class App extends Component {
render() {
return (
<div>
<Root>
<ToastContainer hideProgressBar={true} newestOnTop={true} />
<Routes>
<Route exact path="/" element={<Home />} />
<Route path="/closet" element={<AuthComponent />} />
<Route path="*" element={<NotFound />} />
</Routes>
</Root>
</div>
);
}
}

TypeScript - ReactRouter | Arrow function captures the global value of 'this' which implicitly has type 'any'

I'm rendering a component via React Router 4 using render={() => </Component />}
I need to pass state to the given component i.e: <Game />
export const Routes: React.SFC<{}> = () => (
<Switch>
<Route path="/" exact={true} component={Home} />
<Route path="/play-game" render={() => <Game {...this.state} />} />
<Redirect to="/" />
</Switch>
)
To which TS breaks saying:
The containing arrow function captures the global value of 'this' which implicitly has type 'any'
The final goal is to be able to pass the Routes to my main app: i.e:
export default class App extends Component<{}, AppState> {
public state = {
// state logic
}
public render(): JSX.Element {
return (
<BrowserRouter>
<div className="App">
<Navigation />
<Routes />
</div>
</BrowserRouter>
)
}
}
How could I apply the correct types to suppress this TypeScript error?
Arrow functions do not have lexical contexts, so any invocation of this inside the body of an arrow will degenerate to its value in the outer scope. This is what TS is complaining about.
For your problem of passing the state around you need to pass this as a prop to the Routes component which will dispatch it to the relevant route.
export default class App extends Component<{}, AppState> {
public state = {
// state logic
}
public render(): JSX.Element {
return (
<BrowserRouter>
<div className="App">
<Navigation />
<Routes state={this.state}/>
</div>
</BrowserRouter>
)
}
}
// you need to pass the correct type to React.SFC<>
// probably something along React.SFC<{ state: State }>
// where State is the type of `state` field in App.
export const Routes: React.SFC<...> = ({ state }) => (
<Switch>
<Route path="/" exact={true} component={Home} />
<Route path="/play-game" render={() => <Game {...state} />} />
<Redirect to="/" />
</Switch>
)

withRouter Does Not passes Route props to component

Here is my navigation component:
import React from 'react'
class Navigation extends React.Component {
constructor(props) {
super(props);
this.state = {
type: 'signUp', // or login
showModal: false,
isLoggedIn: false,
}
}
...some code
render() {
const { showModal, type, isLoggedIn } = this.state
console.log(this.props.location); // all problem is this, I'm not getting it in console
return(
...some more code
)
}
}
export default withRouter(Navigation)
And here is where it it been used in app.js
class App extends React.Component {
render () {
return(
<Router>
<Fragment>
<Navigation /> // <= right there
<Switch>
<Route exact path='/' component={HomePage}/>
<Route exact path='/search' component={HomePage}/>
<Route component={Lost} />
</Switch>
</Fragment>
</Router>
)
}
}
I want to get updated route props like match and location and history in my <Navigation /> component but I get it only when the first time that component mounts on the DOM, in my other components I update the route using window.history.pushState but I am not able to get route props from withRouter after link in the browser is been updated.
I update route with window.history.pushState because:
I could not find any way to update just link in the address bar without showing user or redirecting user to new component with React router DOM (am I doing it in right way or not?)
based on that I then use window.location.pathname to add some specific stylings to some components)
Also, I read the entirety of this and this but I could not solve this issue. What am I doing wrong?
withRouter gives you the closest <Route>'s route props, and since the Navigation component is not inside a Route you will not get the route props.
You could e.g. put the Navigation component on a Route outside of the Switch that will always be visible.
Example
class App extends React.Component {
render() {
return (
<Router>
<Fragment>
<Route path="/" component={Navigation} />
<Switch>
<Route exact path="/" component={HomePage} />
<Route exact path="/search" component={HomePage} />
<Route component={Lost} />
</Switch>
</Fragment>
</Router>
);
}
}

ReactJS: Pass parameter from rails to react router to component

I am trying to pass a value from the render function to the component:
= react_component('App', props: {test: 'abc'}, prerender: false)
Routes.jsx
<Route path="/" component={App} >
App.jsx (component)
class App extends React.Component {
render() {
return (
<Header test={this.props.test}>
</Header>
{this.props.children}
<Footer />
);
}
}
App.propTypes = { test: PropTypes.string };
There does not seem to be a coherent answer to this complete flow.
I have tried the following:
<Route path="/" component={() => (<App myProp="value" />)}/>
But this still does not answer the question of picking up the value provided by the initial render call(react_component)
Looking for an end to end answer on how to pass a parameter from the
"view" to the "react router" to the "component"
We will start from the view:
<%= react_component('MyRoute', {test: 123}, prerender: false) %>
Now we will create a component that holds our route:
class MyRoute extends Component{
constructor(props){
super(props)
}
render(){
return(
<Switch>
<Route path="/" render={() => <App test={this.props.test} />} />
<Route path="/login" component={Login} />
</Switch>
)
}
}
As you can see, we passed the test prop from the Route component to the App component. Now we can use the test prop in the App component:
class App extends Component{
constructor(props){
super(props)
}
render(){
return(
<h1>{this.props.test}</h1>
)
}
}
<Route path="/" render={attr => <App {...attr} test="abc" />} />
In Router v3 you would do something like this
Wrap your App component under withRouter like this
import { withRouter } from 'react-router';
class App extends React.Component {
render() {
return (
<Header test={this.props.test}>
</Header>
{
this.props.children &&
React.clone(this.props.children, {...this.props} )}
<Footer />
);
}
}
App.propTypes = { test: PropTypes.string };
export const APP = withRouter(App);
And construct your routes like this...
<Route path="/" component={APP}>
<Route path="/lobby" component={Lobby} />
<Route path="/map" component={GameMap} />
...
</Route>
So your child routes will be rendered inside the APP children property an the props will be passed down to them.
Hope this helps!

Accessing parent state in child in React

I have (e.g.) two components in React. The first, app.js, is the root component. It imports some JSON data and puts it in its state. This works fine (I can see it in the React devtools).
import data from '../data/docs.json';
class App extends Component {
constructor() {
super();
this.state = {
docs: {}
};
}
componentWillMount() {
this.setState({
docs: data
});
}
render() {
return (
<Router history={hashHistory}>
<Route path="/" component={Wrapper}>
<IndexRoute component={Home} />
<Route path="/home" component={Home} />
<Route path="/docs" component={Docs} />
</Route>
</Router>
);
}
}
The second, docs.js, is meant to show this JSON data. To do that it needs to access the state of app.js. At the moment it errors, and I know why (this does not include app.js). But how then can I pass the state from app.js to docs.js?
class Docs extends React.Component {
render() {
return(
<div>
{this.state.docs.map(function(study, key) {
return <p>Random text here</p>;
})}
</div>
)
}
}
The proper way of doing this would be by passing state as props to Docs component.
However, because you are using React Router it can be accessed in a bit different way: this.props.route.param instead of default this.props.param
So your code should look more or less like this:
<Route path="/docs" component={Docs} docs={this.state.docs} />
and
{this.props.route.docs.map(function(study, key) {
return <p>Random text here</p>;
})}
Another way of doing this is:
<Route path="/docs" component={() => <Docs docs={this.state.docs}/>}>
If you need to pass children:
<Route path="/" component={(props) => <Docs docs={this.state.docs}>{props.children}</Docs>}>
If you are doing it like this, then you can access your props values directly by this.props.docs in Child Component:
{
this.props.docs.map((study, key)=> {
return <p key={key}>Random text here</p>;
})
}
Another way of doing this will be
<Route path='/' render={ routeProps => <Home
{...routeProps}
docs={this.state.docs}
/>
}
/>
While in the child component you can access docs using
this.props.docs
Hope it helps!

Categories

Resources