React - Get data before rendering pages - javascript

I have a situation where I need to fetch updated props within componentWillMount()
My Layout :
#connect((store) => {
//console.log(store);
return {
element: store.elements.elements,
connections: store.connections.connections,
attributes: store.attributes.attributes,
media: store.media.media,
places: store.places.places,
user: store.user.user
};
})
export default class Layout extends React.Component {
componentWillMount() {
this.props.dispatch(fetchUser())
}
componentWillReceiveProps(nextProps) {
this.props.dispatch(updateStoreUser(nextProps.user))
}
shouldComponentUpdate(nextProps) {
console.log(nextProps);
return true;
}
render() {
const { location } = this.props;
return (
<div className="main-container">
<Header/>
<NavConnector/>
{this.props.children}
</div>
);
}
}
{this.props.children} will render pages depending on the route.
I have a BasicInfo Component :
componentWillMount() {
console.log(this.props);
this.props.dispatch(fetchPlaces(1))
}
Where I need to pass user id to fetchPlaces, something like this this.props.dispatch(fetchPlaces(this.props.user.id)
But this.props does not have user.id yet, in the componentWillReceiveProps of the layout I'm updating the store, but gets updated after componentWillMount() of BasicInfo component is called.
The console log :
UPDATE
I have a connector for BasicInfo, this.props.user inside the render method is always undefined. But the store has the user values by now.
Is there any way to pass data from Layout? The place where {this.props.children} is being called? Because that's where the BasicInfoConnector is being called.
import React from "react"
import * as Redux from 'react-redux';
import Basicinfo from "./Basicinfo"
const mapStateToProps = function (store) {
return {
elements: store.elements.elements,
places: store.places.places,
geocode : store.geocode.geocode,
user : store.user.user
};
};
class BasicinfoConnector extends React.Component{
render() {
console.log(this.props.user);
return (
<BasicInfoConnector elements={this.props.elements} places={this.props.places} geocode={this.props.geocode} user={this.props.user}/>
);
}
}
export default Redux.connect(mapStateToProps)(BasicinfoConnector);
Client JS
import React from "react"
import ReactDOM from "react-dom"
import { Router, Route, IndexRoute, hashHistory } from "react-router"
import { Provider } from "react-redux"
import { useScrollToTop } from "scroll-behavior"
import store from "./store"
import '../styles/sass/master/global.scss'
import Layout from "./components/Layout";
import Alerts from "./components/Dashboard/Alerts/Alerts"
import AttributesConnector from "./components/Dashboard/Attributes/AttributesConnector"
import BasicInfoConnector from "./components/Dashboard/Basicinfo/BasicinfoConnector"
import ConnectionsConnector from "./components/Dashboard/Connections/ConnectionsConnector"
import MediaConnector from "./components/Dashboard/Media/MediaConnector"
import Stats from "./components/Dashboard/Stats/Stats"
const app = document.getElementById('app')
ReactDOM.render(
<Provider store={store}>
<Router histroy={hashHistory} onUpdate={() => window.scrollTo(0, 0)}>
<Route path="/" component={Layout}>
<IndexRoute component={BasicInfoConnector}></IndexRoute>
<Route path="location" component={BasicInfoConnector}></Route>
<Route path="alerts" component={Alerts}></Route>
<Route path="attributes" component={AttributesConnector}></Route>
<Route path="connections" component={ConnectionsConnector}></Route>
<Route path="media" component={MediaConnector}></Route>
<Route path="stats" component={Stats}></Route>
</Route>
</Router>
</Provider>,
app);

Assuming that you want to fetch places in componentWillMount, the only solution is to not render the component at all using conditional rendering until the user id is available since componentWillMount gets called only once. Something like this:
{this.props.user?<BasicInfo />:null}
Update:
You need to export a component which is connected (subscribed) to redux store. You are exporting the component which is not connected yet. Just remove the export default before the component declaration
class BasicinfoConnector extends React.Component
and add an export default before the connect statement.
export default Redux.connect(mapStateToProps)(BasicinfoConnector);
This should fix your issue.

Related

React-Router: Programatically routing to a route from the root element

I am trying to route my application so that when it is any route, say / or /listing/foo, I can use the navbar's textbox to search to navigate to /search/${search-term}. However, the conventional methods for programmatically routing use history.push which isn't available in the root element, since the navbar is technically outside the Switch element.
Here's the example code:
import {
HashRouter as Router,
Switch,
Route,
NavLink as Link,
Redirect
} from 'react-router-dom';
import React from 'react';
class App extends React.Component{
constructor(){
this.state = {
searchTerm: '',
}
}
render(){
return(
<>
<nav>
<input className="input" type="text" placeholder="Search listings..." onChange={e => this.startSearching(e)} value={this.state.searchTerm}/>
</nav>
<Switch>
<Route exact path='/' component={Home} />
<Route path='/search/:searchTerm' component={Search} />
</Switch>
</>
)
}
startSearching(){
this.setState({
searchTerm: e.target.value
});
// Omitted debouncing methods
return (<Redirect push to=`/search/${this.state.searchTerm}` />); // Doesn't work
this.props.history.push(`/search/${this.state.searchTerm}`) // this.props is undefined
}
}
What should I do to switch the page inside the <Switch>?
You can wrap your nav component with the withRouter so in your component you can use the history.push
import React from "react";
import PropTypes from "prop-types";
import { withRouter } from "react-router";
// A simple component that shows the pathname of the current location
class Navbar extends React.Component {
static propTypes = {
match: PropTypes.object.isRequired,
location: PropTypes.object.isRequired,
history: PropTypes.object.isRequired
};
render() {
const { match, location, history } = this.props;
return <div>You are now at {location.pathname}</div>;
}
}
// Create a new component that is "connected" (to borrow redux
// terminology) to the router.
const NavbarWithRouter = withRouter(Navbar);
see more detail at https://reactrouter.com/web/api/withRouter

mapStateToProps & mapActionsToProps not firing in react component

I have a component (SearchFilter.js) and am using connect to trigger mapStateToProps and mapActionsToProps on export.
Trouble is, mapStateToProps isn't firing -- no props (neither state nor actions) show up in React DevTools and I can't even console log from inside mapStateToProps.
I've tried looking at various Stack Overflow threads but they mostly seem to be typos, or the actions themselves not working.
What's more, I've got an almost identical redux setup for another component (Counter.js) that woks perfectly.
I think it could have something to do with how I provide the store/route to components (see App.js below) as React.Provider shows up in React DevTools for the Counter but not SearchFilter.
Here's the SearchFilter component:
import React, { Component } from "react";
import { connect } from "react-redux";
import { addSearchTerm } from "../redux/actions/searchActions";
import "../styles/SearchFilter.css";
export class SearchFilter extends Component {
constructor(props) {
super(props);
this.state = {
searchTerm: "",
showFilters: false,
};
}
//various content...
}
const mapStateToProps = (state) => {
console.log(state);
return {
search: state.search,
};
};
const mapActionsToProps = {
addSearchTerm,
};
export default connect(mapStateToProps, mapActionsToProps)(SearchFilter);
App.js
function App() {
return (
<Router>
<Provider store={store}>
<div className="App">
<NavBar />
<Counter />
<Switch>
<Route exact path="/" component={Home} /> // SearchFilter rendered in Home page
<Route path="/account" component={Account} />
</Switch>
</div>
</Provider>
</Router>
);
}
EDIT: Where I've implemented this component in the Home.js view:
export default function Home() {
return (
<div>
<h4>This is the Home page</h4>
<SearchFilter />
<ProfilesList />
</div>
);
}
Try to remove the "export" when you declare the class component, maybe that helps.
change
export class SearchFilter extends Component
to
class SearchFilter extends Component
Try using bindActionCreators from redux library - https://redux.js.org/api/bindactioncreators
To dispatch any action from your component, update mapActionsToProps const in your SearchFilter component.
const mapActionsToProps = (dispatch) => bindActionCreators({
addSearchTerm},dispatch);
};

