Reusing component in react with dynamic router links - javascript

I'm trying to reuse a component in react to render a table view from mysql. I have my navigation generated by a query that lists all of the tables in the database.
import React, { Component } from 'react';
import { BrowserRouter as Router, NavLink } from "react-router-dom";
import './navigation.scss';
class Navigation extends Component {
state = {
items: []
}
Normalize = name => {
return name.split('_')
.map(part => part.charAt(0).toUpperCase() + part.slice(1))
.join(' ');
}
componentDidMount() {
fetch('http://localhost:3132/api/tables')
.then(data => {
return data.json();
})
.then(json => {
let tables = json.map((table, index) => {
var name = table.table_name;
return <li key={index}>
<NavLink activeClassName="active-nav" to={'/table/' + name} name={name}>{this.Normalize(name)}</NavLink>
</li>;
})
this.setState({ tables: tables });
});
}
render() {
return (
<div className="navigation">
<h2>Tables</h2>
<Router>
<ul>
<li><NavLink exact activeClassName="active-nav" to="/">Home</NavLink></li>
{this.state.tables}
</ul>
</Router>
</div>
)
}
}
export default Navigation;
The links work in that they update the url and the active class gets assigned. However, the 'view' component never changes after initial page load.
import React from 'react';
import { Switch, Route } from 'react-router-dom';
import Home from './components/home/home.component';
import Table from './components/table/table.component';
const Routes = () => {
return (
<div className="main">
<Switch>
<Route exact path="/" component={Home}></Route>
<Route path={"/table/:path"} component={(props) => (<Table {...props} />)}></Route>
</Switch>
</div>
)
}
export default Routes;
Even the static Home route doesn't update when it is active. This is what the Table component looks like:
import React, { Component } from 'react';
class Table extends Component {
state = {};
componentDidMount() {
this.setState({tableName: this.props.match.params.path});
}
render() {
return <h1>{this.state.tableName}</h1>
}
}
export default Table;
What do I have to do to get the 'Table' component to render/update?
Edit:
Tried a functional component as suggested in the comment as such and no luck
import React, { Component } from 'react';
const Table = props => {
return <h1>{props.match.params.path}</h1>
}
export default Table;

Related

Context empty after async initialisation

I am trying to fetch data from a backend API and initialise my FieldsContext. I am unable to do it, it returns an empty fields array in the Subfields component. I have spent hours on fixing it. But I eventually give up. Please take a look into this. Thanks in advance.
Here is my code
App.js
import React from 'react';
import 'bootstrap/dist/css/bootstrap.min.css'
import './App.css';
import Index from './components/pages/index/'
import FieldsProvider from './providers/fieldProvider'
import AuthProvider from './providers/authProvider'
import {BrowserRouter as Router,Switch,Route} from 'react-router-dom';
import SubFields from './components/pages/subfields';
function App() {
return (
<Router>
<AuthProvider>
<FieldsProvider>
<Switch>
<Route exact path="/" component={Index} />
<Route exact path="/:fieldid/subfields" component={SubFields} />
</Switch>
</FieldsProvider>
</AuthProvider>
</Router>
);
}
export default App;
FieldsContext.js
import React from 'react'
const FieldsContext = React.createContext();
export default FieldsContext
FieldsProvider.js
import React, { Component } from 'react'
import FieldsContext from '../libs/fieldContext'
export default class FieldsProvider extends Component {
state = {fields:[]}
getFields()
{
fetch('/api/fields')
.then(res => res.json())
.then(fields => this.setState({fields}));
}
async componentDidMount() {
await this.getFields();
}
render() {
return (
<FieldsContext.Provider value={this.state} >
{this.props.children}
</FieldsContext.Provider>
)
}
}
Subfields.js
import React, { Component } from 'react'
import FieldsContext from '../../../libs/fieldContext'
import FieldsList from '../../Fields/fieldlist'
export default class SubFields extends Component {
componentDidMount(){
// const fieldId = this.props.match.params.fieldid;
console.log(this.context);
}
render() {
return (
<div>
</div>
)
}
}
SubFields.contextType = FieldsContext
try using an ES6 Arrow function, which binds the function to the object instance, so that this refers to the object instance of the class when it is called.
When its called asynchronously, this will refer the the class object instance you want to update.
import React, { Component } from 'react'
import FieldsContext from '../libs/fieldContext'
export default class FieldsProvider extends Component {
state = {fields:[]}
// ES6 Arrow function
getFields = () =>
{
fetch('/api/fields')
.then(res => res.json())
.then(fields => this.setState({fields}));
}
async componentDidMount() {
await this.getFields();
}
render() {
return (
<FieldsContext.Provider value={this.state} >
{this.props.children}
</FieldsContext.Provider>
)
}
}
Alternatively, Try binding of your function in the class constructor.
export default class FieldsProvider extends Component {
state = {fields:[]}
constructor(props) {
//bind the class function to this instance
this.getFields = this.getFields.bind(this);
}
//Class function
getFields()
{
fetch('/api/fields')
.then(res => res.json())
.then(fields => this.setState({fields}));
}
async componentDidMount() {
await this.getFields();
}
render() {
return (
<FieldsContext.Provider value={this.state} >
{this.props.children}
</FieldsContext.Provider>
)
}
}
As a side note: Prefer to use functional components for consuming of ContextAPI.
import React, { Component } from 'react'
import FieldsContext from '../../../libs/fieldContext'
import FieldsList from '../../Fields/fieldlist'
export default function SubFields (props) {
const {
match
} = props;
//much better way to consume mulitple Contexts
const { fields } = React.useContext(FieldsContext);
//useEffect with fields dependency
React.useEffect(() => {
console.log(fields);
},[fields]);
return (
<div>
</div>
)
}

