Pass state from App.js to React Router component - javascript

I have a state in App.js that gets populated with animal data returned by an API. This data is accessible with this.state.animals on the component that is loaded, but my problem is when changing routes to /animals, I can no longer call this.state.animals. It's as if I no longer have access or the data is gone. How can I pass the state to any component when I navigate to them?
App.js
import React, { Component } from 'react';
import { BrowserRouter as Router, Route, NavLink } from 'react-router-dom'
import Animals from './components/Animals'
class App extends Component {
state = {
animals: []
}
componentDidMount() {
fetch('animals api path here')
.then(result => result.json())
.then((data) => {
this.setState({ animals: data })
})
.catch(console.log)
}
render() {
return (
<Router>
<NavLink to="/animals">animals</NavLink>
<Route exact path="/animals" render={()=> <Animals parentState={this.state.animals} />} />
</Router>
);
}
}
export default App
components/Animals.js
import React, { Component } from 'react'
class Animals extends Component {
render() {
console.log(this.state.animals)
return (
<h2>Animals</h2>
//<div>I would loop through and list the animals if I could...</div>
)
}
}
export default Animals

In this example if you want to access animal data, it would be {this.props.parentState} inside fo the animal component

Your Route declaration is defined outside the context of the router. When using React-Router, all your components should have among their ancestors a . Usually, is the top-level component rendered in (at least if you are not using other libraries that need root-context definition, like Redux).

Related

how to get match, location, history props in react router DOM 5 using component class?

This question is specifically for react-router-dom version 5.
Here's my App component following the structures of the quick start guide:
import React, { Component } from "react";
import ProductDetails from "./components/productDetails";
class App extends Component {
render() {
return (
<Switch>
<Route path="/products/:id">
<ProductDetails />
</Route>
</Switch>
);
}
}
And here's my productDetails.jsx:
import React, { Component } from "react";
class ProductDetails extends Component {
render() {
return (
<h1>Product Details - {this.props.match.params.id}</h1>
);
}
}
export default ProductDetails;
which
How to get the this.props.match.params.id in class component way, not function way.
Also I found that the corresponding props empty:
but, those values are in Router.Provider:
So, how to get all the Route component's props from it's children using class?
Add withRouter HOC to your class component then you will have access to the this.props.match.params
import it from react-router
import { withRouter } from "react-router";
Then wrap your component with it like this
export default withRouter(ProductDetails);

React Router & Global Context

I'm building an e-commerce website with React (my first ever React project) and I'm using React router to manage my pages.
I've got the following component tree structure:
<Router>
<BrowserRouter>
<Router>
<withRouter(Base)>
<Route>
<Base>
<BaseProvider>
<Context.Provider>
<Header>
<PageContent>
The standard React Router structure basically, and withRouter I've got the following:
Base.js
import React, { Component } from 'react';
import { withRouter } from 'react-router';
import { Header } from './Header';
import { Footer } from './Footer';
import Provider from '../../BaseProvider';
class Base extends Component {
render() {
return (
<Provider>
<Header/>
<div className="container">{this.props.children}</div>
<Footer />
</Provider>
);
}
}
BaseProvider.js
import React, { Component, createContext } from 'react';
const Context = createContext();
const { Provider, Consumer } = Context;
class BaseProvider extends Component {
state = {
cart: [],
basketTotal: 0,
priceTotal: 0,
};
addProductToCart = product => {
const cart = { ...this.state.cart };
cart[product.id] = product;
this.setState({ cart, basketTotal: Object.keys(cart).length });
};
render() {
return (
<Provider
value={{ state: this.state, addProductToCart: this.addProductToCart }}
>
{this.props.children}
</Provider>
);
}
}
export { Consumer };
export default BaseProvider;
This gives me a template essentially, so I just the children pages without having to include Header and Footer each time.
If I want to use my global context I'm having to import it each time, and it seems like I've done something wrong as surely I should be able to use this on any page since it's exported in BaseProvider?
If I was to visit the About page, I'd get the same component structure, but no access to the consumer without using:
import { Consumer } from '../../BaseProvider';
Why do I have to do this for each file even though it's exported and at the top level of my BaseProvider? It just seems such a bad pattern that I'd have to import it into about 20 files...
Without importing it, I just get:
Line 67: 'Consumer' is not defined no-undef
I tried just adding the contextType to base but I get: Warning: withRouter(Base): Function components do not support contextType.
Base.contextType = Consumer;
I feel like I've just implemented this wrong as surely this pattern should work a lot better.
I'd recommend using a Higher Order Component - a component that wraps other components with additional state or functionality.
const CartConsumer = Component => {
return class extends React.Component {
render() {
return (
<MyContext.Consumer>
<Component />
</MyContext.Consumer>
)
}
}
}
Then in any component where you'd like to use it, simply wrap in the export statement:
export default CartConsumer(ComponentWithContext)
This does not avoid importing completely, but it's far more minimal than using the consumer directly.