Unable to console.log props using Link

I am trying to make a single web application. Basically, I am trying to use the ReactRouter to display what is passed as a Route Parameter. However, I am unable to do that. To check if somethings wrong, I decided to console.log out this.props.match, still nothing shows up. Could someone explain what the problem is? And a possible get around?
My code is-
import React from 'react';
export default class Post extends React.Component {
state = {
id: null
}
componentDidMount(props) {
console.log(this.props.match);
}
render = () => {
return (<div>Hello WOrld</div>)
}
}
The App.js file:
import React, { Fragment, Component } from 'react';
import Navbar from './components/Navbar';
import Home from './components/Home';
import Contact from './components/Contact';
import About from './components/About'
import Post from './components/Post';
import { BrowserRouter, Route } from 'react-router-dom';
class App extends Component {
render = () => {
return (
<BrowserRouter>
<div className="App">
<Navbar />
<Route exact path="/" component={Home} />
<Route path="/contact" component={Contact} />
<Route path="/about" component={About} />
<Route path="/:post-id" component = {Post} />
</div>
</BrowserRouter>
);
}
}
export default App;
I just ran your code on my end, it looks like the problem is using /:post-id. I changed that to /:pid and it worked. I got the below object when I console log this.props.match
{
"path":"/:pid",
"url":"/1",
"isExact":true,
"params":
{
"pid":"1"
}
}
I hope this helps.
You have to load the component with router
try this
import { withRouter } from 'react-router-dom';
class Post extends React.Component {
state = {
id: null
}
componentDidMount(props) {
console.log(this.props.match);
}
render = () => {
return (<div>Hello WOrld</div>)
}
}
export default withRouter(Post);

