How to get Routing History to Components not in Route? - reactjs

I have some components that are not in my route(they are components to load up some part of my site but have nothing to do with navigation).
I however want to have the route history available to these components as some of the do ajax requests and if the user has lost authentication I want to kick them back to my home page.
I have no clue though how to pass the history to components so I could something like
this.props.history.replace(null, "/")
I am using: https://github.com/reactjs/react-router
Edit
import React from 'react';
import {connect} from 'react-redux';
import {bindActionCreators} from 'redux';
import * as ReactRouter from "react-router";
class App extends React.Component {
componentWillReceiveProps(nextProps) {
if (localStorage.accessToken === undefined) {
//nextProps.history.replace(null, "/");
}
}
render() {
return (
<div>
<NavigationContainer route={this.props.route} /> // want to pass history into this component so I can use it
</div>
);
}
}
function mapStateToProps(state) {
return {
//states
};
}
function matchDispatchToProps(dispatch) {
return bindActionCreators({
//binding
}, dispatch);
}
export default connect(mapStateToProps, matchDispatchToProps)(App);
Edit 2
Here is my NaviagationContainer
import React from 'react';
import {connect} from 'react-redux';
import {bindActionCreators} from 'redux';
import { IndexLink, withRouter } from 'react-router';
class SideNavContainer extends React.Component {
componentWillMount() {
let props = this.props;
this.props.fetchStorage().then(function (response) {
//stuff
}).catch(function (response) {
// here is where I want to use it
if(response.response.status == 401) {
props.router.replace(null, "/");
}
});
}
render() {
return (
// return
)
}
}
function mapStateToProps(state) {
return {
//reducers
};
}
function matchDispatchToProps(dispatch) {
return bindActionCreators({
//bind
}, dispatch);
}
export default withRouter(connect(mapStateToProps, matchDispatchToProps)(SideNavContainer));
my router
ReactDOM.render(
<Provider store={store}>
<Router history={hashHistory}>
<Route path="/" component={Layout}>
<IndexRoute component={Home}></IndexRoute>
<Route path="app" name="app" component={App}></Route>
</Route>
</Router>
</Provider>,
document.getElementById('root')
);
Seems like when using withRouter. Replace() does not work for me at all. Not in my NaivgationContainer nor in my App Component.

Yes, you can use push()/replace()
https://github.com/reactjs/react-router/blob/master/docs/API.md#pushpathorloc
This might give you a better answer: https://stackoverflow.com/a/31079244/5924322
//this HoC gives you the `router` which gives you push()
import { withRouter } from 'react-router'
Edit:
import React from 'react';
import {connect} from 'react-redux';
import {bindActionCreators} from 'redux';
import { withRouter } from "react-router";
class App extends React.Component {
componentWillUpdate(nextProps) {
if (localStorage.accessToken === undefined) {
nextProps.router.replace(null, "/");
}
}
render() {
return (
<div>
<NavigationContainer route={this.props.route} /> // want to pass history into this component so I can use it
</div>
);
}
}
function mapStateToProps(state) {
return {
//states
};
}
function matchDispatchToProps(dispatch) {
return bindActionCreators({
//binding
}, dispatch);
}
export default withRouter(connect(mapStateToProps, matchDispatchToProps)(App));

Related

How should you do page redirects on action completion in flux based react using react-router and hashHistory

