How to fix "dispatch is not a function" error - javascript

I am working on a simple react-redux project that gets information about movies from the OMDB api based on search term provided by the user. I am currently having trouble trying to get text typed into the searchbar to update the store value corresponding to the title of the film to search for. I'm fairly new to react and completely new to redux I've only finished one other redux project before and I set up my actions and reducers in the exact same way as last time but this time I'm running into "Uncaught TypeError: dispatch is not a function". This was not a problem I encountered in the previous project and my google searching has not been very helpful thus far.
I've searched this problem on google and only found a few results and none of them seem to be having the exact same issue as me, they involve using mapDispatchToProps which I'm not using inside of my connect function. Supposedly when you write a mapStateToProps like I have, dispatch should just be passed down as a prop to the connected component but whenever I try to access it I get the aforementioned "Uncaught TypeError: dispatch is not a function" error.
here is the index.js for my component
import { connect } from 'react-redux';
import MovieSearch from './MovieSearchContainer';
import {
updateSearchTerm,
getMovies
} from './movieSearchActions';
function mapStateToProps(state){
return {
title: state.movieSearch.title,
year: state.movieSearch.year,
plot: state.movieSearch.plot,
released: state.movieSearch.released,
runtime: state.movieSearch.runtime,
genre: state.movieSearch.genre,
plot: state.movieSearch.plot,
ratings: {
IMDB: state.movieSearch.ratings.IMDB,
Metascore: state.movieSearch.ratings.Metascore
},
posterUrl: state.movieSearch.posterUrl,
cachedMovies: state.movieSearch.cachedMovies
};
}
export default connect(mapStateToProps)(MovieSearch);
here is my action
export function updateSearchTerm(searchTerm){
return {
type: "UPDATE_SEARCH_TERM",
payload: { searchTerm }
}
}
here is my jsx component
import React from 'react';
import PropTypes from 'prop-types';
import {
updateSearchTerm,
getMovies
} from './movieSearchActions';
export default class MovieSearchContainer extends React.Component
{
constructor(props) {
super(props);
this.handleUpdateSearchTerm =
this.handleUpdateSearchTerm.bind(this);
}
handleUpdateSearchTerm(event){
const { dispatch } = this.props;
const { value } = event.target;
dispatch(updateSearchTerm(value));
}
render() {
return (
<div>
<h1 className='text-center'>Movie Finder</h1>
<input type='text' className='col-sm-11' id='searchBar'
onChange={ this.handleUpdateSearchTerm }/>
<button type='button' id='getMovies' className='col-sm-
1'>Go!</button>
</div>
)
}
}
MovieSearchContainer.propTypes = {
store: PropTypes.object
}
here is the reducer
export default function movieSearchReducer(state = defaultState,
action) {
const { type, payload } = action;
switch(type){
case 'UPDATE_SEARCH_TERM': {
return {
...state,
title: payload.title
}
}
default: {
return state;
}
}
}
I expect changes in the searchbar on the component on the page to be reflected in the redux store, but instead I just get this error

The dispatch prop is only available when you are directly interacting with the redux-store. When you define something like mapDispatchToProps() and pass it as the 2nd argument to connect(), dispatch, gets passed to mapDispatchToProps().
const mapDispatchToProps = (dispatch) => {
return{
actionCreator: (arg) => {
dispatch(actionCreator(arg))
}
}
}
export default connect(mapStateToProps, mapDispatchToProps)(Component)
If you dont want to define mapDispatchToProps(), you can effectively bind your action-creators by passing in an object to connect() as the 2nd argument. This implicitly binds dispatch to the action-creators:
import React from "react";
import PropTypes from "prop-types";
import { connect } from "react-redux";
import { updateSearchTerm, getMovies } from "./movieSearchActions";
class MovieSearchContainer extends React.Component {
constructor(props) {
super(props);
this.handleUpdateSearchTerm = this.handleUpdateSearchTerm.bind(this);
}
handleUpdateSearchTerm(event) {
const { value } = event.target;
this.props.updateSearchTerm(value);
}
render() {
console.log(this.props.movies);
return (
<div>
<h1 className="text-center">Movie Finder</h1>
<input
type="text"
className="col-sm-11"
id="searchBar"
onChange={this.handleUpdateSearchTerm}
/>
<button
type="button"
id="getMovies"
className="col-sm-
1"
>
Go!
</button>
</div>
);
}
}
MovieSearchContainer.propTypes = {
store: PropTypes.object
};
const mapStateToProps = state => {
return {
title: state.movieSearch.title,
year: state.movieSearch.year,
plot: state.movieSearch.plot,
released: state.movieSearch.released,
runtime: state.movieSearch.runtime,
genre: state.movieSearch.genre,
plot: state.movieSearch.plot,
ratings: {
IMDB: state.movieSearch.ratings.IMDB,
Metascore: state.movieSearch.ratings.Metascore
},
posterUrl: state.movieSearch.posterUrl,
cachedMovies: state.movieSearch.cachedMovies
};
};
export default connect(
mapStateToProps,
{
updateSearchTerm,
getMovies
}
)(MovieSearchContainer);
With that, you do not need to explicitly call dispatch to use your action-creator. Simply use this.props.nameOfActionCreator()
See sandbox for example: https://codesandbox.io/s/simple-redux-7s1c0

