After entering the text in input field and clicking submit button the error occurs: Uncaught TypeError: dispatch is not a function
at onSubmit
Connecting state and props seems to be correct.
What could be wrong?
*/TODOLIPUT*/
import React from 'react'
import { connect } from 'react-redux'
import {addTodo} from '../actions/index'
import {bindActionCreators} from 'redux'
let AddTodo = ({ dispatch }) => {
let input
return (
<div>
<form onSubmit={e => {
e.preventDefault()
if (!input.value.trim()) {
return
}
dispatch(addTodo(input.value))
input.value = ''
}}>
<input ref={node => {
input = node
}} />
<button type="submit">
Add Todo
</button>
</form>
</div>
)
}
const mapStateToProps = (state) => {
return {
todos: state.todos
}
}
function matchDispatchToProps(dispatch){
return bindActionCreators({addTodo: addTodo}, dispatch);
}
export default connect(mapStateToProps, matchDispatchToProps)(AddTodo)
/*TODOLIST*/
import React from 'react';
import {Todo} from './todo';
import { connect } from 'react-redux'
import {bindActionCreators} from 'redux'
const TodoList = ({ todos, onTodoClick }) => (
<ul>
{todos.map(todo =>
<Todo {...todo} />
)}
</ul>
)
function mapStateToProps(state) {
return {
todos:state.todos
}
}
export default connect(mapStateToProps, null)(TodoList)
/* REDUCER */
import {combineReducers} from 'redux';
export const reducers = combineReducers({
todos:todos
})
export function todos(state=[], action) {
switch(action.type) {
case 'ADD_TODO':
return [
...state,
{
text:text,
completed:false
}
]
default:
return state
}
}
*/ACTION*/
export const addTodo = (text) => {
return {
type: 'ADD_TODO',
text
}
}
You can make two changes to get your code working.
First: If you use dispatch in your component, you need not use mapDispatchToProps since dispatch will be made available to you by default by connect
export default connect(mapStateToProps)(AddTodo)
Second: The other way is to make use of bindActionCreators which binds your action creators to dispatch and hence there is not need for a separate dispatch event in your component
let AddTodo = (props) => {
let input
return (
<div>
<form onSubmit={e => {
e.preventDefault()
if (!input.value.trim()) {
return
}
props.addTodo(input.value);
input.value = ''
}}>
<input ref={node => {
input = node
}} />
<button type="submit">
Add Todo
</button>
</form>
</div>
)
}
One more thing,since your are resolvind the props to {dispatch} in your AddTodo argument, you wont be having access to the todos state.
You don't need to use dispatch, since you have used bindActionCreators in your mapDispatchToProps .
bindActionCreators is a helper that enables action creators to directly dispatch actions. So you can just invoke your action creator and it should automatically dispatch the action.
You can either use dispatch and not pass mapDispatchToProps, or you can use the props injected by mapDispatchToProps, and not use dispatch. This is why mapDispatchToProps is called this way—it lets you define some other props based on dispatch so you don’t need to use it again.
Check this: https://github.com/reactjs/react-redux/blob/master/docs/api.md#examples
Related
I'm tried to use React Hooks form and pass the values to the state from the Redux store through my setLog() action, and as I understand my action doesn't work but not getting through any type of error. So, I don't know what exactly is going wrong. Also, I use redux-saga, but for now, I don't use it, I just declared it, it could be the issue or not?
SignUp.js file
import React from "react";
import { useForm } from "react-hook-form";
import { connect } from "react-redux";
import { setLog } from "../actions";
function SignUp({ info }) {
const { register, handleSubmit, setValue } = useForm();
const onSubmit = data => setLog(data);
return (
<React.Fragment>
<form onSubmit={handleSubmit(onSubmit)}>
<input {...register("login")} />
<input {...register("password")} />
<input type="submit" />
</form>
{/* <h1>{info}</h1> */}
</React.Fragment>
);
}
const mapStateToProps = (state) => {
return {
info: state.signUp
};
}
export default connect(mapStateToProps, { setLog })(SignUp);
Actions:
export const setLog = (data) => {
return {
type: 'SET_LOG',
payload: data
};
}
SignUp reducer
export default(state=[],action) => {
switch(action.type){
case 'SET_LOG':
return [...state, ...action.payload];
default:
return state;
}
}
My store:
import { createStore, applyMiddleware, compose } from 'redux';
import createSagaMiddleware from 'redux-saga';
import rootSaga from '../sagas';
import rootReducer from '../reducers';
const configureStore = () => {
const sagaMiddleware = createSagaMiddleware();
const store = createStore(
rootReducer,
window.__REDUX_DEVTOOLS_EXTENSION__
? compose(
applyMiddleware(sagaMiddleware),
window.__REDUX_DEVTOOLS_EXTENSION__(),
)
: applyMiddleware(sagaMiddleware),
);
sagaMiddleware.run(rootSaga);
return store;
};
export default configureStore;
What is happening is that you're not calling the setLog from the props, you're just calling the function without tying it with redux, you need to attach the function using the mapDispatchToProps function and calling the dispatch inside that function like this:
const mapDispatchToProps = (dispatch) => {
return {
// Calling it submitLog to avoid name collisions with the imported setLog
submitLog: (data) => dispatch(setLog(data)),
}
}
export default connect(mapStateToProps, mapDispatchToProps)(SignUp);
The code above is connecting the action to redux, now you just need to call it as a prop like this:
// Now you will have the submitLog as a prop and it will be tie to redux.
function SignUp({ info, submitLog }) {
const { register, handleSubmit, setValue } = useForm();
const onSubmit = data => submitLog(data);
// Rest of the code
}
Dispatch in component is UNDEFINED and dispatch in mapDispatchToProps is ok
Action and reducers is ok - 100%;
I want transfer to dispatch in action to create request and run action from action
My component:
import React, { Component, Fragment } from 'react';
import { bindActionCreators } from 'redux';
import { connect } from 'react-redux';
import styled from 'styled-components';
import { carAction, fetchDogAction } from '../../../actions/CarAction';
class CarListContainer extends Component {
render () {
const { cars, carAction, dispatch, dog } = this.props;
console.log(dispatch) // THIS UNDEFINED!!!!
return(
<Fragment>
<Title onClick={() => {
fetchDogAction(dispatch)
}}>HEllo</Title>
{cars.map((elem) =>
<Title onClick={() => {carAction (elem)}} key={elem.id}>{elem.mark}</Title>
)}
{dog ? <img src={dog.message} /> : null }
</Fragment>
)
}
}
const Title = styled('h2')`
color: red;
font-size: 30px;
`;
function mapStateToProps (state) {
return {
cars: state.cars,
dog: state.dog
}
}
function mapDispatchToProps(dispatch) {
console.log(dispatch) //THIS DISPATCH IS OK
return bindActionCreators ({
carAction: carAction,
fetchDogAction: fetchDogAction
}, dispatch);
}
export default connect(mapStateToProps, mapDispatchToProps)(CarListContainer);
Your error comes from having bindActionCreators() inside mapDispatchToProps() which is not recommended practice, since mapDispatchToProps uses bindActionCreators internally.
bindActionCreators and mapDispatchToProps - Do I need them?
You can try using an arrow function and dispatching actions directly in the mapDispatchToProps method.
import * ACTIONS from './actions'
function mapDispatchToProps(dispatch) {
return {
carAction: (elem) => dispatch(ACTIONS.fetchCarAction(elem)) ,
dogAction: () => dispatch(ACTIONS.fetchDogAction())
}
}
//action creators import them into your component
export const fetchCarAction = (elem) => {
return {
type: ACTION_TYPES.CARACTION,
payload: elem
}
}
export const fetchDogAction = () => {
return {
type: ACTION_TYPES.DOGACTION,
}
}
//render method
<Title onClick={() => this.props.dogAction() }>HEllo</Title>
{cars.map((elem) =>
<Title onClick={() => this.props.carAction(elem) } key={elem.id}>{elem.mark}
</Title>
)}
So in your render you will call the name of the property in mapDispatchToProps and not the name of the action creator, I kept the name of the action creators and name of the properties different so you can see this.
If you want a fully functioning react-redux app you can check out this starter project I built, I got good feedback on it.
https://github.com/iqbal125/modern-react-app-sample
I have a component that connects to a store and displays a child component like below:
render() {
return <div>
<div className="userBox">
<ProfilePhoto userid={this.props.id} />
</div>
<div className="nameTitleBox">
<div className="firstLastTitle">
<h1>{this.props.firstName} {this.props.lastName}</h1>
</div>
<IDBox userid={this.props.id} />
</div>
<div className="childcomponent">
<childComponent />
</div>
<div className="profileBox">
<EditInterests interestsList={this.props.interest} />
</div>
</div>
}
}
export default connect(
(state) => state.user,
UserState.actionCreators
)(User);
I want the child component to be a smart component that loads it's own data and controls everything itself. The code for it is pretty simple.
import * as React from 'react';
import { Link, RouteComponentProps } from 'react-router-dom';
import { ApplicationState } from '../../store';
import { connect } from 'react-redux';
import * as ChildState from '../../store/childStore';
export class ChildComponent extends React.Component {
componentWillMount() {
this.props;
}
render() {
return (<div>
<div className="textCenter"><h2 id="sss">{this.props.text}</h2></div>
<div className="textRight">
<input type="button" className="button" value="Yes" /> <b className="textColor">No</b>
</div>
</div>
</div>
</div>)
}
}
const mapDispatchToProps = (dispatch) => {
return {
action: dispatch(ChildState.actionCreators.requestChildren())
}
}
export default connect(
mapDispatchToProps,
ChildState.actionCreators
)(ChildComponent);
this.props in the child component is always an empty object. Nothing from the child state is in there, the initial state, the actions, dispatch...anything. I've tried a few different things. ChildState loads fine if I actually load it in the parent. Don't know why it's not loading in the child and connecting the props.
Adding the store below:
import { Action, Reducer } from 'redux';
import { fetch, addTask } from 'domain-task';
import { AppThunkAction } from './';
export const actionCreators = {
requestChildren: () => (dispatch, getState) => {
let url = 'random';
var myheaders = new Headers();
myheaders.append("X-Requested-With", "XMLHttpRequest");
let fetchTask = fetch(url, {
headers: myheaders,
credentials: "same-origin"
})
.then(response => response.json())
.then(data => {
dispatch({ type: 'POST_ACTION', children: data });
});
addTask(fetchTask);
}
}
export const initialState = { ... };
export const reducer = (state = initialState, incomingAction) => {
const action = incomingAction;
switch (action.type) {
case 'REQUEST_ACTION':
return {
...
};
case 'POST_ACTION':
return {
...
};
default:
}
return state || initialState;
};
I believe the problem is in mapDispatchtoProps have you tried using bindActionCreators
bindActionCreators make sure action (ChildState.actionCreators.requestChildren) flows through the middleware if there is any and then to the reducers
import { bindActionCreators} from 'redux';
const mapDispatchToProps = (dispatch) => {
return bindActionCreators({
ChildState.actionCreators.requestChildren}, dispatch); }
export default connect(
ChildState.actionCreators,
mapDispatchToProps
)(ChildComponent);
This was happening because I was exporting both the child component and the connect function. I removed the export on the child component and its working now as expected.
I am trying to create search/filter application with help of this SO post. however, I checked logs my reducers is not being called after my action search action triggered. I have registered that one combine reducers also. May I have done something wrong in foolish way but I am not getting what? and please let me know should I go with my below approach or should I use class/state approach
Here is my search container
import Search from "../components/Search"
import React from "react"
import {connect} from "react-redux"
const SearchContainer = () =>(
<Search/>
)
const mapStateToProps= state => {
console.log("state",state)
return{
results:state.searchResult.values
}
}
export default connect(
mapStateToProps
) (SearchContainer)
Search.js
import React from "react"
import Select from "react-select"
import {connect} from "react-redux"
import {bindActionCreators} from 'redux';
import {search} from "../action/SearchAction"
const Search = () => {
// const {search,value} = this.props
return (
<div>
<input name="search" id="searchbutton" onKeyUp={(event) => search(event.target.value)}></input>
</div>
)
}
const mapDispatchToProps= (dispatch) => (
bindActionCreators({search},dispatch)
)
export default connect(
mapDispatchToProps
// mapStateToProps
) (Search)
SearchActioN
import names from "../api/nameList.js"
export function search (value) {
console.log("search triggere",value)
return {
type:"SEARCH",
payload:value
}
}
SearchReducer
import * as intialState from "../api/names.json"
const intialValues = {names:['vihag','pratixa','saisunnel','eshwaran'],values:[]}
export default function reducer(
state=intialValues,
action){
console.log("search reducer")
switch(action.type){
case "SEARCH":{
const values=state.names.filter(val => val.includes(action.payload))
const newState={...state,values};
console.log("new search state",newState)
return newState
}
default:
return state;
}
}
In your Search.js component, it looks like you are using connect incorrectly.
const Search = () => {
const {search,value} = this.props
return (
<div>
<input name="search" id="searchbutton" onKeyUp={(event) => search(event.target.value)}></input>
</div>
)
}
const mapDispatchToProps = (dispatch) => (
bindActionCreators({search},dispatch)
)
export default connect(
undefined, // mapState to props is always the first argument
mapDispatchToProps,
)(Search)
const Search = () => {
// const {search,value} = this.props
return (
<div>
<input name="search" id="searchbutton" onKeyUp={(event) => search(event.target.value)}></input>
</div>
)
}
You seem to have search commented out, but you use it in onKeyUp on the next lines. This would cause you to just call the actual search function. You should also make sure the mapped action does not share the same name as the import. You also seem to have your dispatch in the wrong spot.
export default connect(
null,
mapDispatchToProps
) (Search)
I am standing in a tricky situation.
I my reducer rhythmReducer.js is the following:
import {TOGGLE_NOTE_VALUE} from '../constants/actionTypes';
import objectAssign from 'object-assign';
import initialState from './initialState';
export default function rhythmReducer(state = initialState.rhythm, action) {
let newState = objectAssign({}, state);
console.log("---RhythmReducer");
console.log(action.type);
switch (action.type) {
case TOGGLE_NOTE_VALUE:
console.log("TOGGLE_NOTE_VALUE");
return newState;
default:
return newState;
}
}
The component using it is RhythmContainer.js:
import React, {PropTypes} from 'react';
import {connect} from 'react-redux';
import {bindActionCreators} from 'redux';
import * as actions from '../actions/rhythmActions';
import {Meter} from './Meter';
export const RhythmContainer = (props) => {
let rows = [];
for (let i=0; i < props.rhythm.meters.length; i++) {
rows.push(<Meter key={i} actions={actions} rhythm= {props.rhythm.meters[i]}/>);
}
const handleClick = () => {
return props.store.dispatch(actions.toggleNoteValue);
};
return (
<div onClick={handleClick}>
This will be a 4/4 rhythm
{rows}
</div>
);
};
RhythmContainer.propTypes = {
rhythm: PropTypes.object.isRequired,
store: PropTypes.object.isRequired,
};
function mapStateToProps(state) {
return {
rhythm: state.rhythm,
};
}
function mapDispatchToProps(dispatch) {
return {
actions: bindActionCreators(actions, dispatch)
};
}
export default connect(
mapStateToProps,
mapDispatchToProps
)(RhythmContainer);
My action is defined in rhythmActions.js
import * as types from '../constants/actionTypes';
export function toggleNoteValue() {
console.log("toggleNoteValue");
return {type: types.TOGGLE_NOTE_VALUE};
}
Even though the reducer runs when the page is initializing I can not get it to run when I click on the div.
toggleNoteValue() is firing up but it never goes in the actual Reducer.
Any help?
PS the full project is here just in case it helps: https://github.com/ichionid/rhythmGeneratorReact/tree/master/src
Here are a couple things to try.
In your project, configureStore.js imports a rootReducer from
"../rootReducer", but there's no such module. I'm not sure if this is
just a commit issue, but it's worth checking.
The argument to dispatch should be an action. actions.toggleNoteValue
is not an action, it's a function that returns an action. Try
props.store.dispatch(actions.toggleNoteValue()) or
props.actions.toggleNoteValue() instead.
I sometimes notice this problem when reducers don't fire because they've not been put through mapDispatchToProps correctly:
// WRONG
import { action } from './actions'
// action will still fire as a function, but that's it
const Comp = ({ label }) => <button onClick={() => action()}>{label}<button>
export default connect(mapStateToProps, { action })
// RIGHT
import { action } from './actions'
// action is sent in as a prop meaning we use the connected version rather than the action directly
const Comp = ({ action, label }) => <button onClick={() => action()}>{label}<button>
export default connect(mapStateToProps, { action })