The primary problem is that doing a history.push() as part of a dispatch is not allowed because you can't do a dispatch within a dispatch. As an alternative to actually doing the redirect in the action function itself I'm currently passing callback functions to action methods to do calls to history methods. I don't think this is a good way to do things and I'm looking for a better way to do it. I'm doing this is in quite a few places including in the case of getting 401 errors - I clear session info and go to the login page. Here is how my code looks.
client.js
import React from "react";
import ReactDOM from "react-dom";
import { Router, Route, IndexRoute, hashHistory } from "react-router";
import CreateEntity from "./pages/CreateEntity";
import Dashboard from "./pages/Dashboard";
import Entity from "./pages/Entity";
const app = document.getElementById('app');
ReactDOM.render(
<Router history={hashHistory}>
<Route path="/" component={Layout}>
<Route path="dashboard" component={Dashboard}></Route>
<Route path="createEntity" component={CreateEntity}></Route>
<Route path="entity/:name" component={Entity}></Route>
</Route>
</Router>,
app);
pages/Entity.js
import React from "react";
import * as EntityActions from "../actions/EntityActions";
import EntityStore from "../stores/EntityStore";
export default class Entity extends React.Component {
constructor() {
super();
this.finishDelete = this.finishDelete.bind(this);
this.state = {
description: undefined,
name: undefined,
id: undefined,
};
}
delete() {
EntityActions.deleteProject('mbrady', this.state.id, this.finishDelete)
}
finishDelete() {
this.props.history.push("dashboard")
}
componentWillMount() {
EntityStore.on("change", this.getProject);
this.refresh()
}
componentWillUnmount() {
EntityStore.removeListener("change", this.getProject);
}
render() {
return (
<div>
<button class="btn btn-default" onClick={this.delete.bind(this)}>
<span class="glyphicon glyphicon-trash"></span>
</button>
</div>
);
}
}
actions/EntityActions.js
import dispatcher from "../dispatcher";
import axios from "axios";
import * as config from '../AppConfig'
export function deleteEntity(user, id, delete_callback) {
dispatcher.dispatch({type: "DELETE_ENTITY"});
const url = "http://localhost:"+port+"/"+config.appName+"/some_url";
axios.delete(url)
.then((response) => {
dispatcher.dispatch({type:"DELETE_ENTITY_FULFILLED", payload: response.data});
console.log(delete_callback)
delete_callback(response.data);
})
.catch((err) => {
dispatcher.dispatch({type:"DELETE_ENTITY_FAILED", payload: err})
})
}
stores/EntityStore.js
import { EventEmitter } from "events";
import dispatcher from "../dispatcher";
class CurrentProjectStore extends EventEmitter {
constructor() {
super()
this.project = undefined;
}
handleActions(action) {
switch(action.type) {
case "DELETE_PROJECT": {
break;
}
case "DELETE_PROJECT_FAILED": {
break;
}
case "DELETE_PROJECT_FULFILLED": {
this.project = undefined
break;
}
}
}
}
const projectStore = new CurrentProjectStore;
dispatcher.register(projectStore.handleActions.bind(projectStore));
export default projectStore;

Connected component not receiving store props n Redux

I was doing a bit of refactoring and tried connecting a higher level component to redux using connect() but the component I'm connecting keeps giving me empty props.
I've included the relevant code, I've structured my redux reducers into a ducks format, so the actions/creators and reducers are in one module file.
The files are containers/login.js, presentation/login.js, presentation/logins.js, app.js and the root index.js.
When I decided to rename some actions, files and reducers, moved the connect to a higher component, the connection stopped working and now I have empty props.
Help much appreciated.
// containers/login.js
import { connect } from 'react-redux';
import { withRouter } from 'react-router-dom'
import { fetchPage } from '../redux/modules/Login';
import Login from '../presentation/Login';
const mapStateToProps = (state) => {
return {
page: state.page,
forms: state.forms
}
}
const mapDispatchToProps = (dispatch) => {
return {
fetchPage: () => dispatch(fetchPage())
} // here we're mapping actions to props
}
export default connect(
mapStateToProps,
mapDispatchToProps
)(Login);
// redux/modules/login.js
import fetch from 'cross-fetch';
const RECIEVE_FORM = 'RECIEVE_FORM';
export const receiveForm = (response) => ({
type: RECIEVE_FORM,
forms: response.forms
})
const initialState = {
page: "",
forms: []
}
// MIDDLEWARE NETWORK REQUEST DISPATCHER
export const fetchPage = () => {
return dispatch => {
return fetch('http://localhost:3001/login')
.then(
response => response.json(),
)
.then(
response => dispatch(receiveForm(response))
)
}
}
// REDUCER COMPOSITION CALL EXISTING REDUCERS
// REDUCER COMPOSITION PATTERN
// ACCUMULATIVE ACTION REDUCER
export default function Login(state = initialState, action){
switch (action.type){
case RECIEVE_FORM:
return {
...state,
forms: action.forms
}
default:
return state;
}
}
// presentation/login.js
import React, { Component } from 'react';
import styled from 'styled-components';
import Wrapper from '../components/Wrapper';
import Card from '../components/Card';
import Text from '../components/Text';
import Logo from '../components/Logo';
import FormGroup from '../components/FormGroup';
const WrapperLogin = styled(Wrapper)`
.login__card{
padding: 4.5rem 2.5rem 2rem 2.5rem;
}
`;
const BoxLogo = styled.div`
.login__logo{
display: block;
margin: 0 auto;
}
`;
export default class Login extends Component{
componentDidMount() {
console.log(this.props)
//this.props.fetchPage();
}
render(){
return(
<main>
<WrapperLogin className="login">
<Card className="login__card">
<BoxLogo>
<Logo className="login__logo" width={187.36} height={76.77} />
</BoxLogo>
<FormGroup name="login" className="login_formGroup" />
</Card>
<Text primitive="p" margin='4px 0 0 0' size="0.8rem" textAlign="center" display='block'>Brought to you by WORLDCHEFS</Text>
</WrapperLogin>
</main>
)
}
}
// app.js
// manage routes here
//import _ from 'lodash';
import React, { Component } from 'react'
import { BrowserRouter as Router, Route, Redirect } from 'react-router-dom';
import { ThemeProvider } from 'styled-components';
import Login from './presentation/Login';
type Props = {
}
type State = {
mode: string
};
export default class App extends Component <Props, State> {
constructor(){
super();
this.state = {
...this.state,
mode: 'mobile'
}
}
render(){
return(
<ThemeProvider theme={{ mode: this.state.mode }}>
<Login />
</ThemeProvider>
)
}
}
// root
import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';
import configureStore from './redux/configureStore';
import registerServiceWorker from './registerServiceWorker';
import App from './App';
import { injectGlobal } from 'styled-components';
import styles from './assets/styles';
const store = configureStore();
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>
, document.getElementById('root')
);
The reason your props in Login component are empty is because you are not actually using the connected Login container, As you have mentions its in containers/login
So in your App.js change the import of login from ./presentation/login to
import Login from '/path/to/containers/Login';
You have imported presentation component in your app.js rather than container component. Please import your container component like below
import Login from './containers/login.js';
This will solve the problem as per my understanding from your code