How to pass a default params or state to the react router? any alternative approach?

I have a state that is list of valid name (valid_list = []). I want to route to home page if someone types a /topics/:name which is not valid. I want o hence pass the valid list to the new component.
I have mentioned all my attempts at the end.
My parent component is this:
import React, { Component } from 'react'
import { BrowserRouter as Router, Route } from 'react-router-dom'
import Topic from '../topic'
class App extends Component { {
return (
<Router>
<div>
<Route path="/topics/:name" component={Topic} />
</div>
</Router>
);
}
the calling component is
import React,{ Component } from 'react'
import { Link } from 'react-router-dom'
class Maintopic extends Component { {
render() {
return (
<div>
<Link to='/topic:$name' > NextTopic </Link>
</div>
);
}
and called component is something where I am facing issues.
render() {
return (
<div>
<h1>{this.props.match.params.name}</h1>
</div>
);
}
Attempt 1:
Passing object to link:
<Link to={
{
pathname:`/topic/${name}`,
state:{
valid_list : this.state.valid_list
}
}}>
And then performing check on the called component by using artlist this.props.location.state.valid_list,
Here the problem is if I manually type URL like "/topic/name1" or "/topic/name2" i get error like "TypeError: Cannot read property 'valid_list' of undefined"
Attempt 2:
Having called and calling component both have manually stored state in their component
Here issues is I really want my valid_list to be dynamic and can be increased visa input button in future implementation. Plus it does not make sense to maintain different copies of same data.
If your valid_list is an array of stuff, one solution for your problem is making a decorator and decorates your component, so you should do it like this.
const withValidList = (valid_list) => (Component) => (routerProps) => {
return <Component {...routerProps} valid_list={valid_list} />
}
then in your <Route> you must decorate your component with the valid_list like this
import React, { Component } from 'react'
import { BrowserRouter as Router, Route } from 'react-router-dom'
import Topic from '../topic'
class App extends Component { {
return (
<Router>
<div>
<Route path="/topics/:name" component={withValidList(['list1', 'list2'])(Topic)} />
</div>
</Router>
);
}
that's should works, so basically your are wrapping your component adding some extra functionality with the decorator pattern, that give you the ability to passing down the valid_list props to be used into the Topic component.

Unidirectional Data Flow in React with MobX

I'm trying to setup a project architecture using MobX and React and was wondering if doing this following would be considered "not bad". I don't want this question to end up being another "this is a matter of personal preference and so this question doesn't belong here... We can all agree that some things really are bad.
So I'm thinking of only having a single Store.js file that looks something like this:
import { observable, action, useStrict } from 'mobx';
useStrict(true);
export const state = observable({
title: ''
});
export const actions = {
setTitle: action((title) => {
state.title = title;
})
};
Note: that all application state will be in state, there will only be a single store.
I then use state in my root component a.k.a App.js like so:
import React, { Component } from 'react';
import { observer } from 'mobx-react';
import { state } from './Store';
import DisplayTitle from './components/DisplayTitle/DisplayTitle';
import SetTitle from './components/SetTitle/SetTitle';
class App extends Component {
render() {
return (
<div className="App">
<DisplayTitle title={state.title}/>
<SetTitle />
</div>
);
}
}
export default observer(App);
I'll obviously have a whole bunch of components in my app, but none of the components will ever read state directly from Store.js. Only my App.js will import the state and pass it down the component tree.
Another note: I'm not so sure anymore why other components can't read the state directly from Store.js...
This is the case with the DisplayTitle component:
import React, { Component } from 'react';
class DisplayTitle extends Component {
render () {
return (
<h1>{this.props.title}</h1>
);
}
}
export default DisplayTitle;
But, even though no other components can directly import state (except App.js), any component can import actions from Store.js in order to mutate the state.
For example in the SetTitle component:
import React, { Component } from 'react';
import { actions } from './../../Store';
class SetTitle extends Component {
updateTitle (e) {
actions.setTitle(e.currentTarget.value);
}
render () {
return (
<input onChange={this.updateTitle} type='text'/>
);
}
}
export default SetTitle;
Are there any flaws or other obvious reasons why this approach wouldn't be the best route to go? I'd love any and all feedback!
you are missing a few things
at root level:
import { Provider } from 'mobx-react'
...
<Provider state={state}>
<Other stuff />
</Provider>
At component level:
import { inject } from 'mobx-react'
#inject('state')
class Foo ... {
handleClick(){
this.props.state.setTitle('foo')
}
render(){
return <div onClick={() => this.handleClick()}>{this.props.state.title}</div>
}
}
You can stick only the interface actions={actions} in your provider and inject that, ensuring children can call your API methods to mutate state and have it flow from bottom up. Though if you were to mutate it direct, no side effects will happen because all components willReact and update in your render tree - flux is cleaner to reason about.

How to pass redux state to sub routes?

I have a hard time understanding how to use redux together with react-router.
index.js
[...]
// Map Redux state to component props
function mapStateToProps(state) {
return {
cards: state.cards
};
}
// Connected Component:
let ReduxApp = connect(mapStateToProps)(App);
const routes = <Route component={ReduxApp}>
<Route path="/" component={Start}></Route>
<Route path="/show" component={Show}></Route>
</Route>;
ReactDOM.render(
<Provider store={store}>
<Router>{routes}</Router>
</Provider>,
document.getElementById('root')
);
App.js
import React, { Component } from 'react';
export default class App extends React.Component {
render() {
const { children } = this.props;
return (
<div>
Wrapper
{children}
</div>
);
}
}
Show.js
import React, { Component } from 'react';
export default class Show extends React.Component {
constructor(props) {
super(props);
}
render() {
return (
<ul>
{this.props.cards.map(card =>
<li>{card}</li>
)}
</ul>
);
}
}
This throws
Uncaught TypeError: Cannot read property 'map' of undefined
The only solution I've found is to use this instead of {children}:
{this.props.children &&
React.cloneElement(this.props.children, { ...this.props })}
Is this really the proper way to do it?
Use react-redux
In order to inject any state or action creators into the props of a React component you can use connect from react-redux which is the official React binding for Redux.
It is worth checking out the documentation for connect here.
As an example based on what is specified in the question you would do something like this:
import React, { Component } from 'react';
// import required function from react-redux
import { connect } from 'react-redux';
// do not export this class yet
class Show extends React.Component {
// no need to define constructor as it does nothing different from super class
render() {
return (
<ul>
{this.props.cards.map(card =>
<li>{card}</li>
)}
</ul>
);
}
}
// export connect-ed Show Component and inject state.cards into its props.
export default connect(state => ({ cards: state.cards }))(Show);
In order for this to work though you have to wrap your root component, or router with a Provider from react-redux (this is already present in your sample above). But for clarity:
import React from 'react';
import ReactDOM from 'react-dom';
import { Router, Route } from 'react-router';
import { createStore } from 'redux';
import { Provider } from 'react-redux';
import reducers from './some/path/to/reducers';
const store = createStore(reducers);
const routes = <Route component={ReduxApp}>
<Route path="/" component={Start}></Route>
<Route path="/show" component={Show}></Route>
</Route>;
ReactDOM.render(
// Either wrap your routing, or your root component with the Provider so that calls to connect have access to your application's state
<Provider store={store}>
<Router>{routes}</Router>
</Provider>,
document.getElementById('root')
);
If any components do not require injection of any state, or action creators then you can just export a "dumb" React component and none of the state of your app will be exposed to the component when rendered.
I solved it by explicitly mapping the state with connect in every component:
export default connect(function selector(state) {
return {
cards: state.cards
};
})(Show);
This way I can decide what properties of the state the component should have access to as well, polluting the props less. Not sure if this is best practice though.

Categories