component props returns empty react js

I have created two components App and leftBar. I'm getting the Class component props in App component. But the same prop is empty in LeftBar.
Index.js:
import React from 'react';
import ReactDOM from 'react-dom';
import {BrowserRouter, Route, Switch} from 'react-router-dom';
/** Component **/
import App from './App';
ReactDOM.render(
<BrowserRouter>
<Switch>
<Route path="/:folders/:actions" component={App}></Route>
</Switch>
</BrowserRouter>, document.getElementById('app'));
App.js
import React, {Component} from 'react';
import {BrowserRouter as Router, Route} from 'react-router-dom';
import LeftBar from './components/left-bar';
class App extends Component {
constructor(props){
super(props);
console.log(this.props); //Returns the object
}
render(){
return (
<LeftBar />
)
}
}
LeftBar.js
class LeftBar extends Component{
constructor(props) {
super(props);
this.state = {
leftBar: ["sarath"]
}
console.log(this.props); //this returns empty object {}
}
}
someone point out what was the issue behind this. I want to use props across the multiple components.
react-router will not pass props down the way you are hoping. You need to first pass props to App by doing
// ...
<Route path="/:folders/:actions" component={props => <App {...this.props} />} />
Then you need to pass them along in your child component(s). I am including this.state with the assumption you want to use this inside of LeftBar but you may remove {...this.state} if that is incorrect:
// ...
render () {
return (
<LeftBar
{...this.props}
{...this.state} />
)
}
Because you don't assign any props on <LeftBar />.
You have to pass the required props to this component as well:
render(){
return (
<LeftBar {...this.props}/>
)
}

Programmatically Navigate using react-router

