How to pass this props to functional component - reactjs

First of all I rarely use functional component, but this time I required to use it. So, I have this component called Login that use redux :
import React, { useEffect } from "react";
import { connect } from "react-redux";
import { getLoginData } from "../../redux/actions/LoginActions";
function Login() {
useEffect(() => {
const { getLoginData } = this.props;
getLoginData("test");
}, []);
return (
<div>
<h1>Login</h1>
</div>
);
}
const mapStateToProps = (state) => ({
login: state.login,
});
const mapDispatchToProps = (dispatch) => ({
getLoginData: (value) => dispatch(getLoginData(value)),
});
export default connect(mapStateToProps, mapDispatchToProps)(Login);
It produce error since this is undefined. But, if I change it to class component like this:
import React from "react";
import { connect } from "react-redux";
import { getLoginData } from "../../redux/actions/LoginActions";
class Login extends React.Component {
componentDidMount() {
const { getLoginData } = this.props;
getLoginData("test");
}
render() {
return (
<div>
<h1>Login</h1>
</div>
);
}
}
const mapStateToProps = (state) => ({
login: state.login,
});
const mapDispatchToProps = (dispatch) => ({
getLoginData: (value) => dispatch(getLoginData(value)),
});
export default connect(mapStateToProps, mapDispatchToProps)(Login);
It will worked as expected(the redux is also worked). The question is, How can I pass this.props to functional component?

Function components get their props passed in as the argument to that function:
function Login(props) {
useEffect(() => {
props.getLoginData("test");
}, []);
// ...
}
// Or with destructuring:
function Login({ login, getLoginData }) {
useEffect(() => {
getLoginData("test");
}, []);
// ...
}
That said, if you're using a function component, then it's simpler to use hooks instead of connect:
function Login() {
const login = useSelector(state => state.login);
const dispatch = useDispatch();
useEffect(() => {
dispatch(getLoginData("test"));
}, []);
//...
}
// Note that there is no mapStateToProps/mapDispatchToProps/connect here
export default Login;

As per the React docs, you pass a props object to the function and access the values as attributes of props.
So, for your implementation, you'd do it like this:
function Login(props) {
useEffect(() => {
props.getLoginData("test");
}, []);
return (
<div>
<h1>Login</h1>
</div>
);
}
Or, you could replace function Login(props) with function Login({getLoginData}) to unwrap the value and replace props.getLoginData("test") with getLoginData("test").

Related

Using redux state inside HOC in reactjs

In my react app, I have a withAuth HOC which checks if the user is authenticated before the wrapped component is loaded. It looks up the redux store to check auth status and loads the component, if isAuth is true. However, I can't access redux store from the HOC. when I try, I get the following error message on the browser. Any help to overcome this problem is highly appreciated.
withAuth.js
import React, { useState, useEffect } from 'react';
import { setAuth } from '../actions/Actions';
import { connect } from 'react-redux';
function withAuth(WrappedComponent) {
return (props) => {
const [isAuth, setAuth] = useState(false);
useEffect(() => {
if (props.isAuth) {
setAuth(true);
} else {
props.history.push('/catalog');
}
}, [props.history]);
return isAuth ? <WrappedComponent {...props} /> : <p>Loading..</p>
};
}
const mapStateToProps = (state) => {
return {
isAuth: state.isAuth,
};
};
const mapDispatchToProps = (dispatch) => {
return {
setAuth: (status) => dispatch(setAuth(status)),
};
};
export default connect(mapStateToProps, mapDispatchToProps)(withAuth);
You cannot pass a HOC to connect, you have to pass a function component:
export default function withAuth(WrappedComponent) {
const Component = (props) => {
const [isAuth, setAuth] = useState(false);
useEffect(() => {
if (props.isAuth) {
setAuth(true);
} else {
props.history.push('/catalog');
}
}, [props.history, props.isAuth]);//forgot dependency here
return isAuth ? (
<WrappedComponent {...props} />
) : (
<p>Loading..</p>
);
};
const mapStateToProps = (state) => {
return {
isAuth: state.isAuth,
};
};
const mapDispatchToProps = (dispatch) => {
return {
setAuth: (status) => dispatch(setAuth(status)),
};
};
return connect(
mapStateToProps,
mapDispatchToProps
)(Component);
}

my mapdispatch to props is automatically calling