passing history props to the dynamic created routes wrapped in a parent component

how can i pass the history to the dynamically created routes which are
wrapped with a another component
see the below snippet
import React, {Component} from 'react';
import {BrowserRouter as Router, Switch, Route } from 'react-router-dom';
import Loader from './Loader';
import RootComponent from './RootComponent';
import About from './About';
import Contact from './Contact';
class App extends Component{
state = {
loaderStatus: false,
dynamicRoutes: null
}
componentWillMount(){
this.setState({
loaderStatus: true
})
}
componentDidMount(){
let routes = () => {
let accessedRoutes = [{path:'about', component: () => <About />},{path:'contact', component: () => <Contact />}].map(o => {
return (
<Route
exact={true}
path={o.path}
component={o.component}
>
</Route>
)
})
return accessedRoutes
}
setTimeout(() => {
let output = this.createRoutes(routes)
this.setState({
dynamicRoutes: output,
loaderStatus: false
})
}, 4000)
}
createRoutes = (routes) => {
return (
<RootComponent>
<Switch>
{routes}
</Switch>
</RootComponent>
)
}
render(){
return(
<Fragment>
{
this.state.loaderStatus ?
<Loader />
:
<Router>
{this.state.dynamicRoutes}
</Router>
}
</Fragment>
)
}
}
export default App;
// About.js
import React from 'react';
const about = (props) => {
console.log('props in about', props) // giving empty object
return (
<div>About</div>
)
}
export default about
Change the structure of accessedRoutes to [{path:'about', component:About}].
now you can access to the router props

Can't access url params, passed using react-router 4

I'm having issues accessing a parameter called bookId from the Reader.js component. The parameter is passed down from BookCarouselItem.js using react-router. Reader.js is a connected component.
I'm not sure if that makes a difference, but does react-router work with redux connected components? Or do I need to use something like connected-react-router?
I've tried to refer to similar questions but wasn't able to find a solution, help would be greatly appreciated.
App.js
import React, { Component } from 'react'
import { BrowserRouter as Router, Route } from 'react-router-dom'
import { routes } from 'constants/index';
import Reader from 'components/reader/Reader'
Class App extends Component {
render() {
return (
<div className='container-fluid main-container'>
<Router>
<div>
<Route
path={'/reader/:bookId'}
component={() => <Reader />}
/>
</div>
</Router>
</div>
);
}
}
export default App;
BookCarouselItem.js
import React from 'react'
import { Link } from 'react-router-dom'
export class BookCarouselItem extends React.Component {
render() {
const { bookThumbnail } = this.props;
const { name, numberOfSections } = bookThumbnail;
const bookId = 0;
return (
<Link className='book-carousel-link' to={`/reader/${bookId}`}>
<div className='book-info-overlay'>
<h5>{name}</h5>
<span>{numberOfSections} Sections</span>
</div>
</Link>
);
}
}
export default BookCarouselItem;
Reader.js
import React from 'react'
import { connect } from 'react-redux'
import { compose } from 'recompose'
export class Reader extends React.Component {
render() {
const { match, pageLevel } = this.props;
console.log(match); // undefined
return (
<div>
<div className='reader-body'>
<Book bookId={match.params.bookId}
pageLevel={pageLevel}
bank={bank}/>
</div>
);
}
}
Const mapStateToProps = (state) => {
return {
metadata: state.book.metadata,
pageLevel: state.book.pageLevel
}
};
const authCondition = (authUser) => !!authUser;
export default compose(
withAuthorization(authCondition),
connect(mapStateToProps, mapDispatchToProps),
)(Reader);
You can just give the component to the component prop and the route props will be passed down to the component automatically.
<Route
path="/reader/:bookId"
component={Reader}
/>
If you want to render something that is not just a component, you have to pass down the route props manually.
<Route
path="/reader/:bookId"
render={props => <Reader {...props} />}
/>
I'm not sure but maybe mapStateToProps rewrite you props so could you please first read this issue

React - Dynamically load a component into the page

