How to open and close an inline dialog in a react redux app - javascript

I have a working inline dialog using react state. The working code is below.
import React, { PureComponent } from 'react';
import { render } from 'react-dom';
import PropTypes from 'prop-types';
import Button from '#atlaskit/button';
import InlineDialog from '#atlaskit/inline-dialog';
const styles = {
fontFamily: 'sans-serif',
textAlign: 'center',
};
class ButtonActivatedDialog extends PureComponent {
static propTypes = {
content: PropTypes.node,
position: PropTypes.string,
}
state = {
isOpen: false,
};
handleClick = () => {
this.setState({
isOpen: !this.state.isOpen,
});
}
handleOnClose = (data) => {
this.setState({
isOpen: data.isOpen,
});
}
render() {
return (
<InlineDialog
content={this.props.content}
position={this.props.position}
isOpen={this.state.isOpen}
onClose={this.handleOnClose}
>
<Button
onClick={this.handleClick}
isSelected
>
The Button
</Button>
</InlineDialog>
);
}
}
const App = () => (
<ButtonActivatedDialog
content={
<div>
<h5>
Displaying...
</h5>
<p>
Here is the information I need to display.
</p>
</div>}
position='bottom right'
/>
);
render(<App />, document.getElementById('root'));
I would like to have the same behavior with the button but using redux to maintain the state of the dialog.
After reading some material I believe I need to dispatch an action that will activate a reducer witch in turn will help me update the state of the dialog. However, I don't believe I fully understand how this should be put together.
Here is my work in progress but for some reason my codeSanbox does not like the format in which I'm creating the store.
mport React, { PureComponent } from 'react';
import { render } from 'react-dom';
import PropTypes from 'prop-types';
import Button from '#atlaskit/button';
import InlineDialog from '#atlaskit/inline-dialog';
import { connect, createStore } from 'react-redux'
const styles = {
fontFamily: 'sans-serif',
textAlign: 'center',
};
const mapStateToProps = state => {
return {
isDialogOpen: false,
}
}
const mapDispatchToProps = dispatch => {
return {
toggleDialog: () => dispatch({
type: 'TOGGLE_DIALOG'
})
}
}
// action:
const tottleDialog = 'TOGGLE_DIALOG';
//action creator
const toggleDialog = (e) => ({
type: 'TOGGLE_DIALOG',
e,
})
class ButtonActivatedDialog extends PureComponent {
static propTypes = {
content: PropTypes.node,
position: PropTypes.string,
}
state = {
isOpen: false,
};
handleClick = () => {
this.setState({
isOpen: !this.state.isOpen,
});
}
handleOnClose = (data) => {
this.setState({
isOpen: data.isOpen,
});
}
render() {
return (
<InlineDialog
content={this.props.content}
position={this.props.position}
isOpen={this.state.isOpen}
onClose={this.handleOnClose}
>
<Button
onClick={this.handleClick}
isSelected
>
The Button
</Button>
</InlineDialog>
);
}
}
const App = () => (
<ButtonActivatedDialog
content={
<div>
<h5>
Displaying...
</h5>
<p>
Info here
</p>
</div>}
position='bottom right'
/>
);
const store = createStore(toggleDialog, {})
//need and action
//need an action creator - a function that returns an action:
//
// render(<App />, document.getElementById('root'));
render(
<Provider store={store}>
<App />
</Provider>, document.getElementById('root')
);