I am developing an application in which I check if the user is not loggedIn. I have to display the login form, else dispatch an action that would change the route and load other component. Here is my code:
render() {
if (isLoggedIn) {
// dispatch an action to change the route
}
// return login component
<Login />
}
How can I achieve this as I cannot change states inside the render function.
Considering you are using react-router v4
Use your component with withRouter and use history.push from props to change the route. You need to make use of withRouter only when your component is not receiving the Router props, this may happen in cases when your component is a nested child of a component rendered by the Router and you haven't passed the Router props to it or when the component is not linked to the Router at all and is rendered as a separate component from the Routes.
import {withRouter} from 'react-router';
class App extends React.Component {
...
componenDidMount() {
// get isLoggedIn from localStorage or API call
if (isLoggedIn) {
// dispatch an action to change the route
this.props.history.push('/home');
}
}
render() {
// return login component
return <Login />
}
}
export default withRouter(App);
Important Note
If you are using withRouter to prevent updates from being blocked by
shouldComponentUpdate, it is important that withRouter wraps the
component that implements shouldComponentUpdate. For example, when
using Redux:
// This gets around shouldComponentUpdate
withRouter(connect(...)(MyComponent))
// This does not
connect(...)(withRouter(MyComponent))
or you could use Redirect
import {withRouter} from 'react-router';
class App extends React.Component {
...
render() {
if(isLoggedIn) {
return <Redirect to="/home"/>
}
// return login component
return <Login />
}
}
With react-router v2 or react-router v3, you can make use of context to dynamically change the route like
class App extends React.Component {
...
render() {
if (isLoggedIn) {
// dispatch an action to change the route
this.context.router.push('/home');
}
// return login component
return <Login />
}
}
App.contextTypes = {
router: React.PropTypes.object.isRequired
}
export default App;
or use
import { browserHistory } from 'react-router';
browserHistory.push('/some/path');
In react-router version 4:
import React from 'react'
import { BrowserRouter as Router, Route, Redirect} from 'react-router-dom'
const Example = () => (
if (isLoggedIn) {
<OtherComponent />
} else {
<Router>
<Redirect push to="/login" />
<Route path="/login" component={Login}/>
</Router>
}
)
const Login = () => (
<h1>Form Components</h1>
...
)
export default Example;
Another alternative is to handle this using Thunk-style asynchronous actions (which are safe/allowed to have side-effects).
If you use Thunk, you can inject the same history object into both your <Router> component and Thunk actions using thunk.withExtraArgument, like this:
import React from 'react'
import { BrowserRouter as Router, Route, Redirect} from 'react-router-dom'
import { createBrowserHistory } from "history"
import { applyMiddleware, createStore } from "redux"
import thunk from "redux-thunk"
const history = createBrowserHistory()
const middlewares = applyMiddleware(thunk.withExtraArgument({history}))
const store = createStore(appReducer, middlewares)
render(
<Provider store={store}
<Router history={history}>
<Route path="*" component={CatchAll} />
</Router
</Provider>,
appDiv)
Then in your action-creators, you will have a history instance that is safe to use with ReactRouter, so you can just trigger a regular Redux event if you're not logged in:
// meanwhile... in action-creators.js
export const notLoggedIn = () => {
return (dispatch, getState, {history}) => {
history.push(`/login`)
}
}
Another advantage of this is that the url is easier to handle, now, so we can put redirect info on the query string, etc.
You can try still doing this check in your Render methods, but if it causes problems, you might consider doing it in componentDidMount, or elsewhere in the lifecycle (although also I understand the desire to stick with Stateless Functional Compeonents!)
You can still use Redux and mapDispatchToProps to inject the action creator into your comptonent, so your component is still only loosely connected to Redux.
This is my handle loggedIn. react-router v4
PrivateRoute is allow enter path if user is loggedIn and save the token to localStorge
function PrivateRoute({ component: Component, ...rest }) {
return (
<Route
{...rest}
render={props => (localStorage.token) ? <Component {...props} /> : (
<Redirect
to={{
pathname: '/signin',
state: { from: props.location },
}}
/>
)
}
/>
);
}
Define all paths in your app in here
export default (
<main>
<Switch>
<Route exact path="/signin" component={SignIn} />
<Route exact path="/signup" component={SignUp} />
<PrivateRoute path="/" component={Home} />
</Switch>
</main>
);
Those who are facing issues in implementing this on react-router v4. Here is a working solution for navigating through the react app programmatically.
history.js
import createHistory from 'history/createBrowserHistory'
export default createHistory()
App.js OR Route.jsx. Pass history as a prop to your Router.
import { Router, Route } from 'react-router-dom'
import history from './history'
...
<Router history={history}>
<Route path="/test" component={Test}/>
</Router>
You can use push() to navigate.
import history from './history'
...
render() {
if (isLoggedIn) {
history.push('/test') // this should change the url and re-render Test component
}
// return login component
<Login />
}
All thanks to this comment: https://github.com/ReactTraining/react-router/issues/3498#issuecomment-301057248
render(){
return (
<div>
{ this.props.redirect ? <Redirect to="/" /> :'' }
<div>
add here component codes
</div>
</div>
);
}
I would suggest you to use connected-react-router https://github.com/supasate/connected-react-router
which helps to perform navigation even from reducers/actions if you want.
it is well documented and easy to configure
I was able to use history within stateless functional component, using withRouter following way (needed to ignore typescript warning):
import { withRouter } from 'react-router-dom';
...
type Props = { myProp: boolean };
// #ts-ignore
export const MyComponent: FC<Props> = withRouter(({ myProp, history }) => {
...
})
import { useNavigate } from "react-router-dom"; //with v6
export default function Component() {
const navigate = useNavigate();
navigate.push('/path');
}
I had this issue and just solved it with the new useNavigate hook in version 6 of react-router-dom

Categories

Resources