I have a static component called Item.js
Routes.js
export default () => (
<Provider store={store}>
<BrowserRouter>
<Switch>
<Route path="/posts" component={Posts} />
<Route path="/form" component={Postform} />
<Route exact path="/" component={App} />
<Route path="/items" component={Items} />
<Route path="/cart" component={Cart} />
<Route path="/page/:id" component={Page} />
</Switch>
</BrowserRouter>
</Provider>
);
In the above page component, I want to load item.js or any other page depending on whats passed to the URL params in as "id" in the page component.
import React, { Component } from 'react';
import Navbar from './Navbar';
import Item from './pages/Item';
class Page extends Component {
componentDidMount() {
const { id } = this.props.match.params;
console.log(id);
}
render() {
return (
<div>
<Navbar />
<div>Hello</div>
</div>
);
}
}
export default Page;
How do I achieve this? I don't know.
Are there any alternative ways of doing it?
Ok, I solved it by following a variation of Johnny Peter's answer.
Page.js
import React, { Component } from 'react'
import Navbar from './Navbar';
import components from './indexPage'
class Page extends Component {
render() {
const { id } = this.props.match.params;
const PageComponent = components.find(comp => comp.id === id).component;
return (
<div>
<Navbar />
<PageComponent/>
</div>
);
}
}
export default Page;
indexPage.js
import Item from './pages/Item'
import Meow from './pages/Meow'
const components = [{
id: 'item',
component: Item
},
{
id: 'meow',
component: Meow
}
]
export default components;
Something like this should work
import React from 'react';
import Navbar from './Navbar';
import Item from './pages/Item';
const components = [{
id: 'your/id/passed/in/param'
component: Item
}]
class Page extends React.Component {
state = {
Component: null,
}
componentDidMount() {
const { id } = this.props.match.params;
this.setState({ Component: components.find(comp => comp.id === id).component })
}
render() {
const { Component } = this.state;
return (
<div>
<Navbar />
<Component />
</div>
);
}
}
export default Page;
You can create an index file like so:
index.js:
import Item from './item';
const pages = {
pageId: Item,
};
export default pages;
And in page component:
import React, { Component } from 'react';
import Navbar from './Navbar';
import Item from './pages/Item';
import pages from './pages';
class Page extends Component {
componentDidMount() {
const { id } = this.props.match.params;
console.log(id);
}
render() {
const { id } = this.props.match.params;
if (pages[id]) {
return pages[id];
}
return (
<div>
<Navbar />
<div>Hello</div>
</div>
);
}
}
export default Page;
in order to dynamically load component based on id.

React-router-dom - Link change url but does not render

I'm new to React and I've made a <Link>to go to next or previous item from dy datas(for example, if i am on user/2 view, previous link go to user/1 and next link go to user/3), the url is correctly changed but the component is not rendered at all and the datas are not reloaded at all.
I've read that it's due to the component not detecting that the children is not changing state so the parent component does not render.
I've tried to use withRouter but I've got a error : You should not use <Route> or withRouter() outside a <Router> and I'm not understanding what I'm doing so if someone has the solution and some explanation to it I would be grateful :)
App.js :
import React, { Component } from 'react';
import {
Route,
Switch,
withRouter,
} from 'react-router-dom';
import HomePage from './pages/home';
import SinglePage from './pages/single';
class App extends Component {
render() {
return (
<Switch>
<div>
<Route exact path="/" component={HomePage} />
<Route path="/:id" component={SinglePage} />
</div>
</Switch>
);
}
}
export default withRouter(App);
Single.js :
import React, { Component } from 'react';
import Details from '../components/details'
import Header from '../components/header'
import { ProgressBar } from 'react-materialize';
class SinglePage extends Component {
constructor(props) {
super(props);
this.state = {
data: { data: null },
}
}
componentDidMount() {
fetch(`http://localhost:1337/${this.props.match.params.id}`)
.then((res) => res.json())
.then((json) => {
this.setState({
data: json,
});
});
}
render() {
const { data } = this.state;
return (
<div>
<h2> SinglePage </h2>
{!data ? (
<ProgressBar />
) : (
<div>
<Header id={this.props.match.params.id} />
<Details item={data} />
</div>
)}
</div>
);
}
}
export default SinglePage;
Header.js :
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { Link, withRouter } from 'react-router-dom';
class Header extends Component {
static propTypes = {
item: PropTypes.shape({
data: PropTypes.string.isRequired,
}).isRequired,
}
render() {
const prev = parseInt(this.props.id) - 1
const next = parseInt(this.props.id) + 1
return (
<div>
<Link to="/"> Retour </Link>
<Link to={`/${prev}`}> Précédent </Link>
<Link to={`/${next}`}> Suivant </Link>
</div>
)
}
}
export default Header;
the solution is pretty-simple. All you need to do is make use of componentWillReceiveProps and check if the param updated, if it did fetch the data again
componentDidMount() {
this.getData(this.props.match.params.id);
}
componentWillReceiveProps(nextProps) {
if(this.props.match.params.id !== nextProps.match.params.id) {
this.getData(nextProps.match.params.id);
}
}
getData = (param) => {
fetch(`http://localhost:1337/${params}`)
.then((res) => res.json())
.then((json) => {
this.setState({
data: json,
});
});
}

Categories