Ok so first you have to set up your redux state. We usually do this according to the re-ducks pattern here: https://github.com/alexnm/re-ducks
This means you will create a directory for each "part" of your application. Each part then has a:
Operation: To perform tasks on the state (like open up or close your inline menu)
Selector: To get some value of the state (like is the inline menu open?)
Action: To perform an action on the state (like set isOpen to true/false)
Reducer: To apply an action to the state (like the one from above)
Type: Any type of state change. Any action has a type, and the type decides which part in the reducer is executed.
So in your example, I would create a state/inlineMenu folder and inside it the following files:
actions.js:
import types from './types';
const toggleState = {
type: types.TOGGLE_STATE
};
export default {
updateMenuState
}
operations.js:
import actions from './actions';
const toggleState = actions.toggleState;
export default {
updateMenuState
};
reducers.js:
import types from './types';
const initialState = {
isOpen: false // closed per default
};
const inlineMenuReducer = (state = initialState, action) => {
switch (action.type) {
case types.TOGGLE_STATE:
return { ...state, isOpen: !state.isOpen }
default:
return state;
}
};
export default inlineMenuReducer;
selectors.js:
const isMenuOpen = state => state.inlineMenu.isOpen;
export default {
isMenuOpen
};
types.js:
const TOGGLE_STATE = 'inlineMenu/TOGGLE_STATE';
export default {
TOGGLE_STATE
};
index.js:
import reducer from './reducers';
export { default as inlineMenuSelectors } from './selectors';
export { default as inlineMenuOperations } from './operations';
export default reducer;
You also have to set up the default provider. Your path to the isOpen property in the selectors should probably be adjusted.
Now you have your global redux state set up.
We need to get the data in it now to the view. We need to use redux' connect function for this, in which it will take the operations and selectors and map them to the default react props.
So your connected component could look like this:
import React, { PureComponent } from 'react';
import { render } from 'react-dom';
import { connect } from 'react-redux';
import PropTypes from 'prop-types';
import Button from '#myKitkit/button';
import InlineDialog from '#mykit/inline-dialog';
import { inlineMenuOperations, inlineMenuOperations } from '../path/to/state/inlineMenu';
const styles = {
fontFamily: 'sans-serif',
textAlign: 'center',
};
class ButtonActivatedDialog extends PureComponent {
static propTypes = {
content: PropTypes.node,
position: PropTypes.string,
toggleState: PropTypes.func.isRequired
}
handleClick = () => {
const { toggleState } = this.props;
// This will dispatch the TOGGLE_STATE action
toggleState();
}
handleOnClose = () => {
const { toggleState } = this.props;
// This will dispatch the TOGGLE_STATE action
toggleState();
}
render() {
return (
<InlineDialog
content={this.props.content}
position={this.props.position}
isOpen={this.props.isOpen}
onClose={this.handleOnClose}
>
<Button
onClick={this.handleClick}
isSelected
>
The Button
</Button>
</InlineDialog>
);
}
}
// You need to add the provider here, this is described in the redux documentation.
const App = () => (
<ButtonActivatedDialog
content={
<div>
<h5>
Displaying...
</h5>
<p>
Here is the information I need to display.
</p>
</div>}
position='bottom right'
/>
);
const mapStateToProps = state => ({
isOpen: inlineMenuSelectors.isOpen(state);
});
const mapDispatchToProps = dispatch => ({
toggleState: () => dispatch(inlineMenuOperations.toggleState())
}
const ConnectedApp = connect(mapStateToProps, mapDispatchToProps);
render(<ConnectedApp />, document.getElementById('root'));

Related

Error: Actions must be plain objects. Use custom middleware for async actions, in a delete button?

I am trying to get a react action to fetch a list of files after the user deletes a file from the list.
In App.js I pass a handleClick function to the nested component.
App.js
class App extends Component {
static propTypes = {
files: PropTypes.array.isRequired,
isFetching: PropTypes.bool.isRequired,
dispatch: PropTypes.func.isRequired,
handleClick : PropTypes.func
};
componentDidMount() {
const {dispatch} = this.props;
dispatch(fetchFiles);
}
handleClick = fileId => {
const {dispatch} = this.props;
deleteFileById(dispatch,fileId);
};
render() {
const {files, isFetching, dispatch} = this.props;
const isEmpty = files.length === 0;
return (
<div>
<h1>Uploadr</h1>
{isEmpty
? (isFetching ? <h2>Loading...</h2> : <h2>No files.</h2>)
: <div style={{opacity: isFetching ? 0.5 : 1}}>
<Files files={files} handleClick={this.handleClick}/>
</div>
}
</div>
)
}
}
const mapStateToProps = state => {
const {isFetching, items: files} = state.files;
return {
files,
isFetching,
}
};
export default connect(mapStateToProps)(App)
Files.js
import React from 'react'
import PropTypes from 'prop-types'
const Files = ({files, handleClick }) => (
<ul>
{files.map((file, i) =>
<li key={i}>{file.name}
<button onClick={() => (handleClick(file.id))}>Delete</button>
</li>
)}
</ul>
);
Files.propTypes = {
files: PropTypes.array.isRequired,
handleClick: PropTypes.func.isRequired
};
export default Files
actions.js
I am wanting to trigger a request to get a new list of files from the API after the delete action is done.
export const deleteFileById = (dispatch, fileId) => {
dispatch(deleteFile);
return fetch(`/api/files/${fileId}`, {method : 'delete'})
.then(dispatch(fetchFiles(dispatch)))
};
export const fetchFiles = (dispatch) => {
dispatch(requestFiles);
return fetch('/api/files')
.then(response => response.json())
.then(json => dispatch(receiveFiles(json)))
};
However I am getting the following error
Error: Actions must be plain objects. Use custom middleware for async actions.
What is the best way to implement this
An action will dispatch another action but not event handler function.
You no need to dispatch deleteFileById from component because this is a function exported in actions which will dispatch an action.
Please remove dispatch in handleClick to work.
Wrong one:
handleClick = fileId => {
this.props.deleteFileById(dispatch(this.props.dispatch,fileId));
};
Correct one:
handleClick = fileId => {
this.props.deleteFileById(this.props.dispatch,fileId);
};
Regarding this.props.deleteFileById is not a function.
There are many ways to access actions in your component. Below are few ways
You need to install prop-types
npm install -s prop-types
If your component is Test then set prop types as like below
import PropTypes from 'prop-types';
import React, {Component} from 'react';
class Test extends Component{
render(){
return(
<div</div>
)
}
}
Test.propTypes = {
deleteFileById: PropTypes.func
}
If you are using redux connect then
Without prop-types
import React, { Component } from 'react';
import { connect } from 'react-redux';
import * as actions from '../actions';
class Test extends Component{
render(){
return(
<div</div>
)
}
}
export default connect(null, {...actions})(Test);
OR
With inbuilt react proptypes you no need to install prop-types separately
import React, { Component } from 'react';
import { connect } from 'react-redux';
import * as actions from '../actions';
import {push} from 'react-router-redux';
class Test extends Component{
static get propTypes() {
return {
sendContactForm: React.PropTypes.func
}
}
render(){
return(
<div</div>
)
}
}
const actionsToProps = {
deleteFileById: actions.deleteFileById,
push
}
export default connect(null, actionsToProps)(Test);
Your code App.jsx should be something like below
class App extends Component {
static propTypes = {
files: PropTypes.array.isRequired,
isFetching: PropTypes.bool.isRequired,
deleteFileById : PropTypes.func,
fetchFiles: PropTypes.func
};
componentDidMount() {
this.props.fetchFiles();
}
handleClick = fileId => {
this.props.deleteFileById(fileId);
};
render() {
const {files, isFetching} = this.props;
const isEmpty = files.length === 0;
return (
<div>
<h1>Uploadr</h1>
{isEmpty
? (isFetching ? <h2>Loading...</h2> : <h2>No files.</h2>)
: <div style={{opacity: isFetching ? 0.5 : 1}}>
<Files files={files} handleClick={this.handleClick}/>
</div>
}
</div>
)
}
}
const mapStateToProps = state => {
const {isFetching, items: files} = state.files;
return {
files,
isFetching,
}
};
export default connect(mapStateToProps)(App)
dispatch should be returned in actions but not from component to actions or vice versa
Below is sample action file for your ref.
import ajax from '../ajax';
import {Map, fromJS} from 'immutable';
import config from '../config';
import {push} from 'react-router-redux'
export const URL_PREFIX = 'http://localhost:3000/api';
export const SEND_CONTACT_FORM_REQUEST = 'SEND_CONTACT_FORM_REQUEST';
export const SEND_CONTACT_FORM_SUCCESS = 'SEND_CONTACT_FORM_SUCCESS';
export const SEND_CONTACT_FORM_ERROR = 'SEND_CONTACT_FORM_ERROR';
export function sendContactFormRequest(){
return {
type: SEND_CONTACT_FORM_REQUEST,
loading: true
}
}
export function sendContactFormSuccess(data){
return {
type: SEND_CONTACT_FORM_SUCCESS,
loading: false,
data: data
}
}
export function sendContactFormError(errors){
return {
type: SEND_CONTACT_FORM_ERROR,
loading: false,
errors: errors
}
}
export function sendContactForm(firstName, lastName, email, subject, message) {
return dispatch => {
dispatch(sendContactFormRequest());
return ajax.post(URL_PREFIX + '/communication/contact', { firstName, lastName, email, subject, message })
.then(res => {
dispatch(sendContactFormSuccess(res.data))
})
.catch(errors => {
dispatch(sendContactFormError(errors))
})
}
}

Trying to build a basic React-Redux click switcher but nothing is displayed

The app is just supposed to display 'Hello' and when you click on it, switch to 'Goodbye', but it won't render 'Hello'. I've set default state, connected everything, etc. but I can't figure out what I'm missing.
import React from 'react';
import { render } from 'react-dom';
import { Provider } from 'react-redux';
import { createStore } from 'redux';
import { connect } from 'react-redux';
import "./styles.css";
const switcheroo = () => {
return {
type: 'SWITCH'
};
};
const switchReducer = (state = {value: 'Hello'}, action) => {
switch (action.type) {
case 'SWITCH':
return { ...state, value: 'Goodbye' };
default:
return state;
}
}
class ClickMachine extends React.Component {
render() {
const { value, switcheroo } = this.props;
return(
<div >
<p onClick={switcheroo}>{value}</p>
</div>
)
}
};
const mapStateToProps = (state) => ({
value: state.value,
});
const mapDispatchToProps = (dispatch) => ({
switcheroo: () => dispatch(switcheroo()),
});
connect(mapStateToProps, mapDispatchToProps)(ClickMachine);
const store = createStore(switchReducer);
class AppWrapper extends React.Component {
render() {
return (
<Provider store={store}>
<ClickMachine />
</Provider>
);
};
};
const rootElement = document.getElementById("root");
render(<AppWrapper />, rootElement);
My CodeSandbox is here: https://codesandbox.io/s/k29r3928z7 and I have the following dependencies:
react
react-dom
react-redux
redux
Its because you've not assigned the connect function to a component, without which redux won't be associated with the ClickMachine Component
just change this line, it will work
ClickMachine = connect(mapStateToProps, mapDispatchToProps)(ClickMachine);
Sandbox link https://codesandbox.io/s/4x2pr03489

_This2 not a function error when dispatching action

Background
I am working on a very routine chunk of code, I have created actions and reducers many times throughout my app. I am now setting up authentication, and have two containers loading based on routes / & /register.
Issue
I am trying to dispatch an action, and do a simple console.log("test"). I have done this many times before, in-fact, I have literally duplicated a container and altered the names of the dispatched action names. One container works, while the other is hitting me with:
Uncaught TypeError: _this2.propsregisterHandler is not a function
I am confused why its not showing a . between props and registerHandler
Here is the relevent code:
Container Import
import { register } from "../../store/actions/authentication";
JSX
<div
className="btn btn-primary col"
onClick={() =>
this.props.registerHandler(
this.state.email,
this.state.password
)
}
>
Register
</div>
....
Disptach Code
const mapStateToProps = state => {
return {};
};
const mapDisptachToProps = dispatch => {
return {
registerHandler: () => dispatch(register())
};
};
export default connect(
mapStateToProps,
mapDisptachToProps
)(Register);
The action
import * as actionTypes from "./actiontypes";
export const register = () => {
console.log("TEST");
return { type: actionTypes.REGISTER };
};
Reducer
const reducer = (state = initialState, action) => {
switch (action.type) {
case actiontypes.REGISTER: {
console.log("you called the reducer");
return state;
}
Revised
This code here does not work, I always get the error, however if I call the same action in my login component, it will work.
import React, { Component } from "react";
import { connect } from "react-redux";
import { registerUserToApp } from "../../store/actions/authentication";
import "../Login/login";
export class Register extends Component {
state = {
email: "",
password: ""
};
render() {
return (
<div
className="btn btn-primary"
onClick={() => {
this.props.registerUserToAppHandler();
}}
>
Register
</div>
);
}
}
const mapStateToProps = state => {
return {};
};
const mapDispatchToProps = dispatch => {
return {
registerUserToAppHandler: () => dispatch(registerUserToApp())
};
};
export default connect(
mapDispatchToProps,
mapStateToProps
)(Register);
login Component
import React, { Component } from "react";
import { connect } from "react-redux";
import Aux from "../../components/hoc/Aux";
import Logo from "../../assets/images/Logo.png";
import GoogleLogo from "../../assets/images/google.svg";
import {
loginUser,
loginUserWithGoogle,
registerUserToApp
} from "../../store/actions/authentication";
import "./login.css";
export class Login extends Component {
state = {
email: "",
password: ""
};
render() {
const userNameChangeHandler = event => {
this.setState({
email: event.target.value
});
};
const passworChangeHandler = event => {
this.setState({
password: event.target.value
});
};
return (
<Aux>
...
<div
className="btn btn-primary col"
onClick={() => {
this.props.loginUserHandler(
this.state.email,
this.state.password
);
this.props.registerUserToAppHandler();
}}
>
Sign In
</div>
...
</Aux>
);
}
}
const mapStateToProps = state => {
return {};
};
const mapDisptachToProps = dispatch => {
return {
loginUserHandler: (email, password) => dispatch(loginUser(email, password)),
registerUserToAppHandler: () => dispatch(registerUserToApp()),
loginUserWithGoogleHandler: () => dispatch(loginUserWithGoogle())
};
};
export default connect(
mapStateToProps,
mapDisptachToProps
)(Login);
I can't leave a comment, but shouldn't you add .css extension when importing styles?
import "../Login/login";
The issue was due to how I was loading this component into my container. I am nut sure of the exact reasoning but I was importing my component into the container using a named import import {Login} from ".../path", whereas it should have been import Login from ".../path".

Redux-thunk dispatch an action and wait for re-render

import React from "react";
import { render } from "react-dom";
import { createStore, applyMiddleware } from "redux";
import { Provider, connect } from "react-redux";
import thunk from "redux-thunk";
const disabled = (state = true, action) => {
return action.type === "TOGGLE" ? !state : state;
};
class Button extends React.Component {
componentDidUpdate(prevProps) {
if (prevProps.disabled !== this.props.disabled && !this.props.disabled) {
// this.ref.focus(); // uncomment this to see the desired effect
}
}
render() {
const { props } = this;
console.log("rendering", props.value);
return (
<div>
<input
type="checkbox"
onClick={() => {
props.toggle();
this.ref.focus(); // doesn't work
}}
/>
<input
disabled={props.disabled}
ref={ref => {
this.ref = ref;
}}
/>
</div>
);
}
}
const toggle = () => ({
type: "TOGGLE"
});
const A = connect(state => ({ disabled: state }), { toggle })(Button);
const App = () => (
<Provider store={createStore(disabled, applyMiddleware(thunk))}>
<A />
</Provider>
);
render(<App />, document.getElementById("root"));
I want to focus the input when the checkbox is checked.
However, this.ref.focus() must be called only after the component re-renders with props.disabled === false, as an input with disabled prop cannot be focused.
If I do the logic in componentDidUpdate, I'm able to achieve what I want. But this is not a clean solution as the logic is specific to the onClick handler rather than a lifecycle event.
Is there any other way to accomplish this? (preferably with a working codesandbox example)
I think the best thing to do is not rely on refs use state to manage the focus.
This solution instead uses the autoFocus prop on the input, and modifies it when the state of the checkbox changes.
import React from "react";
import { render } from "react-dom";
import { createStore, applyMiddleware } from "redux";
import { Provider, connect } from "react-redux";
import thunk from "redux-thunk";
const disabled = (state = true, action) => {
return action.type === "TOGGLE" ? !state : state;
};
class Button extends React.Component {
state = {
checked: false,
focus: false
};
componentDidUpdate(prevProps, prevState) {
if (prevState.checked !== this.state.checked) {
this.props.toggle();
this.setState({
focus: this.state.checked
});
}
}
render() {
const { props } = this;
const { checked, focus } = this.state;
console.log("rendering", props.value, checked);
return (
<div>
<input
type="checkbox"
checked={checked}
onClick={({ target }) => {
this.setState({ checked: target.checked });
}}
/>
<input key={`input_${checked}`} autoFocus={focus} />
</div>
);
}
}
const toggle = () => ({
type: "TOGGLE"
});
const A = connect(state => ({ disabled: state }), { toggle })(Button);
const App = () => (
<Provider store={createStore(disabled, applyMiddleware(thunk))}>
<A />
</Provider>
);
render(<App />, document.getElementById("root"));
I'm not sure why, but changing the autoFocus prop when the component was previously disabled doesn't trigger the input to be re-rendered. So I've also added a key to the input to force it.
This is an hypothetical situation and an open issue in REACT(at the same time NOT) since it is consistent with the HTML spec for autofocus (https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input#Attributes#autofocus). Focus is one of those things that is really tricky to do decoratively because it's part of a shared global state. If 2 unrelated components declare that they should be focused in a single render pass, who is right? So REACT give you the hooks to manage that state yourself but it won't do it for you (thus where the work around like the one your are using came).
But It would be great if REACT added the option to focus on render (could be just autoFocusOnRender), and just have the docs warn people of the behavior if multiple things call for focus at once. Ideally this wouldn't happen because an app with good UX would have specific conditions for calling autoFocusOnRender on different inputs.
I would Suggest what you have done is the best way of doing it :). Hope we get an enhancement for this in REACT.
I think that you can have confidence that the updated Redux state data is there before you perform your focus() call, because of the data flow:
Dispatch async action toggleThunk, and wait for its resolution
then dispatch synchronous action to update the state (new state data), and wait for its resolution (?)
then focus() your ref
https://codesandbox.io/s/r57v8r39om
Note that in your OP, your toggle() action creator is not a thunk. Also, it's a good rule to enforce that your thunks return a Promise so that you can control data flow in the way you're describing.
import React from "react";
import { render } from "react-dom";
import { createStore, applyMiddleware } from "redux";
import { Provider, connect } from "react-redux";
import thunk from "redux-thunk";
const disabled = (state = true, action) => {
return action.type === "TOGGLE" ? !state : state;
};
class Button extends React.Component {
textInput = React.createRef();
handleClick = () => {
const { toggleThunk } = this.props;
toggleThunk().then(() => {
this.textInput.current.focus();
});
};
render() {
const { disabled, value } = this.props;
return (
<div>
<input type="checkbox" onClick={this.handleClick} />
<input disabled={disabled} ref={this.textInput} />
</div>
);
}
}
// Action
const toggle = () => ({
type: "TOGGLE"
});
// Thunk
const toggleThunk = () => dispatch => {
// Do your async call().then...
return Promise.resolve().then(() => dispatch(toggle()));
};
const A = connect(state => ({ disabled: state }), { toggleThunk })(Button);
const App = () => (
<Provider store={createStore(disabled, applyMiddleware(thunk))}>
<A />
</Provider>
);
render(<App />, document.getElementById("root"));
You can manage this with the prop and a ref. The ref will avoid the need to rerender the input (i.e. for autoFocus to work):
import React, { Component } from "react";
import { render } from "react-dom";
import { createStore, applyMiddleware } from "redux";
import { Provider, connect } from "react-redux";
import thunk from "redux-thunk";
const disabled = (state = true, action) => {
return action.type === "TOGGLE" ? !state : state;
};
class Button extends Component {
componentDidUpdate(prevProps) {
if (!this.props.disabled && prevProps.disabled) {
this.ref.focus();
}
}
render() {
const { disabled } = this.props;
return (
<div>
<input
type="checkbox"
checked={!disabled}
onClick={() => {
this.props.toggle();
}}
/>
<input
disabled={disabled}
ref={ref => {
this.ref = ref;
}}
/>
</div>
);
}
}
const toggle = () => ({
type: "TOGGLE"
});
const A = connect(state => ({ disabled: state }), { toggle })(Button);
const App = () => (
<Provider store={createStore(disabled, applyMiddleware(thunk))}>
<A />
</Provider>
);
render(<App />, document.getElementById("root"));

React Redux cant get reducer to pick up actions

I've been learning redux & react and am building a todo list. I've been reading and looking at different articles but cant figure out what I'm missing in my setup.
Currently you can add a todo via the input. On pushing enter it sends a addTodo action with the todo text.
I'm expecting the reducer to see the action type and update the state but it never does. What am I missing?
index.jsx
import React from 'react';
import ReactDOM from 'react-dom';
import { createStore } from 'redux';
import { Provider } from 'react-redux';
import reducer from './reducer.js';
import TodoList from './containers/container.js';
const store = createStore(reducer);
ReactDOM.render(
<Provider store={store}>
<TodoList />
</Provider>,
document.getElementById('app'));
actions.js
var uuid = require('node-uuid');
export function addTodo(text) {
console.log('action addTodo', text);
return {
type: 'ADD_TODO',
payload: {
id: uuid.v4(),
text: text
}
};
}
TodoListComponent.jsx
import React from 'react';
import TodoComponent from './TodoComponent.jsx';
import { addTodo } from '../actions/actions.js'
export default class TodoList extends React.Component {
render () {
const { todos } = this.props;
return (
<div>
<input type='text' placeholder='Add todo' onKeyDown={this.onSubmit} />
<ul>
{todos.map(c => (
<li key={t.id}>
<TodoComponent todo={t} styleName='large' />
</li>
))}
</ul>
</div>
)
}
onSubmit(e) {
const input = e.target;
const text = input.value;
const isEnterKey = (e.which === 13);
if (isEnterKey) {
input.value = '';
addTodo(text);
}
}
}
TodoComponent.jsx
import React from 'react';
import CSSModules from 'react-css-modules';
import styles from './style.css';
export default class TodoComponent extends React.Component {
render () {
const { todo } = this.props;
return (
<div styleName='large'>{todo.text}</div>
)
}
}
export default CSSModules(TodoComponent, styles);
container.js
import { connect } from 'react-redux';
import TodoList from '../components/TodoListComponent.jsx';
import { addTodo } from '../actions/actions.js';
const mapStateToProps = (state) => {
return {
todos: state
}
};
const mapDispatchToProps = (dispatch) => {
return {
addTodo: text => dispatch(addTodo(text))
}
};
export default connect(mapStateToProps, mapDispatchToProps)(TodoList);
reducer.js
import { List, Map } from 'immutable';
const init = List([]);
export default function(todos = init, action) {
console.log('reducer action type', action.type);
switch(action.type) {
case 'ADD_TODO':
console.log('ADD_TODO');
return todos.push(Map(action.payload));
default:
return todos;
}
}
In your TodoListComponent you are importing your actions directly from your actions file, but in fact you want to use the action that you map to dispatch and pass as property in the container. That explains why you see logs from the actions, but not from reducer, as the action is never dispatched to the store.
So your TodoListComponent should be:
import React from 'react';
import TodoComponent from './TodoComponent.jsx';
export default class TodoList extends React.Component {
render () {
const { todos } = this.props;
return (
<div>
<input type='text' placeholder='Add todo' onKeyDown={this.onSubmit} />
<ul>
{todos.map(c => (
<li key={t.id}>
<TodoComponent todo={t} styleName='large' />
</li>
))}
</ul>
</div>
)
}
onSubmit(e) {
const input = e.target;
const text = input.value;
const isEnterKey = (e.which === 13);
if (isEnterKey) {
input.value = '';
this.props.addTodo(text);
}
}
}
import React from 'react';
import TodoComponent from './TodoComponent.jsx';
export default class TodoList extends React.Component {
render () {
const { todos } = this.props;
return (
<div>
<input type='text' placeholder='Add todo' onKeyDown={this.onSubmit.bind(this)} />
<ul>
{todos.map(c => (
<li key={t.id}>
<TodoComponent todo={t} styleName='large' />
</li>
))}
</ul>
</div>
)
}
onSubmit(e) {
// use the addTodo passed via connect
const { addTodo } = this.props;
const input = e.target;
const text = input.value;
const isEnterKey = (e.which === 13);
if (isEnterKey) {
input.value = '';
addTodo(text);
}
}
}

Categories