I'm trying to display current user name in a different component but It's not working, what am I doing wrong ?
Here's where I'm initialising the username (App.js)
class App extends Component{
constructor(props) {
super(props);
this.state = {
logged_in: localStorage.getItem('token') ? true : false,
username: ''
};
}
componentDidMount() {
if (this.state.logged_in) {
fetch('http://localhost:8000/user/current_user/', {
headers: {
Authorization: `JWT ${localStorage.getItem('token')}`
}
})
.then(res => res.json())
.then(json => {
this.setState({ username: json.username });
console.log(json.username);
});
}
}
render() {
return (
<div className="App">
<Router history={history}>
<Switch>
<Route exact path="/" component={First} />
<Route path="/home" component={Projects} />
<Route path="/menu" component={MenuH} />
<Route path ="/createProject" component={CreateP} />
<Route path ="/connect" component={Newacount} />
<Route component={Notfound} />
</Switch>
</Router>
</div>
);
}
}
...
And here's how I'm calling it in a different component (projects)
{this.state.username}
Here's a good discussion about How to pass component via react router.
When following the discussion, you code will look like:
<div className="App">
<Router history={history}>
<Switch>
<!-- Pass username as props here -->
<Route path="/your-route" render={()=><Projects username={this.state.username}/>} />
</Switch>
</Router>
</div>
And into your Projects component, you retrieve it with this.props.username.
Your Projects component does not have access to the state in App.
You should do this with a state manager like React Context or Redux
This won't work, you cannot just access the parent's state directly in all the child components,
you have to send the parents state down to the child components as a prop to access them.
In scenario like this where you want to use one state in all of your child components the good thing is to use Redux, it will provide you a global state which will be accessible in all of your components.
Also you can solve this problem with React's Context API.
try sending state as props for child and access it from child! or to access state directly u can use redux store or context api from react!
<Route path="/home" render={()=><Home username={this.state.username} />} />
Related
I'm trying to figure out how to change the state of my Dashboard when a new route is clicked. This new route change needs to update the TopMenu component.
This is the code for the Dashboard
class Dashboard extends Component {
constructor (props) {
super(props)
this.state = {
selectedMenuItem: 'Now'
}
}
render () {
return (
<Router>
<div id='dashboard-container'>
<LeftMenu/>
<div className='column'>
<TopMenu selectedMenuItem={this.state.selectedMenuItem} />
<div id='content-container'>
<div>
<Switch>
<Route exact path='/' component={Now} />
<Route exact path='/surveys' component={Surveys} />
<Route exact path='/my-questions' />
<Route exact path='/feedback' />
<Route exact path='/logout' />
<Route render={function () {
return <p>Not Found</p>
}} />
</Switch>
</div>
</div>
</div>
</div>
</Router>
)}}
This is the code for the TopMenu
class TopMenu extends Component {
render () {
return (
<h3>{this.props.selectedMenuItem}</h3>
)
}
}
How do I listen to a change in React Router such that I can change the 'selectedMenuItem' state variable in the Dashboard and pass that to TopMenu.
Thanks!
Assuming you are using version 4, check the usage of match in React Router 4: https://reacttraining.com/react-router/web/api/match
match will include all the path information you will need to handle route changes. You can access it by calling this.props.match in your top level page component.
I had the same problem and I solved it with withRouter which is build for integrate router with redux, the only thing you need to do is wrap your connect with withRouter. for more information read redux integration document
i want redirect to "/user". i write but this not work.
how to correctly redirect to the right page
onClick = (e) => {
this.setState({ errorLoad: false});
getPlayerInfo(this.state.id).then(data => {
if(data.success == false) {
this.setState({ errorLoad: true});
return;
}
this.setState({ user: data.player});
console.log(data);
<Redirect to="/user"/>
});
}
My router list. Among them there is a router with the path "/ user"
<Route path="/user" render={(props) => <User {...props} user={this.state.user} />} />
UPADATE
App.js
The button I click on is in the component <SearchForm/>
render() {
let style = {marginLeft: '20px'};
return (
<div>
<Header source='https://www.shareicon.net/data/2017/02/15/878753_media_512x512.png'/>
<SearchForm onClick={this.onClick} style={style} onChange={this.onHandle} placeholder="search"/>
<Centered style={ {marginTop: '50px'} }>
<BrowserRouter>
<Switch>
<Route exact path='/' component={Startup} />
<Route path="/user" render={(props) => <User {...props} user={this.state.user} />} />
</Switch>
</BrowserRouter>
</Centered>
</div>
);
}
There are two ways to programmatically navigate with React Router - <Redirect /> and history.push. Which you use is mostly up to you and your specific use case.
<Redirect /> should be used in user event -> state change -> re-render order.
The downsides to this approach is that you need to create a new property on the component’s state in order to know when to render the Redirect. That’s valid, but again, that’s pretty much the whole point of React - state changes update the UI.
The real work horse of React Router is the History library. Under the hood it’s what’s keeping track of session history for React Router. When a component is rendered by React Router, that component is passed three different props: location, match, and history. This history prop comes from the History library and has a ton of fancy properties on it related to routing. In this case, the one we’re interested is history.push. What it does is it pushes a new entry onto the history stack - aka redirecting the user to another route.
You need to use this.props.history to manually redirect:
onClick = (e) => {
this.setState({ errorLoad: false});
getPlayerInfo(this.state.id).then(data => {
if(data.success == false) {
this.setState({ errorLoad: true});
return;
}
this.setState({ user: data.player});
console.log(data);
this.props.history.push('/user');
});
}
You should be getting history as a prop from your <Router> component.
EDIT:
Okay thank you for the code update. The SearchForm component is not nested under your BrowserRouter, so it is not getting the history prop. Either move that component inside the BrowserRouter or use the withRouter HOC in SearchForm reacttraining.com/react-router/web/api/withRouter
Option 1: Move SearchForm inside the BrowserRouter
render() {
let style = {marginLeft: '20px'};
return (
<div>
<Header source='https://www.shareicon.net/data/2017/02/15/878753_media_512x512.png'/>
<Centered style={ {marginTop: '50px'} }>
<BrowserRouter>
<SearchForm onClick={this.onClick} style={style} onChange={this.onHandle} placeholder="search"/>
<Switch>
<Route exact path='/' component={Startup} />
<Route path="/user" render={(props) => <User {...props} user={this.state.user} />} />
</Switch>
</BrowserRouter>
</Centered>
</div>
);
}
Option 2: use the withRouter HOC to inject the history prop into SearchForm manually:
import { withRouter } from 'react-router-dom';
class SearchForm extends React.Component { ... }
export default withRouter(SearchForm)
I have several routes rendering the same component. Depending on the route I want the component to fetch different data. However since I keep rendering the same component, React doesn't see any changes to the DOM when I click a Link tag (from my nav bar located in the Layout component) to another route rendering that same component. Meaning the component is not re-rendered with the new data. Here are my routes:
class App extends Component {
render() {
return (
<BrowserRouter>
<Provider store={store}>
<Layout>
<Switch>
<Route exact path="/" component={Home} />
<Route exact path="/fashion" component={PostTypePageContainer} />
<Route exact path="/beauty" component={PostTypePageContainer} />
</Switch>
</Layout>
</Provider>
</BrowserRouter>
);
}
}
export default App;
Here is the PostTypePageContainer component that I want to re-render with the new data each time:
class PostTypePageContainer extends Component {
componentDidMount() {
let route;
switch (this.props.location.pathname) {
case '/fashion':
route = '/fashion';
break;
case '/beauty':
route = '/beauty';
break;
default:
console.log('No data was found');
}
let dataURL = `http://localhost:8888/my-site//wp-json/wp/v2${route}?_embed`;
fetch(dataURL)
.then(res => res.json())
.then(res => {
this.props.dispatch(getData(res));
});
}
render() {
let posts = this.props.postData.map((post, i) => {
return <PostTypePage key={i} props={post} />;
});
return <div>{posts}</div>;
}
}
const mapStateToProps = ({ data }) => ({
postData: data.postData
});
export default connect(mapStateToProps)(PostTypePageContainer);
How do I go about re-rendering that component each time?
This is intended behavior of react-router.
While i suggest you create a HOC to fetch the data from different locations and pass it to the PostTypePageContainer via props, using a key will give you a quick work around that will cause your component to remount.
class App extends Component {
render() {
return (
<BrowserRouter>
<Provider store={store}>
<Layout>
<Switch>
<Route exact path="/" component={Home} />
<Route exact key={uniqueKey} path="/fashion" component={PostTypePageContainer} />
<Route exact key={someOtherUniqueKey} path="/beauty" component={PostTypePageContainer} />
</Switch>
</Layout>
</Provider>
</BrowserRouter>
);
}
}
export default App;
Source: https://github.com/ReactTraining/react-router/issues/1703
I wasn't able to get the <Route key={...} ... /> to work in my case. After trying several different approaches the one that worked for me was using the componentWillReceiveProps function in the reused component. This was getting called each time the component was called from a <Route />
In my code, I did:
componentWillReceiveProps(nextProps, nextContext) {
// When we receive a call with a new tag, update the current
// tag and refresh the content
this.tag = nextProps.match.params.tag;
this.getPostsByTag(this.tag);
}
App.js
import React, { Component } from 'react';
import { Router, Route, browserHistory } from 'react-router';
import Login from './Login';
import Courses from './Courses';
class App extends Component {
constructor() {
super();
this.state = {
username: '',
password: ''
};
}
setCredentials = ({ username, password }) => {
this.setState({
username,
password
});
}
render() {
return (
<Router history={browserHistory}>
<Route
path='/'
component={Login}
setCredentials={this.setCredentials}
/>
<Route
path='/courses'
component={Courses}
credentials={this.state}
/>
</Router>
);
}
}
The Login component takes in credentials from user, verifies them via an API call, updates App's state with them, and then redirects to the Courses component using this.props.router.push('/courses').
The Courses component should take in the updated credentials (i.e. App's updated state) as props, and afterwards perform an API call to fetch that user's courses.
How can I detect the updates in App's state inside the Courses component? Am I doing it wrong altogether?
Pass props to route's component correctly:
<Route
path='/'
component={(props) => <Login setCredentials={this.setCredentials} {...props} />}
/>
and NOT:
<Route
path='/'
component={Login}
setCredentials={this.setCredentials}
/>
The same for the Courses component, you pass extra-props the same way:
<Route
path='/courses'
component={(props) => <Courses credentials={this.state} {...props} />}
/>
And NOT:
<Route
path='/courses'
component={Courses}
credentials={this.state}
/>
Which answers the question:
How can I detect the updates in App's state inside the Courses component?
since crendentials becomes a prop of Courses and its value state of App.
As you said with the courses component reading from props you could have a lifecycle hook like so in the courses component:
componentWillReceiveProps(nextProps) {
if(nextProps.isAuthenticated && this.props.credentials !== nextProps.credentials) {
// perform fetch for courses here
}
}
I would like to have a shared state (list of clients fetched remotely) between 2 sibling Routes : Timesheets and Clients.
I want to try how far i can go with 'pure' React (No Flux architecture).
This example works, but I have an error : browser.js:49 Warning: [react-router] You cannot change <Router routes>; it will be ignored
So, it doesn't seem to like async props.
constructor(props) {
super(props);
this.state = {
clients : []
}
}
componentDidMount() {
fetch("clients.json")
.then(response => response.json())
.then(clients => this.setState({ clients }));
}
render() {
return (
<Router history={browserHistory}>
<Route path="/" component={Header} >
<Route path="timesheet" component={() => (<Timesheets {...this.state} />) }/>
<Route path="clients" component={() => (<Clients {...this.state} />) }/>
</Route>
</Router>
);
}
Is it possible to send async props down to each route?
Or is it possible to set the whole state in the parent route (Header component) and then access this state from each child route (Timesheets and Clients components)?
You can use an high-order component to fetch and inject data to your top level component. Then you can pass props to sub routes via React.cloneElement.
HOC
const FetchHOC = url => Component => class extends React.Component() {
state = { data: null };
componentDidMount() {
fetch(url).then(data => this.setState({ data }));
}
render() {
return <Component {...this.props} {...this.state.data} />;
}
}
Route configuration
<Route path="/" component={FetchHOC('http://some-url')(App)}>
<Route path="subroute1" component={SubRoute1} />
<Route path="subroute2" component={SubRoute2} />
</Route>
Parent route render()
<div className="App">
{this.props.children && React.cloneElement(this.props.children, {
data: this.props.data,
// ...
})}
</div>
You can take a look at reacts Context. Redux also makes use of Context. It allows you to pass some data down to all children. But you should write code to use this data, for instance you have determine contextTypes etc.
You can see details on docs about how to use it.