Where to set the visibility of Network Indicator with Redux?

I have several actions in my application which fetches data from an API. I am setting a "loading"-attribute in my redux-store, if the action is fetching. Now I want to show a network indicator the app is fetching data.
I found a quick&dirty solution but I am sure, that this is not the way to do it:
import React, { Component } from 'react';
import { AppRegistry, StatusBar } from 'react-native';
import { Provider } from 'react-redux';
import { createStore, applyMiddleware } from 'redux';
import thunk from 'redux-thunk';
import reducer from './app/reducers';
import App from './app/providers/App';
const createStoreWithMiddleware = applyMiddleware(thunk)(createStore);
const store = createStoreWithMiddleware(reducer);
class AppName extends Component {
render() {
store.subscribe(() => {
if(
store.getState().dishes.loading
|| store.getState().deals.loading
) StatusBar.setNetworkActivityIndicatorVisible(true);
else StatusBar.setNetworkActivityIndicatorVisible(false);
});
return (
<Provider store={store}>
<App />
</Provider>
);
}
}
AppRegistry.registerComponent('AppName', () => AppName);
What is the correct way to hook such a listener?
To avoid calling StatusBar.setNetworkActivityIndicatorVisible too many times, you can watch the changes in your state using componentWillReceiveProps in your connected component.
import AppContainer from './containers/AppContainer';
class AppName extends Component {
render() {
return (
<Provider store={store}>
<AppContainer />
</Provider>
);
}
}
containers/AppContainer.js
import App from '../components/App.js';
const mapStateToProps = state => ({
loading: state.dishes.loading || state.deals.loading
});
export default connect(mapStateToProps)(App);
components/App.js
class App extends Component {
componentWillReceiveProps(nextProps) {
if (!this.props.loading && nextProps.loading) {
// Changing from `not loading` to `loading`
StatusBar.setNetworkActivityIndicatorVisible(true);
} else if (this.props.loading && !nextProps.loading) {
// Changing from `loading` to `not loading`
StatusBar.setNetworkActivityIndicatorVisible(false);
}
}
// ...
}

GraphQL query to pass mapQueriesToProps via connect() not firing