I think you should connect your component inside your jsx file. Then you can access with this.props.yourFunctionToDispatch
import React from 'react';
import { connect } from 'react-redux';
import PropTypes from 'prop-types';
import {
updateSearchTerm,
getMovies
} from './movieSearchActions';
class MovieSearchContainer extends React.Component
{
constructor(props) {
super(props);
this.handleUpdateSearchTerm =
this.handleUpdateSearchTerm.bind(this);
}
handleUpdateSearchTerm(event){
const { dispatch } = this.props;
const { value } = event.target;
dispatch(updateSearchTerm(value));
}
render() {
return (
<div>
<h1 className='text-center'>Movie Finder</h1>
<input type='text' className='col-sm-11' id='searchBar'
onChange={ this.handleUpdateSearchTerm }/>
<button type='button' id='getMovies' className='col-sm-
1'>Go!</button>
</div>
)
}
}
const mapStateToProps = state => {
return {
title: state.movieSearch.title,
year: state.movieSearch.year,
plot: state.movieSearch.plot,
released: state.movieSearch.released,
runtime: state.movieSearch.runtime,
genre: state.movieSearch.genre,
plot: state.movieSearch.plot,
ratings: {
IMDB: state.movieSearch.ratings.IMDB,
Metascore: state.movieSearch.ratings.Metascore
},
posterUrl: state.movieSearch.posterUrl,
cachedMovies: state.movieSearch.cachedMovies
};
}
MovieSearchContainer.propTypes = {
store: PropTypes.object
}
export default connect(mapStateToProps, {yourFunctionToDispatch})(MovieSearchContainer);

Related

MapStrateToProps doesn't connect to the local storage using redux and react

