I am having trouble with the Route path <Route path="customers/:id" render={(props) => <CusDataForm {...props}/>}/> in the code below:
import CusDataCtrl from './cusdata/CusDataCtrl'
import CusDataForm from './cusdata/CusDataForm'
class App extends Component {
render() {
return (
<BrowserRouter>
<Switch>
<Route exact path="/customers" component={CusDataCtrl} />
<Route path="customers/:id" render={(props) => <CusDataForm {...props}/>}/>
</Switch>
</BrowserRouter>
);
}
}
export default App;
if I use <Route exact path="/customers/:id" component={CusDataForm} /> the component does render correctly; however, I need to pass some props down to this component.
My calling component is defined like so:
class CusDataGrid extends Component {
constructor(props) {
super(props)
this.state = {data: []}
}
componentDidMount() {
let me = this;
dbFetch("customers",data => me.setState({data:data}));
}
callEdit = e => {
let recid = e.target.getAttribute("data")
this.props.history.push("/customers/"+recid);
}
render() {
const rows = this.state.data.map((row, ndx) => {
return (
<div key={ndx}><button data={row.recordid} className="waves-effect waves-light btn-small" onClick={this.callEdit}>Edit</button></div>
);
});
return (
<div id="cusdata"><div className="data-scrollable">{rows}</div></div>
);
}
};
export default CusDataGrid;
and my target component is:
class CusDataForm extends Component{
componentDidMount = () =>{
this.setState({id: this.props.id ? this.props.id : ""});
}
render(){
return(<div>HELLO</div>)
}
}
export default CusDataForm;
Please let me know what I am doing incorrectly. Thanks!
you can use hook useParams for it
<Switch>
<Route path="/:id" children={<Child />} />
</Switch>
function Child() {
// We can use the `useParams` hook here to access
// the dynamic pieces of the URL.
let { id } = useParams();
return (
<div>
<h3>ID: {id}</h3>
</div>
);
}
official documentation
Related
class Recommended extends Component {
componentDidMount() {}
render() {
return (
<React.Fragment>
<Switch>
<Route
path="/recommended/category/:categoryId"
component={SceneRecommendedCategory}
render={() => {
return <SceneRecommendedCategory />;
}}
/>
<Route exact path="/recommended" component={SceneRecommendedList} />
</Switch>
</React.Fragment>
);
}
}
handleChangeCategory = categoryId => {
const { history } = this.props;
history.push(`/recommended/category/${categoryId}`);
}
<Link to={`/recommended/category/${category.id}`}>A</Link>
Hi, so my problem is i can not redirect between /recommended/category/:categoryId. Url was changed from /recommended/category/1 to /recommended/category/2, but reactjs was not rendered, i can tell there is nothing change.
Please help me out.
My app currently has three components, User to view a person's profile, Self for a user to view their dashboard, notifications, and settings and a login page.
Both User and Self share common components Nav and Side, where User would pass the self object and call the fetchUser action from redux to Nav and Side, while Self would pass the user and self object along with calling the fetchSelf action.
User.js
class User extends React.Component {
componentDidMount() {
this.props.fetchUser(this.props.username);
}
render() {
const { page, self, user } = this.props
return (
<main>
<Nav
self={self}
/>
<Side
page={page} user={user}
/>
<div>
.....
</div>
</main>
)
}
}
const mapStateToProps = state => ({
page: state.store.page,
self: state.store.self
});
export default connect(mapStateToProps, {fetchUser})(User);
Self.js
class Self extends React.Component {
componentDidMount() {
this.props.fetchSelf();
}
render() {
const { page, self } = this.props
return (
<main>
<Nav
self={self}
/>
<Side
page={page} self={self}
/>
{
tab === 'Dashboard'
? <Dashboard />
: tab === 'Notifications'
? <Notifications />
: tab === 'Settings'
? <Settings />
: null
}
</main>
)
}
}
const mapStateToProps = state => ({
page: state.store.page,
self: state.store.self
});
export default connect(mapStateToProps, {fetchSelf})(Self);
Login.js
class Login extends React.Component {
.....
handleChange = event => {
.....
}
render() {
return (
<div id="login">
.....
</div>
)
}
Side.js
const Side = (props) => {
const { page, self, user } = props;
return (
<aside>
{
page === 'user'
? <div>
<img src={'img/' + user.profile.avatar} alt={`${user.username}'s avatar`} />
</div>
: <div>
<img src={'img/' + self.profile.avatar} alt={`${self.username}'s avatar`} />
<div>
}
</aside>
)
}
What I'd like to do here is instead of using react-router like this
<BrowserRouter>
<Switch>
<Route path="/login" exact={true} component={Login} />
<Route path="/self" exact={true} component={Self} />
<Route path="/:username" component={User} />
</Switch>
</BrowserRouter>
I'd want to be able to do something like this instead
const LayoutForLoginAndSignup = (props) => {
return (
<div class="loginOrSignUp">
<ComponentToBePassedIn />
</div>
)
}
class LayoutWithNavAndSide extends React.Component {
componentDidMount() {
this.props.fetchSelf();
// this.props.fetchUser('someusername')
}
render() {
return (
<main>
<Nav self={this.props.self} />
<Side page={this.props.page} self={this.props.self} user={this.props.user} />
{Content of component that was passed in}
</main>
)
}
}
const mapStateToProps = state => ({
page: state.store.page,
self: state.store.self,
user: state.store.user
});
export default connect(mapStateToProps, {fetchUser, fetchSelf})(LayoutWithNavAndSide);
<BrowserRouter>
<Switch>
<LayoutForLoginAndSignup path="/login" exact={true} component={Login} />
<LayoutWithNavAndSide path='/self' component={Self} />
<LayoutWithNavAndSide path="/:username" component={User} />
</Switch>
</BrowserRouter>
Here's where I get confused as I'm still new to react/redux/react router, how do I get the component of User or Self to show up in the layout? how do I get it to call fetchUser (on componentDidMount) only if someone is accessing /someuser vice versa with fetchSelf only when they goto the /self route? is it possible to do the layout as a function rather than a class?
Create the component you will make a route for that contains both layouts and a condition.
const Layout = (props) => (
{props.layout ? <SelfLayout /> : <UserLayout />}
)
Create two layouts.
const SelfLayout = () => (
<div> Self Layout </div>
)
const UserLayout = () => )
<div> User Layout </div>
)
Create your route.
<Route path={`/layout/${this.state.somelayout}`} render={() => <Layout
layout={this.state.somelayout}/>} />
this.state.somelayout should be the conditional that decides which layout we are on and you can tailor it to the needs of your app this is just a guideline.
I am doing a simple react application and I have an App component which keeps track of the state and then renders it. At first the state it is an empty string. Afterwards when I access the /signin I click on a button that changes the state from "" to "Marc" and pass it via props to the Profile component who renders the name of the user on its page. The problem is that it does not change the state and it is always "". I tried to debug and the state is always "" but the method setState is actually called. So i do not know why. Can anyone help me? Thanks in advance and I enclose the code.
APP:
export default class App extends React.Component {
constructor(props) {
super(props);
this.state = {
session: ""
};
this.updateUser = this.updateUser.bind(this);
}
updateUser() {
this.setState({
session: "Marc"
});
}
render() {
return(
<BrowserRouter>
<Switch>
<Route path exact='/' component={Home}/>
<Route path='/profile' render={(props) => (
<Profile session={this.state.session} />
)}/>
<Route path='/signin' render={(props) => (
<SignIn onClick={this.updateUser} />
)}/>
</Switch>
</BrowserRouter>
);
}
}
SIGNIN:
export default class SignIn extends React.Component{
constructor(props) {
super(props);
this.handleClick = this.handleClick.bind(this);
}
responseGoogle (googleUser) {
const mail = googleUser.profileObj.email;
const familyName = googleUser.profileObj.familyName;
const name = googleUser.profileObj.name;
//this.changeName(mail);
alert("Mail: " + mail + "\n" + "Nom i Cognoms: " + name + "\nSuccessfully Logged In");
}
handleClick() {
this.props.onClick();
}
render () {
return (
<div>
<GoogleLogin
clientId="CLIENTID"
onSuccess={this.responseGoogle}
onFailure={this.responseGoogle}
buttonText="Google"/>
<button onClick={this.handleClick}>Instant User</button>
</div>
);
}
}
PROFILE:
export default class Profile extends React.Component {
constructor(props) {
super(props)
}
render() {
return(
<h1>I am {this.props.session} User</h1>
);
}
}
In your case when at the SignIn component, onClicking the button will update the State correctly, but when you try to visit another page say Profile by manually entering the URL in browser, your state change will be lost and the state will be reinitialized as you session has changed.
You should instead try to navigate Programatically , for which you could refer the following answer on StackOverflow:
Programatically Routing based on a condition with react-router
In short In SignIn component you will have
class SignIn extends React.Component {
...
handleClick() {
this.props.onClick();
this.props.history.push('/profile');
}
...
export default withRouter(SignIn);
The above is what I will recommend you to do, or else for testing you can have a Link component and navigate using that
render() {
return(
<BrowserRouter>
<div>
<Link to="/profile">Profile</Link>
<Switch>
<Route path exact='/' component={Home}/>
<Route path='/profile' render={(props) => (
<Profile session={this.state.session} />
)}/>
<Route path='/signin' render={(props) => (
<SignIn onClick={this.updateUser} />
)}/>
</Switch>
</div>
</BrowserRouter>
);
}
My application use react-router to manage user navigation and now I need add unit tests bu I'm stuck on how to change route.
My <App /> is (simplified):
class AppUser extends Component {
render() {
return (
<div className="layout">
{this.props.children}
</div>
);
}
}
class Initial extends Component {
render() {
return (
<div className="initial" />
);
}
}
export default class App extends Component {
render() {
let masterPageBase = (props) => (
<AppUser>
{props.children}
</AppUser>
);
let notFound = () => (
<div>
<h1>Not found!</h1>
</div>
);
<Router history={browserHistory}>
<Route path="/" component={masterPageBase}>
<IndexRoute component={Initial} />
<Route path="*" component={notFound} />
</Route>
</Router>
}
}
And my test is:
describe('<App />', () => {
it('user', () => {
const wrapper = shallow(<App />);
// FIXME This fails
expect(wrapper.find('AppUser').length).toEqual(1);
});
});
How can I change the route so that will be an existing child.
This is how you can fake a route in your tests:
There is a module called history which you can use to create a fake browser history in your tests. In order to apply it, you need to make your router parametric in the history it uses, like this:
export default class App extends Component {
render() {
createRouter(browserHistory);
}
}
export function createRouter(history) {
let masterPageBase = (props) => (
<AppUser>
{props.children}
</AppUser>
);
let notFound = () => (
<div>
<h1>Not found!</h1>
</div>
);
return <Router history={history}>
<Route path="/" component={masterPageBase}>
<IndexRoute component={Initial} />
<Route path="*" component={notFound} />
</Route>
</Router>
}
In your tests, you can then use the history module to create a fake history:
import { useRouterHistory } from "react-router";
import createMemoryHistory from "history/lib/createMemoryHistory";
function navigatingTo(path) {
return mount(createRouter(useRouterHistory(createMemoryHistory)(path)));
}
describe('Router', () => {
it('user', () => {
expect(navigatingTo("/").find('AppUser').length).toEqual(1);
});
});
PS: If you run these tests in node.js then you need to make use of jsdom in order for enzyme's mount() to work.
My Movielist component looks a bit like:
componentDidMount() {
fetch('http://localhost:3335/movies')
.then(function(response) {
return response.json()
}).then((movies) => {
this.setState({ movies });
}).catch(function(ex) {
console.log('parsing failed', ex)
})
}
renderMovie(movie) {
return (
<Movie movie={movie} key={movie.id}></Movie>
);
}
render() {
return (
<div className="movies columns is-multiline">
{ this.state.movies.map(this.renderMovie) }
</div>
);
}
My Movie component has a <Link> :
shouldComponentUpdate() {
debugger;
if (this.props.params) {
let activeMovie = find(this.state.movies, {'id': this.props.params.id});
debugger;
this.setState({ movies: activeMovie });
}
}
render() {
return (
<Link to={`/movies/${this.props.movie.id}`}/>
...
In my index.js I've setup the following routes:
ReactDOM.render((
<Router history={hashHistory}>
<Route path="/" component={App}>
<Route path="/movies/(:id)" component={Movie}/>
</Route>
</Router>
),
document.getElementById('root')
);
I would like every time I click on The <Link>, to get the param.id and show only that Movie (with something like lodash find):
let activeMovie = find(this.state.movies, {'id': this.props.params.id});
Unfortunately this.props.params is undefined.
basically just have only one instance of the Movie component loaded in memory. (ideally without losing the previous movies so avoiding a new call everytime I go to the list view)
That's like a Todo app but with Movies instead of Todos..
Your route params are not passed to your Movie Component and you are trying to access it in Movie Component. The params only passed to Movielist Component.
MovieList
renderMovie(movie) {
return (
<Movie
params={this.props.params}
movie={movie}
key={movie.id}
></Movie>
);
}
You should remove the function shouldComponentUpdate in your Movie Component. Because when the url changed. The params will be passed to your MovieList Component and componentWillReceiveProps will receive the params. So you can directly update the MovieList there and you don't need to pass the params to your Movie Component.
MovieList
componentWillReceiveProps(nextProps) {
let activeMovie = find(this.state.movies, {'id': nextProps.params.id});
this.setState({ movies: activeMovie, }
}
But this will lose your movies state. So I think you need to add a nested route like below and have a separate page for Movie Details.
ReactDOM.render((
<Router history={hashHistory}>
<Route path="/" component={App}>
<Route path="/movies" component={MovieList}>
<Route path="/:id" component={Movie}>
</Route>
</Route>
</Router>
),
document.getElementById('root')
);
const {
Router,
Route,
IndexRoute,
Redirect,
Link,
IndexLink,
browserHistory
} = ReactRouter;
const {
Component,
} = React;
class App extends Component {
constructor(props) {
super(props);
}
render() {
return (
<div className="container">
{ React.Children.toArray(this.props.children) }
</div>
)
}
}
class MovieList extends Component {
constructor(props) {
super(props);
this.state = {
movies: []
};
this.renderMovies = this.renderMovies.bind(this);
}
componentDidMount() {
this.setState({
movies: [
{ id: '1', name: 'Fantastic Beasts And Where To Find Them'},
{ id: '2', name: 'Ouija: Origin Of Evil'},
{ id: '3', name: 'Marvel\'s Doctor Strange'}]
})
}
renderMovies() {
return this.state.movies.map(movie => <li key={movie.id}><Link to={`/js/${movie.id}`}>{movie.name}</Link></li>)
}
render() {
return (
<div>
<h1>Movies</h1>
<ul>
{this.renderMovies()}
</ul>
</div>
)
}
}
class Movie extends Component {
constructor(props) {
super(props);
}
render() {
return (
<div>{this.props.params.movieId}</div>
);
}
}
ReactDOM.render((
<Router history={browserHistory}>
<Route path="/js" component={App}>
<IndexRoute component={MovieList} />
<Route path="/js/:movieId" component={Movie} />
</Route>
</Router>
), document.getElementById('root'))
I have created a working jsbin example. Hope it helps.