So, I'm attempting to set the results of a graphQL query to mapQueriesToProps, and then pass the result onto the main interface/view of my app via connect(), but the query is not firing (as confirmed by devTools):
My code is as follows:
App.js
import React from 'react';
import { bindActionCreators } from 'redux';
import { connect } from 'react-redux';
// import { connect } from 'react-apollo';
import {
// gql,
graphql,
withApollo
} from 'react-apollo';
import gql from 'graphql-tag';
import * as actionCreators from '../actions/actionCreators';
// import client from '../apolloClient';
// import ApolloClient from 'apollo-client';
/*
Components
This is where the actual interface / view comes into play
Everything is in Main - so we import that one
*/
import Main from './Main';
const allPostsCommentsQuery = gql`
query allPostsCommentsQuery {
allPostses {
id
displaysrc
caption
likes
comments {
id
posts {
id
}
text
user
}
}
}
`;
const mapQueriesToProps = ({ ownProps, state }) => {
return {
posts: {
query: gql`
query allPostsCommentsQuery {
allPostses {
id
displaysrc
caption
likes
comments {
id
posts {
id
}
text
user
}
}
}
`,
// variables: {
// },
// forceFetch: false, // optional
// returnPartialData: false, // optional
},
};
};
export function mapDispatchToProps(dispatch) {
return bindActionCreators(actionCreators, dispatch);
}
var App = connect(
mapQueriesToProps,
mapDispatchToProps
)(Main);
export default App;
Main.js
import React from 'react';
import { Link } from 'react-router';
const Main = React.createClass({
render() {
return (
<div>
<h1>
<Link to="/">Flamingo City</Link>
</h1>
{/* We use cloneElement here so we can auto pass down props */}
{ React.cloneElement(this.props.children, this.props) }
</div>
);
}
});
export default Main;
My app.js into which App.js is imported into:
import React from 'react';
import { render } from 'react-dom';
import { Provider } from 'react-redux';
import { Router, Route, IndexRoute } from 'react-router'
import 'babel-polyfill';
import { ApolloProvider, graphql, gql } from 'react-apollo';
import client from './apolloClient';
/*
Import Components
*/
import App from './components/App';
import Single from './components/Single';
import PhotoGrid from './components/PhotoGrid';
/* Import CSS */
import css from './styles/style.styl';
/* Import our data store */
import store, { history } from './store';
/*
Error Logging
*/
import Raven from 'raven-js';
import { sentry_url } from './data/config';
if(window) {
Raven.config(sentry_url).install();
}
/*
Rendering
This is where we hook up the Store with our actual component and the router
*/
render(
<ApolloProvider store={store} client={client}>
{ /* Tell the Router to use our enhanced history */ }
<Router history={history}>
<Route path="/" component={App}>
<IndexRoute component={PhotoGrid} />
<Route path="/view/:postId" component={Single}></Route>
</Route>
</Router>
</ApolloProvider>,
document.getElementById('root')
);
What am I overlooking?
I resolved the issue by doing the following:
import React from 'react';
import { bindActionCreators } from 'redux';
import { connect } from 'react-redux';
import {
gql,
graphql
} from 'react-apollo';
import * as actionCreators from '../actions/actionCreators';
/*
Components
This is where the actual interface / view comes into play
Everything is in Main - so we import that one
*/
import Main from './Main';
const allPostsCommentsQuery = graphql(gql`
query allPostsCommentsQuery {
allPostses {
id
displaysrc
caption
likes
comments {
id
posts {
id
}
text
user
}
}
}
`);
/*
This will bind our actions to dispatch (make the fire-able)
and make the actions available via props
*/
export function mapDispatchToProps(dispatch) {
return bindActionCreators(actionCreators, dispatch);
}
const App = connect(
mapDispatchToProps
);
export default App(allPostsCommentsQuery(Main));

Props.store does not work on child components

please help.
this.props.store does not work on child components.
but the connect(mapStateToProps, mapDispatchToProps) ... is working fine.
why doesn't work only child components?
1. parent code (is working fine)
import React from 'react';
import ReactDOM from 'react-dom';
import { Home } from './container/home/index';
import { ChildrenComponent } from './container/childrenComponent';
import { Match, Miss } from 'react-router';
import { BrowserRouter as Router } from 'react-router';
import { createStore } from 'redux';
import { Provider } from 'react-redux';
import Reducers from './reducers';
const store = createStore(Reducers);
store.subscribe(() => console.log('ㅡㅡㅡㅡㅡ store was updated ㅡㅡㅡㅡㅡ'));
store.subscribe(() => console.log(store.getState()));
store.subscribe(() => console.log('ㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡ'));
ReactDOM.render(
<Provider store={store}>
<Router>
<div>
<Match pattern="/" component={Home} />
<Match pattern="/ChildrenComponent" component={ChildrenComponent} />
</div>
</Router>
</Provider>,
document.getElementById('root')
);
2. Children Component (is not working only 'this.props.store ..... ')
import React, { Component } from 'react';
import $ from 'jquery';
import { connect } from 'react-redux';
class ChildrenComponent extends Component {
constructor (props) {
super (props);
}
render (
console.log(this.props.store) // undfined
console.log(this.props.store.getState()) // does not working
const mapStateToProps = (state) => {
return {
// .... is working fine
}
}
const mapDispatchToProps = (dispatch) => {
return {
// .... is working fine
}
}
)
return (
<divHellow world</div>
)
}
expoert default connect(mapStateToProps, mapDispatchToProps)(ChildrenComponent);
Trying to access the store and its state directly defeats the entire purpose of using Redux, and React-Redux in particular. You're supposed to use mapStateToProps to access parts of state that you need in each particular component.
Put mapStateToProps and mapDispatchToProps outside the component class and put your jsx inside the render method of your component class.
class ChildrenComponent extends Component {
render () {
return (
<divHellow world</div>
)
}
}
const mapStateToProps = (state) => {
return {
// ....
}
}
const mapDispatchToProps = (dispatch) => {
return {
// ....
}
}
export default connect(mapStateToProps, mapDispatchToProps)(ChildrenComponent);

Resources