I have a web app that is suppose to show a list of notes made by the user on the dashboard if said list exist (that is if the user wrote any note at all). I wrote the reducer, the actions and I connected state and dispatch in order for it to work. But for some reason the notes created don't appear once in the dashboard when I write them, I already made sure that the ADD_NOTE action gets fired and that the reducer updates the data in redux, but in the dashboard component that data disappears.
This is my reducer.
export default (state = [], action) => {
switch (action.type) {
case "ADD_NOTE":
return [
...state,
action.note
];
case "REMOVE_NOTE":
return state.filter(({ id }) => id !== action.id);
default:
return state;
}
}
And those are my actions
import { v4 as uuidv4 } from 'uuid';
export const addNote = ({ title = "", body = ""} = {}) => ({
type: "ADD_NOTE",
note : {
title,
body,
id : uuidv4()
}
});
export const removeNote = ({ id } = {}) => ({
type: "REMOVE_NOTE",
id
});
This is the component that holds the create note form.
import React, { Component } from 'react';
class CreateNote extends React.Component{
constructor(props){
super(props);
this.onTitleChange = this.onTitleChange.bind(this);
this.onBodyChange = this.onBodyChange.bind(this);
this.onSubmit = this.onSubmit.bind(this);
this.state = {
title: "",
body: "",
error: ""
}
}
onTitleChange(e){
const title = e.target.value;
this.setState({ title });
}
onBodyChange(e){
const body = e.target.value;
this.setState({ body });
}
onSubmit(e){
e.preventDefault();
if(!this.state.title || !this.state.body){
this.setState({ error : "Please fill in all gaps"});
} else {
this.setState({ error: ""});
const data = { title: this.state.title, body: this.state.body}
this.props.onChange(data);
}
}
render(){
return(
<div>
{this.state.error && <p>{this.state.error}</p>}
<form onSubmit = {this.onSubmit}>
<label>Put a title for your note</label>
<input
placeholder="Title"
type="text"
value={this.state.title}
autoFocus
onChange = {this.onTitleChange}
/>
<label>Write your note</label>
<textarea
placeholder="Note"
value={this.state.body}
autoFocus
onChange = {this.onBodyChange}
/>
<input type="submit" value="Submit"/>
</form>
</div>
);
}
}
export default CreateNote;
And this is the component that fires the ADD_NOTE action
import React, { Component } from 'react';
import CreateNote from "./actions/CreateNote";
import Header from "./Header";
import { addNote } from "../actions/noteActions"
import { connect } from 'react-redux';
class Create extends React.Component{
constructor(props){
super(props);
this.eventHandler = this.eventHandler.bind(this);
}
eventHandler(data){
this.props.addNote(data);
this.props.history.push("/");
}
render(){
return (
<div>
<Header />
<CreateNote onChange = {this.eventHandler}/>
</div>
)
}
}
const mapDispatchToProps = (dispatch) => ({
addNote: (note) => dispatch(addNote(note))
});
export default connect(null, mapDispatchToProps)(Create);
And finally this is the dashboard component that renders the notes if they exist
import React from "react";
import ListItem from "./actions/ListItem";
import { connect } from 'react-redux';
const ListGroup = (props) => (
<div>
{
props.notes.length === 0 ? <h1>Write a note!</h1> :
(
props.notes.map((note) => {
return <ListItem key={note.id} {...note} />;
})
)
}
</div>
)
// The mapStateToProps does not connect with the local state, the action ADD_NOTE fires whenever
// the Create form is submited and the reducer updates the redux storage. So the problem lies here ?
// It could be that state.note is not definded but I don't know where should I define it if I have to,
// and apparently I don't have to ???????????????
const mapStateToProps = (state) => {
return {
notes: state.note
};
};
export default connect(mapStateToProps)(ListGroup);
When I try to run this it fires an error:
ListGroup.js?11a1:5 Uncaught TypeError: Cannot read property 'length' of undefined
at ListGroup (ListGroup.js?11a1:5)
Showing that the data that gets passed to the props is undefined. I'm thinking that it could be that state.note is not defined and I have to define it somewhere but I don't know if that's the case.
Use Hooks in functional components
connect() is only valid for class based components. For functional components you need to use hooks. Specifically the useSelector hook for reading redux state and useReducer to emit actions. You can find more instructions on redux hooks here https://react-redux.js.org/api/hooks#useselector

Unable to update redux store - React Native

I am new to using redux for React Native and am testing it with a simple case. I have been able to successfully connect to the store, and I can see the action is dispatched properly using the redux debugger, however, the store is not updating in the debugger. I've tried several different implementations, but nothing is working. Any help would be appreciated!
Component:
import React, { PureComponent } from 'react'
import { Text, TouchableOpacity, SafeAreaView, Alert, Button } from 'react-native'
import { Navigation } from 'react-native-navigation';
import { connect } from 'react-redux'
import simpleAction from '../store/actions/simpleAction'
class App2 extends PureComponent {
constructor(props){
super(props);
}
pressRedux = () => {
const data = 'hello'
this.props.simpleAction(data)
}
render() {
return (
<SafeAreaView>
<Text>
{this.props.state.simpleReducer.text}
</Text>
<Button onPress = {this.pressRedux} title = 'Redux' />
</SafeAreaView>
)
}
}
function mapStateToProps(state) {
return {
state: state
};
}
const mapDispatchToProps = {
simpleAction
}
export default connect(mapStateToProps, mapDispatchToProps)(App2);
Action:
import {SET_TEXT} from '../types/types'
export default function simpleAction(data) {
return({
type: SET_TEXT,
payload: data
})
}
reducer:
import SET_TEXT from '../types/types'
const INITIAL_STATE = {
text: 'Hi'
}
const simpleReducer = (state = INITIAL_STATE, action ) => {
switch(action.type){
case SET_TEXT:
return { ...state, text: action.payload};
default:
return state;
}
}
export default simpleReducer;
The code you've shared here looks correct. Only thing I can suggest is, if you're seeing the action come through in the debugger, your issue is either with the data/payload or logic within simpleReducer.
In this case you have it properly stripped down so I'd almost think this isn't actually the code you are running, it might be something in your build process?

React Redux this.props always return undefined

