I have this URL: /user/Jd5Egbh...
I use react-router-dom for navigation. I have made a UserDetails component.
I use mapStateToProps to get url params like this:
function mapStateToProps({ users }, ownProps) {
return { user: users[ownProps.match.params.uid]};
}
It works fine but I would like to protect this route and allow access only for authenticated users.
In other components, I use a withAuthorization component and compose from recomposing to protect the route.
I tried to combine these two features like below:
export default compose(
withAuthorization(authCondition),
connect(mapStateToProps, { fetchUser })
)(UserDetails);
But ownProps is undefined in mapStateToProps function
How can I access URL params if I use compose to protect route?
EDIT1:
import React from 'react';
import { connect } from 'react-redux';
import { compose } from 'recompose';
import { withRouter } from 'react-router-dom';
import { firebase, db, auth } from '../firebase';
const withAuthorization = (condition) => (Component) => {
class WithAuthorization extends React.Component {
componentDidMount() {
firebase.auth.onAuthStateChanged(authUser => {
if (!condition(authUser)) {
this.props.history.push('/');
} else {
db.onceGetUser(authUser.uid).then(snapshot => {
let user_data = snapshot.val();
if (!user_data.admin) {
auth.doSignOut();
}
});
}
});
}
render() {
return this.props.authUser ? <Component /> : null;
}
}
const mapStateToProps = (state) => ({
authUser: state.auth.authUser,
});
return compose(
withRouter,
connect(mapStateToProps),
)(WithAuthorization);
}
export default withAuthorization;
Related
How can I access match prop inside mapStateToProps?
import React from "react";
import "./collectionpage.styles.scss";
import { connect } from "react-redux";
import { selectCollection } from "../../redux/shop/shop.selector";
import { useMatch } from "react-router-dom";
import Collectionitem from
"../../components/collection-items/Collectionitem.component";
const CollectionPage = ({ collection }) => {
const match = useMatch();
const { title, items } = collection;
return (
<div className="collection-page">
<h2 className="title">{title}</h2>
<div className="items">
{items.map(item => <Collectionitem key={item.id} item={item}/>)}
</div>
</div>
)
}
mapStatetoProps = (state) => {
return ({
collection: selectCollection(match.params.colletionID)(state)
})
}
export default connect(mapStatetoProps)(CollectionPage);
mapStatetoProps allow you pass owner props to. So you can pass match to a component. in this case is CollectionPage
const ComponentA = () => {
const match = useMatch();
return (<CollectionPage match={match}/>)
}
then:
const mapStateToProps = (state, ownProps) => {
return {
collection: ownProps.match.params.colletionID
}
}
react-router-dom#5
Since it appears you are using react-router-dom#5 you'll have the withRouter Higher Order Component (HOC) available to you which can inject the route props. I've often combined this with the connect HOC to first wrap and inject the route props so they are accessible within the connect HOC.
Example:
import { connect } from "react-redux";
import { selectCollection } from "../../redux/shop/shop.selector";
import { withRouter } from "react-router-dom";
const CollectionPage = ({ collection }) => {
....
}
mapStateToProps = (state, props) => ({
collection: selectCollection(props.match.params.colletionID)(state),
});
export default withRouter(
connect(mapStateToProps)(CollectionPage)
);
Since you are also using Redux you may also find the compose function very handy here to compose all the HOCs over the component you want to decorate. It serves as a way to "unnest" all the HOCs your components may be using
Example:
import { compose } from 'redux';
import { connect } from "react-redux";
import { selectCollection } from "../../redux/shop/shop.selector";
import { withRouter } from "react-router-dom";
const CollectionPage = ({ collection }) => {
....
}
mapStateToProps = (state, props) => ({
collection: selectCollection(props.match.params.colletionID)(state),
});
export default compose(
withRouter,
connect(mapStateToProps),
)(CollectionPage);
react-router-dom#6
If using react-router-dom#6 you'll need to create your own custom withRouter replacement since it was removed in v6.
Example:
import { useParams, /* other hooks */ } from 'react-router-dom';
const withRouter = WrappedComponent => props => {
const params = useParams();
// etc... other react-router-dom v6 hooks
return (
<WrappedComponent
{...props}
params={params}
// etc...
/>
);
};
...
import { compose } from 'redux';
import { connect } from "react-redux";
import { selectCollection } from "../../redux/shop/shop.selector";
import { withRouter } from "../path/to/withRouter";
const CollectionPage = ({ collection }) => {
....
}
mapStateToProps = (state, props) => ({
collection: selectCollection(props.params.colletionID)(state),
});
export default compose(
withRouter,
connect(mapStateToProps),
)(CollectionPage);
I learn ReactJs and have a design Composition question about ReactJs higher order component (HOC).
In the code below App.jsx I use this withAuthentication HOC that initializes app core processes. This HOC value is not used in the App.js. Therefore I must suppress all withAuthentication HOC render callbaks and I do that in the shouldComponentUpdate by returning false.
(I use this HOC in many other places to the get HOC's value but not in App.jsx)
File App.jsx:
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { compose } from 'recompose';
import { getAlbumData } from './redux/albumData/albumData.actions';
import { getMetaData } from './redux/albumMetaData/albumMetaData.actions';
import Header from './components/structure/Header';
import Content from './components/structure/Content';
import Footer from './components/structure/Footer';
import { withAuthentication } from './session';
import './styles/index.css';
class App extends Component {
componentDidMount() {
const { getMeta, getAlbum } = this.props;
getMeta();
getAlbum();
}
shouldComponentUpdate() {
// suppress render for now boilerplate, since withAuthentication
// wrapper is only used for initialization. App don't need the value
return false;
}
render() {
return (
<div>
<Header />
<Content />
<Footer />
</div>
);
}
}
const mapDispatchToProps = dispatch => ({
getMeta: () => dispatch(getMetaData()),
getAlbum: () => dispatch(getAlbumData()),
});
export default compose(connect(null, mapDispatchToProps), withAuthentication)(App);
The HOC rwapper WithAuthentication below is a standard HOC that render Component(App) when changes are made to Firebase user Document, like user-role changes, user auth-state changes..
File WithAuthentication .jsx
import React from 'react';
import { connect } from 'react-redux';
import { compose } from 'recompose';
import AuthUserContext from './context';
import { withFirebase } from '../firebase';
import * as ROLES from '../constants/roles';
import { setCurrentUser, startUserListener } from '../redux/userData/user.actions';
import { selectUserSlice } from '../redux/userData/user.selectors';
const WithAuthentication = Component => {
class withAuthentication extends React.Component {
constructor() {
super();
this.state = {
authUser: JSON.parse(localStorage.getItem('authUser')),
};
}
componentDidMount() {
const { firebase, setUser, startUserListen } = this.props;
this.authListener = firebase.onAuthUserListener(
authUser => {
this.setState({ authUser });
setUser(authUser);
startUserListen();
},
() => {
localStorage.removeItem('authUser');
this.setState({ authUser: null });
const roles = [];
roles.push(ROLES.ANON);
firebase
.doSignInAnonymously()
.then(authUser => {
if (process.env.NODE_ENV !== 'production')
console.log(`Sucessfully signed in to Firebase Anonymously with UID: ${firebase.getCurrentUserUid()}`);
firebase.doLogEvent('login', { method: 'Anonymous' });
firebase
.userDoc(authUser.user.uid)
.set({
displayName: `User-${authUser.user.uid.substring(0, 6)}`,
roles,
date: firebase.fieldValue.serverTimestamp(),
})
.then(() => {
console.log('New user saved to Firestore!');
})
.catch(error => {
console.log(`Could not save user to Firestore! ${error.code}`);
});
})
.catch(error => {
console.error(`Failed to sign in to Firebase: ${error.code} - ${error.message}`);
});
},
);
}
componentWillUnmount() {
this.authListener();
}
render() {
const { currentUser } = this.props;
let { authUser } = this.state;
// ALl changes to user object will trigger an update
if (currentUser) authUser = currentUser;
return (
<AuthUserContext.Provider value={authUser}>
<Component {...this.props} />
</AuthUserContext.Provider>
);
}
}
withAuthentication.whyDidYouRender = true;
const mapDispatchToProps = dispatch => ({
setUser: authUser => dispatch(setCurrentUser(authUser)),
startUserListen: () => dispatch(startUserListener()),
});
const mapStateToProps = state => {
return {
currentUser: selectUserSlice(state),
};
};
return compose(connect(mapStateToProps, mapDispatchToProps), withFirebase)(withAuthentication);
};
export default WithAuthentication;
My question is will this hit me later with problems or is this ok to do it like this?
I know a HOC is not suppose to be used like this. The WithAuthentication is taking care of Authentication against Firebase and then render on all user object changes both local and from Firestore listener snapshot.
This HOC is used in many other places correctly but App.jsx only need to initialize the HOC and never use it's service.
My question is will this hit me later with problems or is this ok to do it like this?
I made a reducer that fetches admins, and I want it to display certain admins when I call it in my reducer but I am getting Undefined.
I am still very new to redux so apologies for my mistakes.
I tried to include all the relevant folders:
App.js
import React, { Component } from 'react';
import { connect } from 'react-redux';
import * as actions from '../store/actions';
class App extends Component {
async componentDidMount() {
fetch(constants.adminUrl + '/admins/data', {
method: 'GET'
}).then((res) => {
return res.json()
}).then(async (res) => {
this.props.setAdminsInColumns(res.admins)
}).catch((error) => {
toast.error(error.message)
})
}
render() {
return (
{/* SOME CODE */}
);
}
}
let app = connect(null, actions)(App);
export default app;
columnsReducer.js
import { FETCH_ADMINS } from '../actions/types'
import { Link } from 'react-router-dom'
import constants from '../../static/global/index'
import React from 'react';
import { toast } from 'react-toastify'
const initialState = {
admins: [],
{
Header: "Responsible",
accessor: "responsibleAdmin",
style: { textAlign: "center" },
// Place where I want to fetch certain admins and get undefined
Cell: props => <span>{props.value && this.state.admins.name ? this.state.admins.find(admin => admin.id === props.value).name : props.value}</span>
}
}
export default function (state = initialState, action) {
switch (action.type) {
case FETCH_ADMINS:
return { ...state, admins: action.admins}
default:
return state
}
}
index.js
import { FETCH_ADMINS } from "./types"
/**
* starts loader for setting admin
*/
export const setAdminsInColumns = (admins) => async dispatch => {
dispatch({ type: FETCH_ADMINS, admins })
}
types.js
export const FETCH_ADMINS = 'fetch_admins'
When I console.log(action.admins) inside the switch case FETCH_ADMINS in the columnsReducer.js file, I can see all the admin information I want, is there a way to make the state global in the columnsReducer.js file so I can read it?
Any help is appreciated!
use mapStateToProps in the connect method. like below
let mapStateToProps = (state)=>{
return {
admins :[yourcolumnsReducer].admins
}
}
let app = connect(mapStateToProps, actions)(App);
//you can use this.props.admins inside your component
MapStateToProps reference
I've tried a few ways of going about this where in my action is do the dispatch(push) :
import LoginService from '../../services/login-service';
export const ActionTypes = {
SET_LOGIN: 'SET_LOGIN',
}
export function getLogin(data, dispatch) {
LoginService.getLoginInfo(data).then((response) => {
const login = response.data;
dispatch({
type: ActionTypes.SET_LOGIN,
login
})
// .then((res) => {
// dispatch(push('/'));
// })
})
}
I even saw something about using the render property on the route for react router.
Now I am trying to use renderIf based on whether my state is true or false but I can't seem to get props on this page successfully :
import React from 'react';
import renderIf from 'render-if';
import { Redirect } from 'react-router-dom';
import LoginHeader from '../../components/header/login-header';
import LoginPage from './login-page';
const LoginHome = props => (
<div>
{console.log(props)}
{renderIf(props.loginReducer.status === false)(
<div>
<LoginHeader />
<LoginPage />}
</div>)}
{renderIf(props.loginReducer.status === true)(
<Redirect to="/" />,
)}
</div>
);
export default LoginHome;
Here is loginPage:
import React from 'react';
import { form, FieldGroup, FormGroup, Checkbox, Button } from 'react-bootstrap';
import { Field, reduxForm, formValueSelector } from 'redux-form';
import { connect } from 'react-redux';
import { getLogin } from './login-actions';
import LoginForm from './form';
import logoSrc from '../../assets/images/logo/K.png';
import './login.scss'
class LoginPage extends React.Component {
constructor(props) {
super(props);
this.state = {
passwordVisible: false,
}
this.handleSubmit= this.handleSubmit.bind(this);
this.togglePasswordVisibility= this.togglePasswordVisibility.bind(this);
}
togglePasswordVisibility() {
this.setState((prevState) => {
if (prevState.passwordVisible === false) {
return { passwordVisible: prevState.passwordVisible = true }
}
else if (prevState.passwordVisible === true) {
return { passwordVisible: prevState.passwordVisible = false }
}
})
}
handleSubmit(values) {
this.props.onSubmit(values)
}
render () {
return (
<div id="background">
<div className="form-wrapper">
<img
className="formLogo"
src={logoSrc}
alt="K"/>
<LoginForm onSubmit={this.handleSubmit} passwordVisible={this.state.passwordVisible}
togglePasswordVisibility={this.togglePasswordVisibility}/>
</div>
</div>
)
}
}
function mapStateToProps(state) {
return {
loginReducer: state.loginReducer,
};
}
function mapDispatchToProps(dispatch) {
return {
onSubmit: (data) => {
getLogin(data, dispatch);
},
};
}
export default connect (mapStateToProps, mapDispatchToProps)(LoginPage);
Let me know if anything else is needed
Use the withRouter HoC from React Router to provide { match, history, location } to your component as follows;
import { withRouter } from "react-router";
...
export default withRouter(connect(mapStateToProps, mapDispatchToProps)(LoginPage));
Modify getLogin to accept history as a third argument; export function getLogin(data, dispatch, history) { ... }
Now in the then method of your async action you can use history.push() or history.replace() to change your current location. <Redirect /> uses history.replace() internally.
You can read about the history module here. This is what is used internally by React Router.
EDIT: In response to your comment...
With your current setup you will need to pass history into getLogin through your mapDispatchToProps provided onSubmit prop.
function mapDispatchToProps(dispatch) {
return {
onSubmit: (data, history) => {
getLogin(data, dispatch, history);
},
};
}
Your handleSubmit method will also need updated.
handleSubmit(values) {
this.props.onSubmit(values, this.props.history)
}
Modified answer depending on how Router was set up I had to use hashHistory: react-router " Cannot read property 'push' of undefined"
I'm trying hard to wire redux store in a react-native app but seems like I'm missing something. Any help will be appreciated.
action.js
export const getdata = (data) => {
return (dispatch, getState) => {
dispatch({
type: "GET_DATA",
data
});
};
};
reducer/dataReducer.js
export default (state = {}, action) => {
switch (action.type) {
case GET_DATA:
return { ...state, response: action.data };
default:
return state;
}
};
reducer/index.js
import { combineReducers } from 'redux';
import dataReducer from './dataReducer';
//other imports
export default combineReducers({
data: dataReducer,
//other reducers
});
store/configureStore.js
import { createStore, compose, applyMiddleware } from 'redux';
import thunk from 'redux-thunk';
import reducers from './../reducer;
export default function configure(initialState = {}) {
const store = createStore(reducer, initialState, compose(
applyMiddleware(thunk),
window.devToolsExtension ? window.devToolsExtension() : f => f
));
return store;
}
main.js (where I dispatch action)
import React, { Component } from 'react';
import { Provider } from 'react-redux';
import Routes from './Routes';
import configureStore from './store/configureStore';
import { getdata} from './actions';
const store = configureStore();
store.subscribe(() => {
console.log('New state', store.getState); //doesn't update at all
});
class Main extends Component {
componentDidMount() {
store.dispatch(getdata('abc')); //calling action creator
}
render() {
return (
<Provider store={store}>
<Routes />
</Provider>
);
}
}
export default Main;
I also tried wiring Chrome extension to see redux store updates, but no luck there. It always says no store found. How can I get this working?
store can be accessed in the a child class inside the Routes Component by react-redux connect
but here no store in the class but I think You can do the following
import React, { Component } from 'react';
import { Provider } from 'react-redux';
import Routes from './Routes';
import configureStore from './store/configureStore';
import { getdata} from './actions';
const store = configureStore();
store.subscribe(() => {
console.log('New state', store.getState()); //getState() is method
});
store.dispatch(getdata('abc'));
class Main extends Component {
render() {
return (
<Provider store={store}>
<Routes />
</Provider>
);
}
}
export default Main;
You want to dispatch your actions from your container components (aka. smart components, the ones connected to the Redux store). The container components can define props in mapDispatchToProps that let them dispatch actions. I don't have your code, so I'm just gonna assume that your Routes component is the container component that you are connecting to the Redux store. Try something like:
class Routes extends Component {
....
componentDidMount() {
this.props.retrieveData();
}
...
}
const mapStateToProps = state => {
...
};
const mapDispatchToProps = dispatch => {
return {
retrieveData: () => dispatch(getdata('abc'));
};
};
export default connect(mapStateToProps, mapDispatchToProps)(Routes);