Post component:
import React from 'react';
import './post.styles.scss';
import { connect } from 'react-redux';
import { requestContents } from '../../redux/post/post.actions';
class Post extends React.Component {
componentWillMount(){
}
render(){
return (
<div>
</div>
)
}
}
const mapDispatchToProps = (dispatch) => {
return {
onRequestContents : dispatch(requestContents())
}
}
const mapStateToProps = (state) => {
return {
posts: state.post.posts,
isPending: state.post.isPending
}
}
User component:
export default connect(mapStateToProps, mapDispatchToProps)(Post)
import React from 'react';
import './user.styles.scss';
import { connect } from 'react-redux';
import { requestUsers } from '../../redux/user/user.actions';
class User extends React.Component {
componentWillMount(){
this.props.onRequestUsers();
}
render(){
return (
<div>
</div>
}
}
const mapDispatchToProps = (dispatch) => {
return {
onRequestUsers: () => dispatch(requestUsers())
}
}
const mapStateToProps = (state) => {
return {
users: state.user.users,
isPending: state.user.isPending
}
}
export default connect(mapStateToProps, mapDispatchToProps)(User)
Here I am using redux with React.
In user component i am calling onRequestUsers inside componentWillMount()
but in post component i am not calling onRequestContents inside componentWillMount()
But still how it is calling and display in my redux-logger
I am calling mapDispatchToProps only inside user component
Please have a look
You're calling requestContents in your mDTP call:
const mapDispatchToProps = (dispatch) => {
return {
onRequestContents: dispatch(requestContents())
}
}
That's what those two parens do:
requestContents()
Omit them:
onRequestContents: dispatch(requestContents)
That said: there's something odd with your function. Normally you'd mDTP with a function, e.g.,
onRequestContents: () => dispatch(requestContents())
(Used when you need to pass parameters, like an event.)

React-Redux: Using action creators in React components

I am new to React/Redux, and appreciate your help. I am taking a Udemy course on this topic. The course instructor creates a component like this.
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { fetchUser } from '../actions';
class User extends Component {
componentDidMount(){
this.props.fetchUser(this.props.userId);
}
render(){
const { user } = this.props;
if(!user) return null;
return(
<div className="header"> User Info: {user.name}</div>
);
}
}
const mapStateToProps = (state, ownProps) => {
return { user: state.users.find( user => user.id === ownProps.userId)};
};
export default connect(mapStateToProps, { fetchUser })(User)
my question: why inside the componentDidMount() he is prefixing fetchUsers() with this.props?
it is not the case that he is passing fetchUsers() as props from the parent component. This is how the parent is using this component <User userId={post.userId}/>
Note: this code works
It is because of this line :
export default connect(mapStateToProps, { fetchUser })(User)
the second parameter to connect is called mapDispatchToProps, It adds the actions to props
From the docs :
connect can accept an argument called mapDispatchToProps, which lets
you create functions that dispatch when called, and pass those
functions as props to your component.
const mapDispatchToProps = dispatch => {
return {
// dispatching plain actions
increment: () => dispatch({ type: 'INCREMENT' }),
decrement: () => dispatch({ type: 'DECREMENT' }),
reset: () => dispatch({ type: 'RESET' })
}
}
Your code is using the “object shorthand” form.
The way the mapDispatchToProps in the example is shorthanded. It might be easier to tell what is going if it was written like so:
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { fetchUser } from '../actions';
class User extends Component {
componentDidMount(){
this.props.fetchUser(this.props.userId);
}
render(){
const { user } = this.props;
if(!user) return null;
return(
<div className="header"> User Info: {user.name}</div>
);
}
}
const mapStateToProps = (state, ownProps) => {
return { user: state.users.find( user => user.id === ownProps.userId)};
};
const mapDispatchToProps = () => ({
fetchUser
});
export default connect(mapStateToProps, mapDispatchToProps)(User)
Maybe this shows it more clearly, but the dispatch function (fetchUser) is being mapped to the components properties. Just like the state value (user) is being mapped to the properties of the component. I think you just got confused because of the shorthand that was used.

How to access redux-store from within react's componentDIdMount()

In the following code I am trying to pass the state.userData.userDetails from the redux-store to getleftpaneProductCatalogue(), but state.userData.userDetails is unaccessible to componentDidMount(). I tried assigning the state.userData.userDetails to this.prop.userProfile, but still this.prop.userProfile is an empty value. How to access the prop within componentDidMount?
import React,{Component} from 'react';
import { connect } from 'react-redux';
import {Row, Col } from 'react-materialize';
import {getleftpaneProductCatalogue} from '../actions/leftpane-actions';
import ProductCatalogueLeftPaneComp from '../components/pages/product-catalogue-leftpane';
class ProductCatalogueLeftPane extends Component {
constructor(props) {
super(props)
}
componentDidMount() {
console.log('this.props^', JSON.stringify(this.props));
this.props.getleftpaneProductCatalogue().then((data) => {
console.log('productdata', data);
})
}
render() {
return (
<div>
{JSON.stringify(this.props.userProfile)}
</div>
)
}
}
const mapStateToProps = (state) => {
console.log('state^', JSON.stringify(state));
return {leftpaneProductCatalogue: state.leftpaneProductCatalogue, userProfile: state.userData.userDetails};
};
const mapDispatchToProps = (dispatch) => {
return {
getleftpaneProductCatalogue: () => dispatch(getleftpaneProductCatalogue()),
};
};
export default connect(mapStateToProps, mapDispatchToProps)(ProductCatalogueLeftPane);
You can access the state directly in mapDispatchToProps and pass it to getleftpaneProductCatalogue:
componentDidMount() {
const { dispatch, getleftpaneProductCatalogue }
dispatch(getleftpaneProductCatalogue())
}
const mapDispatchToProps = dispatch => {
return {
getleftpaneProductCatalogue: () => (dispatch, getState) => {
const state = getState()
const details = state.userData.userDetails
return dispatch(getleftpaneProductCatalogue(details))
},
dispatch
}
}
However, the way you're doing it, passing the state via mapStateToProps is still valid, but more verbose. Therefore the problem would be somewhere else.
Here's my bet. I guess you're getting the userData somewhere in your code with async API call and it's not being fetched yet. If that's the case - then you should wait for data being fetched firstly, then you can access it in your component ProductCatalogueLeftPane.

React Redux - How to dispatch an action on componentDidMount when using mapDispatchToProps in a connected component

I'm having problems with this. I'm creating a small app with react redux.
In the code below is my app.js component. It was working fine until I tried to use the mapDispatchToProps function inside connect. The problem is that I cannot invoke the dispatch action on componentDidMount anymore. The actions that were in componentDidMount and that now are on mapStateToProps need to be called on comoponentDidMount. Any clues in how to do that?
import React, { Component } from 'react';
import './App.css';
import '../../node_modules/bootstrap/less/bootstrap.less';
import { Route } from 'react-router-dom'
import * as ReadableAPI from '../ReadableAPI'
import HeaderNavigation from './HeaderNavigation';
import TableBody from './TableBody';
import { connect } from 'react-redux';
import sortAsc from 'sort-asc';
import sortDesc from 'sort-desc';
import {
selectedCategory,
fetchCategoriesIfNeeded,
fetchPostsIfNeeded,
invalidateSubreddit,
orderPost
} from '../actions'
class App extends Component {
state = {
posts: []
}
componentDidMount() {
const { dispatch, selectedCategory, fetchCategories, fetchPosts} = this.props
//dispatch(fetchCategoriesIfNeeded(selectedCategory))
//dispatch(fetchPostsIfNeeded(selectedCategory))
}
orderByScoreAsc = (posts) => {
return posts.sort(sortAsc('voteScore'))
}
orderByScoreDesc = (posts) => {
return posts.sort(sortDesc('voteScore'))
}
render() {
const { navCategories, posts } = this.props
return (
<div>
<HeaderNavigation navCategories = {navCategories} />
<Route exact path="/" render={()=>(
<TableBody
showingPosts={posts}
/>)}
/>
</div>
);
}
}
function mapStateToProps ( state ) {
const { categories, posts } = state
return {
navCategories: categories.items,
posts: posts.items
}
}
function mapDispatchToProps (dispatch) {
return {
changeOrder: (data) => dispatch(orderPost(data)),
fetchCategories: (data) => dispatch(fetchCategoriesIfNeeded(data)),
fetchPosts: (data) => dispatch(fetchPostsIfNeeded(data))
}
}
export default connect(
mapStateToProps,
mapDispatchToProps
)(App)
I modified your code to what I think will work. I also left comments.
class App extends Component {
state = {
posts: []
}
componentDidMount() {
// no need to use dispatch again. Your action creators are already bound by
// mapDispatchToProps. Notice also that they come from props
const { selectedCategory, fetchCategoriesIfNeeded, fetchPostsIfNeeded} = this.props;
fetchCategoriesIfNeeded(selectedCategory);
fetchPostsIfNeeded(selectedCategory);
}
//... the same
}
function mapStateToProps ( state ) {
//... the same
}
function mapDispatchToProps (dispatch) {
// when arguments match, you can pass configuration object, which will
// wrap your actions creators with dispatch automatically.
return {
orderPost,
fetchCategoriesIfNeeded,
fetchPostsIfNeeded,
}
}
In map to dispatch you have fetchCategories/fetchPosts so therefore you need to call them like this:
componentDidMount() {
const { dispatch, selectedCategory, fetchCategories, fetchPosts } = this.props
//Call like this instead of fetchCategoriesIfNeeded/fetchPostsIfneeded
//dispatch(fetchCategories(selectedCategory))
//dispatch(fetchPosts(selectedCategory))
}
You have this:
function mapDispatchToProps (dispatch) {
return {
changeOrder: (data) => dispatch(orderPost(data)),
fetchCategories: (data) => dispatch(fetchCategoriesIfNeeded(data)),
fetchPosts: (data) => dispatch(fetchPostsIfNeeded(data))
}
}
So you need to call fetchCategories/fetchPosts from your props instead of fetchCatIfneeded/fetchPostsifneeded
You just don't. The mapDispatchToProps does exactly what you are trying to do in your component. Instead of calling a dispatch you call the method that was provided to your component by connect. in your case:
componentDidMount() {
const { selectedCategory, fetchCategories, fetchPosts} = this.props;
fetchCategories(selectedCategory);
fetchPosts(selectedCategory);
}

Resources