I'm now at React and I'm doing some apps to study, learn more about. Aand right now I'm trying to add the logged user info to redux state, but when I try to check the value of this.props.user my app always returns undefined.
My reducer.js
import { LOG_USER } from '../actions/actions';
let initialState = {
user: {
userName: '',
imageUrl: ''
}
}
const userInfo = (state = initialState, action) => {
switch (action.type) {
case LOG_USER:
return {
...state,
user: action.user
};
default:
return state;
}
}
const reducers = userInfo;
export default reducers;
My actions.js
export const LOG_USER = 'LOG_USER';
My SignupGoogle.js component:
import React, { Component } from 'react';
import Button from '#material-ui/core/Button';
import firebase from '../../config/firebase';
import { connect } from 'react-redux'
import { LOG_USER } from '../../actions/actions';
import './SignupGoogle.css'
class SignupGoogle extends Component {
constructor(props) {
super(props);
}
signup() {
let provider = new firebase.auth.GoogleAuthProvider();
firebase.auth().signInWithPopup(provider).then(function(result) {
console.log('---------------------- USER before login')
console.log(this.props.user)
let user = {
userName: result.user.providerData[0].displayName,
imageUrl: result.user.providerData[0].photoURL
}
console.log(user)
this.props.logUser(user)
console.log('---------------------- USER after login')
console.log(this.props.user)
}).catch((error) => {
console.log(error.code)
console.log(error.message)
console.log(error.email)
})
}
render() {
return (
<Button onClick={this.signup} variant="contained" className="btn-google">
Sign Up with Google
<img className='imgGoogle' alt={"google-logo"} src={require("../../assets/img/search.png")} />
</Button>
);
}
}
const mapStateToProps = state => {
return {
user: state.user
};
}
const mapDispatchToProps = dispatch => {
return {
logUser: (user) => dispatch({type: LOG_USER, user: user})
};
}
export default connect(mapStateToProps, mapDispatchToProps)(SignupGoogle);
And my index.js
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import * as serviceWorker from './serviceWorker';
import { BrowserRouter as Router } from 'react-router-dom';
import { Provider } from 'react-redux'
import { createStore } from 'redux';
import reducers from './reducers/reducers';
const store = createStore(reducers)
ReactDOM.render(
<Provider store={store}>
<Router>
<App />
</Router>
</Provider>,
document.getElementById('root')
);
serviceWorker.unregister();
This is what I can get at my browser log after login with Google firebase:
That's because you're onClick handler method is not bound to the instance of the component, modify your constructor like this and your props should no longer return undefined:
constructor(props) {
super(props);
this.signup = this.signup.bind(this);
}
Alternatively you could also modify your onClick method to look like this:
<Button onClick={() => this.signup()} variant="contained" className="btn-google">
or turn your onClick handler method into an arrow function:
signup = () => {
// ...
}
...
<Button onClick={this.signup} variant="contained" className="btn-google">
but the first option using bind is the preferred one.
Refer to the docs for more information on event handling.
EDIT:
I missed that there was another callback function involved.
You're accessing this.props from within another function in the signInWithPopup-callback. Change your callback to an arrow function, which should preserve the context of the signup method and fix your issue:
firebase.auth().signInWithPopup(provider).then(result => {
// ...
}).catch(error => {
// ...
});
It's all about context. Since your signup function is bound to the onclick event, the this context is the <button>.
You can either in the constructor set the this context:
constructor(props) {
super(props);
this.signup = this.signup.bind(this);
}
or use arrow syntax:
signup = () => {
}
React documentation has a good answer for event binding here: https://reactjs.org/docs/handling-events.html
Your signup definition is fine, but you can just wrap it in an arrow function that has the proper 'this' value.
onClick={()=>signup()}

react native componetWillUpdate not working

