How to dispatch actions from Child components three level down? - reactjs

I am currently facing this issue designing a React application and I don't seem to be able to find an answer to it.
So my application has following heirarchy of Components in React Router
App
-> DynamicContainer
-> -> LoginComponent
Now, LoginComponents has form elements to take username and password.
I have userActionCreators where the login is handled, and it dispatches login successful when finished, but I don't seem to be able find the right way to connect my LoginComponent to dispatch actions or call actionCreators.
How do I do it? Any suggestion would be appreciated.

One option is to bind your single-purpose forms to their actions with connect. Since <LoginComponent /> is typically always doing the exact same thing, you can use it like this:
import React from 'react';
import * as userActionCreators from '../actions/users';
import { connect } from 'react-redux';
export class LoginComponent extends React.Component {
static propTypes = {
login: React.PropTypes.func.isRequired
}
render() {
const { login } = this.props;
const { username, password } = this.state;
return (
<form onSubmit={ () => login(username, password) }>
...
</form>
);
}
}
export default connect(null, userActionCreators)(LoginComponent);
connect automatically binds the action creator and separately provides dispatch to props, so if you want to be more explicit, the above example is the same as
import React from 'react';
import { login } from '../actions/users';
import { connect } from 'react-redux';
export class LoginComponent extends React.Component {
static propTypes = {
dispatch: React.PropTypes.func.isRequired
}
render() {
const { login, dispatch } = this.props;
const { username, password } = this.state;
return (
<form onSubmit={ () => dispatch(login(username, password)) }>
...
</form>
);
}
}
export default connect()(LoginComponent);
And for reference, userActionCreators:
const LOGIN_SUCCESS = 'LOGIN_SUCCESS';
const LOGIN_FAILED = 'LOGIN_FAILED';
export function login(username, password) {
if (username === 'realUser' && password === 'secretPassword') {
return { type: LOGIN_SUCCESS, payload: { name: 'Joe', username: 'realUser' } };
} else {
return { type: LOGIN_FAILED, error: 'Invalid username or password };
}
}

if I understood you correctly, if you read Example: Todo List | Redux you'll find the example that you might be looking for.
There's the App component, connect()ed to Redux, and then there're the other components: AddTodo, TodoList, Todo and Footer.
App calls TodoList that calls Todo where user can click something. This click will surf back callback after callback, from Todo to TodoList to App as detailed below:
App calls TodoList with
<TodoList todos={visibleTodos} onTodoClick={ index => dispatch(completeTodo(index)) } />
TodoList calls Todo with
<Todo {...todo} key={index} onClick={ () => this.props.onTodoClick(index) } />
Todo component has a <li> with onClick={this.props.onClick} property.
So, backwards, when someones clicks inside the Todo compoment, that will call this.props.onClick which will call this.props.onTodoClick(index) from TodoList (notice the optional added parameter index), then, at last, this will invoke the function dispatch(completeTodo(index)) from App.

Two options:
Pass a bound actionCreator from your Container (which is connected to Redux) down to a child component (which is not connected to Redux) via the props object.
Consider adopting React Component Context in your project.

Related

I wonder if this really is the correct way to use onAuthStateChanged

Following this react-firestore-tutorial
and the GitHub code. I wonder if the following is correct way to use the onAuthStateChanged or if I have understod this incorrect I'm just confused if this is the right way.
CodeSandBox fully connect with a test-account with apikey to Firebase!! so you can try it what I mean and I can learn this.
(NOTE: Firebase is blocking Codesandbox url even it's in Authorised domains, sorry about that but you can still see the code)
t {code: "auth/too-many-requests", message: "We have blocked all
requests from this device due to unusual activity. Try again later.",
a: null}a:
Note this is a Reactjs-Vanilla fully fledge advanced website using only;
React 16.6
React Router 5
Firebase 7
Here in the code the Firebase.js have this onAuthStateChanged and its called from two different components and also multiple times and what I understand one should only set it up once and then listen for it's callback. Calling it multiple times will that not create many listeners?
Can someone have a look at this code is this normal in Reactjs to handle onAuthStateChanged?
(src\components\Firebase\firebase.js)
import app from 'firebase/app';
import 'firebase/auth';
import 'firebase/firestore';
class Firebase {
constructor() {
app.initializeApp(config);
.......
}
.....
onAuthUserListener = (next, fallback) =>
this.auth.onAuthStateChanged(authUser => {
if (authUser) {
this.user(authUser.uid)
.get()
.then(snapshot => {
const dbUser = snapshot.data();
// default empty roles
if (!dbUser.roles) {
dbUser.roles = {};
}
// merge auth and db user
authUser = {
uid: authUser.uid,
email: authUser.email,
emailVerified: authUser.emailVerified,
providerData: authUser.providerData,
...dbUser,
};
next(authUser);
});
} else {
fallback();
}
});
user = uid => this.db.doc(`users/${uid}`);
}
export default Firebase;
This two rect-higher-order Components:
First withAuthentication:
(src\components\Session\withAuthentication.js)
import React from 'react';
import AuthUserContext from './context';
import { withFirebase } from '../Firebase';
const withAuthentication = Component => {
class WithAuthentication extends React.Component {
constructor(props) {
super(props);
this.state = {
authUser: JSON.parse(localStorage.getItem('authUser')),
};
}
componentDidMount() {
this.listener = this.props.firebase.onAuthUserListener(
authUser => {
localStorage.setItem('authUser', JSON.stringify(authUser));
this.setState({ authUser });
},
() => {
localStorage.removeItem('authUser');
this.setState({ authUser: null });
},
);
}
componentWillUnmount() {
this.listener();
}
render() {
return (
<AuthUserContext.Provider value={this.state.authUser}>
<Component {...this.props} />
</AuthUserContext.Provider>
);
}
}
return withFirebase(WithAuthentication);
};
export default withAuthentication;
And withAuthorization:
(src\components\Session\withAuthorization.js)
import React from 'react';
import { withRouter } from 'react-router-dom';
import { compose } from 'recompose';
import AuthUserContext from './context';
import { withFirebase } from '../Firebase';
import * as ROUTES from '../../constants/routes';
const withAuthorization = condition => Component => {
class WithAuthorization extends React.Component {
componentDidMount() {
this.listener = this.props.firebase.onAuthUserListener(
authUser => {
if (!condition(authUser)) {
this.props.history.push(ROUTES.SIGN_IN);
}
},
() => this.props.history.push(ROUTES.SIGN_IN),
);
}
componentWillUnmount() {
this.listener();
}
render() {
return (
<AuthUserContext.Consumer>
{authUser =>
condition(authUser) ? <Component {...this.props} /> : null
}
</AuthUserContext.Consumer>
);
}
}
return compose(
withRouter,
withFirebase,
)(WithAuthorization);
};
export default withAuthorization;
This is normal. onAuthStateChanged receives an observer function to which a user object is passed if sign-in is successful, else not.
Author has wrapped onAuthStateChanged with a higher order function – onAuthUserListener. The HOF receives two parameters as functions, next and fallback. These two parameters are the sole difference when creating HOC's withAuthentication and withAuthorization.
The former's next parameter is a function which stores user data on localStorage
localStorage.setItem('authUser', JSON.stringify(authUser));
this.setState({ authUser });
while the latter's next parameter redirects to a new route based on condition.
if (!condition(authUser)) {
this.props.history.push(ROUTES.SIGN_IN);
}
So, we are just passing different observer function based on different requirements. The component's we will be wrapping our HOC with will get their respective observer function on instantiation. The observer function are serving different functionality based on the auth state change event. Hence, to answer your question, it's completely valid.
Reference:
https://firebase.google.com/docs/reference/js/firebase.auth.Auth#onauthstatechanged
https://reactjs.org/docs/higher-order-components.html

How to architect handling onSuccess of a redux dispatched request that becomes a React Navigation change of screen

I have a Registration screen.
The result of a successful registration will update the account store with the state:
{error: null, token: "acme-auth" ...}
On the Registration screen I render an error if there is one from the store.
What I want to do is navigate to the Dashboard with this.props.navigation.navigate when the store state changes.
I can do this hackily:
render() {
const {account} = this.props
const {token} = account
if (token) {
this.props.navigation.navigate('Dashboard')
}
}
I can also use callbacks:
sendRegistration = () => {
const {email, password} = this.getFormFields()
this.props.registerStart({email, password, success: this.onRegisterSuccess, failure: this.onRegisterFailure}) //using mapDispatchToProps
}
Passing the callback through the redux path seems redundant since I already have the changed state thanks to linking the account store to my Registration component props.
I am toying with the idea of a top-level renderer that detects a change in a userScreen store then swaps out the appropriate component to render.
Is there a simpler, or better way?
Yes there is a better way. If you want to navigate in an async fashion the best place to do it is directly in the thunk, sagas, etc after the async action is successful. You can do this by creating a navigation Service that uses the ref from your top level navigator to navigate.
In app.js:
import { createStackNavigator, createAppContainer } from 'react-navigation';
import NavigationService from './NavigationService';
const TopLevelNavigator = createStackNavigator({
/* ... */
});
const AppContainer = createAppContainer(TopLevelNavigator);
export default class App extends React.Component {
// ...
render() {
return (
<AppContainer
ref={navigatorRef => {
NavigationService.setTopLevelNavigator(navigatorRef);
}}
/>
);
}
}
This sets the ref of the navigator. Then in your NavigationService file:
// NavigationService.js
import { NavigationActions } from 'react-navigation';
let _navigator;
function setTopLevelNavigator(navigatorRef) {
_navigator = navigatorRef;
}
function navigate(routeName, params) {
_navigator.dispatch(
NavigationActions.navigate({
routeName,
params,
})
);
}
// add other navigation functions that you need and export them
export default {
navigate,
setTopLevelNavigator,
};
Now you have access to the navigator and can navigate from redux directly. You can use it like this:
// any js module
import NavigationService from 'path-to-NavigationService.js';
// ...
NavigationService.navigate(''Dashboard' });
Here is the documentation explaining more:
https://reactnavigation.org/docs/en/navigating-without-navigation-prop.html

How to dispatch an action when a button is clicked?

//action code
export const CLEAR_COMPLETED = 'CLEAR_COMPLETED'
export const clearCompleted = () => {
return{
type: CLEAR_COMPLETED
}
}
//reducer code
case CLEAR_COMPLETED:
return state.map(todo => {if (todo.completed)
{return {...todo, show:false}}
else {return todo}})
Problem dispatching action on Todo application in react-redux.
import React from 'react'
import { connect } from 'react-redux'
import { clearCompleted } from '../actions'
const ClearButton = ({dispatch}) => {
return(
<button fluid onClick={e => {dispatch(clearCompleted())}}>
Clear Completed
</button>
)
}
export default ClearButton
Trying to change the store by clicking on Clear Completed Button. Clear Completed Button should remove the completed todos from the store and todo list should be updated. I am trying to call 'clearCompleted' action with Clear Completed Button.
The difficulty you're having here is that your component doesn't know anything about the Redux store, and the dispatch function will not be in its props. The most basic way you can make dispatch available would be this:
export default connect()(ClearButton)
This will allow you to use dispatch(clearCompleted()) without messing around further with mapDispatchToProps. You'd have to change its definition so it's not a stateless component though.
However, you should probably ask yourself whether a tiny button really needs connect at all? You could probably just pass the correct function down from the containing component:
// TodoList.js
class TodoList extends Component {
render () {
return (
...
<ClearButton clearCompleted={this.props.clearCompleted} />
)
}
}
const mapStateToProps = state => ({
// ...
})
const mapDispatchToProps = dispatch => ({
clearCompleted: () => dispatch(clearCompleted())
})
export default connect(mapStateToProps, mapDispatchToProps)(TodoList)
Then the function will be in ClearButton's props without it needing to be connected:
<button onClick={this.props.clearCompleted}>
You can do it by wrapping your component in connect.
connect accepts two arguments as first call, mapStateToProps for mapping your store properties into your component's props and mapDispatchToProps for mapping action creators into your component's props. It's also followed by another call to that function with the Component name of yours written in class syntax.
If you insist in using stateless components with connect, you can use compose utility from redux.
import React from 'react'
import {bindActionCreators} from 'redux';
import { connect } from 'react-redux'
import { clearCompleted } from '../actions'
class ClearButton extends React.Component {
render() {
const {clearCompleted} = this.props;
return(
<button fluid onClick={clearCompleted}>
Clear Completed
</button>
)
}
}
const mapDispatchToProps = dispatch => bindActionCreators({ clearCompleted }, dispatch);
export default connect(null, mapDispatchToProps)(ClearButton);

Getting a value from a particular state before component renders using react/redux?

I am using react/redux with a nodejs(express)/mongodb back-end incase that helps.
What I want to happen here is that if a user tries to go to edit a post that does not belong to them I want them to be re routed immediately and never see that page.
For example. User "A" goes to route localhost:8080/posts/post_id/edit, but that post belongs to user "B". I want User A to immediately get re routed back to that post or localhost:8080/posts/post_id.
In my code I can get the user through a action called getUser() which sends an axios.get request to the back-end to get the current user who is logged in. I am using JWT token. Not sure if this is information needed or not.
Here is the code to show you what I am trying to do.
import React , { Component } from 'react';
import { bindActionCreators } from 'redux';
import * as actions from '../../actions/posts_actions';
import * as actionsIndex from '../../actions/index';
import { reduxForm, Field } from 'redux-form';
import {connect} from 'react-redux';
import {Link} from 'react-router-dom';
class EditPost extends Component {
componentWillMount() {
if(this.props.auth) {
console.log(this.props.auth); // -> returns true
this.props.getUser(); // -> this fires off
}
}
componentDidMount() {
const {id} = this.props.match.params;
this.props.getOnePost(id);
if(this.props.auth){
if(this.props.user._id !== this.props.post.author.id){
this.props.history.push(`/posts/${id}`);
}
}
}
renderField(field) {
const { meta: {touched, error} } = field;
const className = `form-group ${touched && error ? 'has-danger' : ''}`;
return (
<div className={className}>
<label><strong>{field.label}:</strong></label>
<input
className="form-control"
type={field.type}
{...field.input}
/>
<div className="text-help">
{ touched ? error : ''}
</div>
</div>
)
}
onSubmit(values) {
const {id} = this.props.match.params;
this.props.updatePost(values, id, () => {
this.props.history.push(`/posts/${id}`);
});
}
render() {
const {handleSubmit} = this.props;
const {id} = this.props.match.params;
console.log(this.props.user); // -> shows me the user after three nulls
return (
<form onSubmit={handleSubmit(this.onSubmit.bind(this))}>
<Field
label="Title"
name="title"
type="text"
component={this.renderField}
/>
<Field
label="Content"
name="content"
type="text"
component={this.renderField}
/>
<button type="submit" className="btn btn-success">Submit</button>
<Link to={`/posts/${id}`} className="btn btn-danger">Cancel</Link>
</form>
);
}
}
function validate(values) {
const errors = {};
if(!values.title) {
errors.title = "Enter a title!";
}
if(!values.content) {
errors.content = "Enter some content please!";
}
return errors;
}
function mapStateToProps({ posts, auth, user }, ownProps) {
return {
initialValues: posts[ownProps.match.params.id],
post: posts[ownProps.match.params.id],
auth: auth.authenticated,
user: user
};
}
function mapDispatchToProps(dispatch) {
return bindActionCreators({...actions, ...actionsIndex}, dispatch);
}
export default connect(mapStateToProps, mapDispatchToProps)(reduxForm({
validate,
form: 'editform'
})(EditPost));
Here are the console.log statements:
Here is an edit of the index.js page , is there some way I could update the user state here?:
"use strict"
import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';
import {BrowserRouter, Route, Switch} from 'react-router-dom';
import { applyMiddleware, createStore } from 'redux';
import reduxThunk from 'redux-thunk';
import reducers from './reducers/index';
import App from './components/app';
import '../style/style.css';
import Home from './components/pages/home';
import Header from './components/header';
import Footer from './components/footer';
import RequireAuth from './components/auth/require_auth';
import RequireUnAuth from './components/auth/require_unauth';
import Signin from './components/auth/signin';
import Signup from './components/auth/signup';
import Signout from './components/auth/signout';
import Posts from './components/pages/posts';
import {AUTH_USER} from './actions/types';
const createStoreWithMiddleware = applyMiddleware(reduxThunk)(createStore);
const store = createStoreWithMiddleware(reducers);
const token = localStorage.getItem('token');
if(token) {
store.dispatch({ type: AUTH_USER });
}
const Routes = (
<Provider store={store}>
<BrowserRouter>
<App />
</BrowserRouter>
</Provider>
)
ReactDOM.render(Routes, document.querySelector('.container'));
If this.props.getUser() is an async (thunk-ed) action creator, you're not guaranteed to have the result of that available by the time you reach componentDidMount
Add a null check for this.props.user in componentDidMount before you attempt to access fields on it.
Something you could do is move
if(this.props.user._id !== this.props.post.author.id){
this.props.history.push(`/posts/${id}`);
}
into componentWillReceiveProps
componentWillReceiveProps ({ user: nextUser }) {
const { history, match, user: currentUser, post } = this.props
const { id } = match.params
/* if you didn't previously have currentUser (i.e. after first load) - !currentUser
* or the user has changed - nextUser !== currentUser*/
if (!currentUser || nextUser !== currentUser) { // probably shouldn't use === here
/* the component might update before `this.props.getUser()` "returns", so make sure this is the
* update we are looking for by ensuring that we have nextUser - nextUser &&
* then get the id (nextUser._id), and run the check (!== post.author.id)*/
if (nextUser && (nextUser._id !== post.author.id)) {
history.push(`/posts/${id}`)
}
}
}
Here's a little light Friday reading on componentWillReceiveProps - might clear some things up.
A couple things regarding your edit:
An HoC might be a good idea if you have a lot of components that rely on user
AuthComponent HoC concept
class AuthComponent extends React.Component {
componentDidMount() {
if (!this.props.user) {
this.props.getUser()
}
}
render() {
// you could inject `user` into children here, but I would suggest that you
// instead store the result of `getUser` into a store like `redux` or `flux` and fetch from that in child components
return this.props.children
}
}
Obviously you'll need to use connect and grab user out of state for this component. You would use it like
class SomeComponent extends React.Component {
render () {
return (
<AuthComponent>
<WrappedComponent/> // <- this is what's rendered
</AuthComponent>
)
}
}
you're using react-router, though, so a better solution would be to incorporate it into your routing
<Route path="" component={AuthComponent}>
<Route path="some-path" component={WrappedComponent}/>
</Route>
like I said in the comment, cache and return user from your store... although it looks like you're already doing this
don't assume that you'll always have access to user - make sure you're null checking it
The code that you are using in your componentWillMount does not belong here.
this.props.getUser();
You better create an action redux-thunk creator which deals with promise + async actions and only then return the result through the state/dispatch mechanism to be later used in componentDidMount lifecycle hook.
Redux thunk example to call API, please take a look at redux-thunk docs:
const store = createStore(
reducer,
applyMiddleware(thunk.withExtraArgument(api))
)
// later
function fetchUser(id) {
return (dispatch, getState, api) => {
// you can use api here
}
}
To pass multiple things, just wrap them in a single object and use destructuring:
const store = createStore(
reducer,
applyMiddleware(thunk.withExtraArgument({ api, whatever }))
)
// later
function fetchUser(id) {
return (dispatch, getState, { api, whatever }) => {
// you can use api and something else here here
}
}

Which way should I use for Connector in Redux?

I seen 2 ways of doing the same thing but I am not sure what is the proper way.
Component
import React, {Component} from 'react';
import {bindActionCreators} from 'redux';
import {connect} from 'react-redux';
import {selectUser} from '../actions/index'
class UserList extends Component {
renderList() {
return this.props.users.map((user) => {
return (
<li
key={user.id}
onClick={() => this.props.selectUser(user)}
>
{user.first} {user.last}
</li>
);
});
}
render() {
return (
<ul>
{this.renderList()}
</ul>
);
}
}
// Get apps state and pass it as props to UserList
// > whenever state changes, the UserList will automatically re-render
function mapStateToProps(state) {
return {
users: state.users
};
}
// Get actions and pass them as props to to UserList
// > now UserList has this.props.selectUser
function matchDispatchToProps(dispatch){
return bindActionCreators({selectUser: selectUser}, dispatch);
}
// We don't want to return the plain UserList (component) anymore, we want to return the smart Container
// > UserList is now aware of state and actions
export default connect(mapStateToProps, matchDispatchToProps)(UserList);
https://github.com/buckyroberts/React-Redux-Boilerplate
Or
import React from "react"
import { connect } from "react-redux"
import { fetchUser } from "../actions/userActions"
import { fetchTweets } from "../actions/tweetsActions"
#connect((store) => {
return {
user: store.user.user,
userFetched: store.user.fetched,
tweets: store.tweets.tweets,
};
})
export default class Layout extends React.Component {
componentWillMount() {
this.props.dispatch(fetchUser())
}
fetchTweets() {
this.props.dispatch(fetchTweets())
}
render() {
const { user, tweets } = this.props;
if (!tweets.length) {
return <button onClick={this.fetchTweets.bind(this)}>load tweets</button>
}
const mappedTweets = tweets.map(tweet => <li>{tweet.text}</li>)
return <div>
<h1>{user.name}</h1>
<ul>{mappedTweets}</ul>
</div>
}
}
https://github.com/learncodeacademy/react-js-tutorials/tree/master/5-redux-react
The first way uses 2 different functions mapStateToProps() and matchDispatchToProps() while the other way uses #connect(....).
When I use the #connect I get a whole bunch of warnings saying that it has not been finalized and might change.
The # symbol is a decorator which is still considered experimental. So I would use that at your own risk. Your first code block is the safer way to do it as described in the official docs. Both blocks essentially do the same thing but decorators are more sugar than anything.
References:
https://github.com/reactjs/react-redux/blob/master/docs/api.md#connectmapstatetoprops-mapdispatchtoprops-mergeprops-options
What's the '#' (at symbol) in the Redux #connect decorator?
I think the first method will give you less problems in the end. Someone else can chime in though too.
The answer by Jackson is right in every sense however he is missing out the importance of using the first version for the usage of unit testing. If you want to be able to unit test a component (which usually means testing with the unconnected version) you need to be able to export the connected and unconnected component.
Using your example and assuming you are using jest/enzyme you could do something like this:
// notice importing the disconnected component
import { UserList } from '../relative/file/path/UserList'
import { mount } from 'enzyme'
describe('UserList', () => {
it('displays the Username', () => {
const users = [{fist: 'Person', last: 'Thing'}, ... ]
const UserList = mount(<UserList users={users} />)
export(UserList.find('li')[0].text()).toEqual('Person Thing')
});
});
Once you build larger projects being able to unit test will provide sanity to your coding life. Hope this helps

Resources