Nitesh
Nitesh

Reputation: 712

How to dispatch actions from Child components three level down?

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.

Upvotes: 14

Views: 15231

Answers (3)

Henrique Pantarotto
Henrique Pantarotto

Reputation: 404

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:

  1. App calls TodoList with <TodoList todos={visibleTodos} onTodoClick={ index => dispatch(completeTodo(index)) } />

  2. TodoList calls Todo with <Todo {...todo} key={index} onClick={ () => this.props.onTodoClick(index) } />

  3. 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.

Upvotes: 7

Nathan Hagen
Nathan Hagen

Reputation: 12780

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 };
  }
}

Upvotes: 16

JonnyReeves
JonnyReeves

Reputation: 6209

Two options:

  1. 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.

  2. Consider adopting React Component Context in your project.

Upvotes: 1

Related Questions