I am trying to render a component with already existing data from state (provided from redux-persist), the data in is state.login.user (i can see it in console.log in the mapStateToProps function that is being called and returns the dataObject : state.login.user but the dataObject is not being updated and because of that componentWillReceiveProps is not being called.
Can you point me to what im doing wrong?
import React from 'react'
import { connect } from 'react-redux'
import { ScrollView, AppRegistry, Component, Text, Image, View, Button, Modal, TouchableOpacity } from 'react-native'
import { GiftedForm, GiftedFormManager } from 'react-native-gifted-form'
// Styles
import styles from './Styles/MyProfileScreenStyles'
class MyProfileScreen extends React.Component {
constructor (props, context) {
const dataObject = {
profile: {
last_name : undefined,
}
}
super(props, context)
this.state = {
form: {
lastName: dataObject.profile.last_name,
tos: false
}
}
}
handleValueChange (values) {
this.setState({form: values})
}
componentWillReceiveProps (newProps) {
console.tron.log("componend will receive")
console.tron.log(newProps)
if (newProps.dataObject) {
this.setState({
dataObject: newProps.dataObject
})
}
}
render () {
const {lastName, tos, gender} = this.state.form
console.log('render', this.state.form)
return (
<View style={styles.container}>
<GiftedForm
formName='signupForm'
openModal={(route) => { this.props.navigator.push(route) }}
onValueChange={this.handleValueChange.bind(this)}
>
<GiftedForm.TextInputWidget
name='lastName'
title='Last name'
placeholder='Last name'
clearButtonMode='while-editing'
value={lastName}
/>
<GiftedForm.HiddenWidget name='tos' value={tos}/>
</GiftedForm>
</View>
)
}
}
const mapStateToProps = (state) => {
if( state.login.user !== null){
console.tron.log("test map state to props")
return {
dataObject: state.login.user
}
}
return {}
}
export default connect(mapStateToProps)(MyProfileScreen)
componentWillReceiveProps is only called when the props are updated after the component has rendered, before the component is re-rendered. You'll want to set the state inside your constructor as the props should already be there.

Why aren't my todo items rendering with Redux?

I'm doing a simple redux / react todo app. I can't get the todo items to show up. I'm able to console.log the data, but can't get it to appear. What am I doing wrong?
I separated the files, here is my app.js:
import React, { Component } from 'react';
import Todos from './todos';
import TodoList from "./todo_list";
export default class App extends Component {
render() {
return (
<div>
<Todos />
<TodoList/>
</div>
);
}
}
Here is the container Todos:
import React, {Component} from 'react';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import { addTodo } from '../actions/index';
class Todos extends Component {
constructor(props) {
super(props);
this.state = {text: ''};
}
addTodo(e) {
e.preventDefault();
this.props.addTodo(this.state.text);
this.setState({
text: ''
});
}
updateValue(e) {
this.setState({text: e.target.value})
}
render() {
return (
<div>
<form onSubmit={(e) => this.addTodo(e)}>
<input
placeholder="Add Todo"
value={this.state.text}
onChange={(e) => {
this.updateValue(e)
}}
/>
<button type="submit">Add Todo</button>
</form>
</div>
);
}
}
function mapDispatchToProps(dispatch) {
return bindActionCreators({addTodo}, dispatch);
}
export default connect(null, mapDispatchToProps)(Todos);
Here is the TodoList:
import React, {Component} from 'react';
import {connect} from 'react-redux';
class TodoList extends Component {
render() {
return (
<ul>
{ this.props.todo.map((tod) => {
return <li key={tod.message}>{ tod.message }</li>
})}
</ul>
);
}
}
function mapStateToProps({ todo }) {
console.log({ todo });
return { todo };
}
export default connect(mapStateToProps)(TodoList);
Reducer:
import { ADD_TODO } from '../actions/types';
export default function(state=[], action) {
switch(action.type) {
case ADD_TODO:
return [ action.payload.message, ...state ]
}
return state;
}
And action
import { ADD_TODO } from './types';
const uid = () => Math.random().toString(34).slice(2);
export function addTodo(message) {
const action = {
id: uid(),
message: message
};
return {
type: ADD_TODO,
payload: action
};
}
This is what I get from the console.log({todo});
Here is my reducers/index:
import { combineReducers } from 'redux';
import TodosReducer from './reducer_addTodo';
const rootReducer = combineReducers({
todo: TodosReducer
});
export default rootReducer;
It's because there's a disconnect between your TodoList and reducer. TodoList, when mapping, expects each todo to have a message prop, but your reducer, when returning next state, only includes the message in the state array, not an object with the message property:
case ADD_TODO:
return [ action.payload.message, ...state ]
Instead, do not just put the message string in the next state's array, put in the whole object:
case ADD_TODO:
return [ action.payload, ...state ]
Now every single element in the todo array will be an object and have a message and id property. Also, try using an always unique expression for key -- it really shouldn't be the todo message, nor the id you supplied because it's using Math.random which both have a possibility of keys being the same.

Categories

Resources