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".
Related
I'm trying pass the data from reducer to component and receive as props.
But the data return UNDEFÄ°NED, so I have tried console the data on reducer and action, but it's okey. There isn't any problem with the data coming from the API, but it always return to component undefined. Where is my fault?
Action
export default ProfileTab;
import axios from 'axios';
import { BASE, API_KEY } from '../config/env';
export const FETCHED_MOVIES = 'FETCHED_MOVIES';
export function fetchMovies() {
return (dispatch) => {
axios
.get(`${BASE}s=pokemon&apikey=${API_KEY}`)
.then((result) => result.data)
.then((data) =>
dispatch({
type: FETCHED_MOVIES,
payload: data.Search,
}),
);
};
}
Reducer
import { FETCHED_MOVIES } from '../actions/movies';
const initialState = {
fetching: false,
fetched: false,
movies: [],
error: {},
};
export default (state = initialState, action) => {
switch (action.type) {
case 'FETCHED_MOVIES':
return {
...state,
movies: action.payload,
};
default:
return state;
}
};
Component
import React, { Component } from 'react';
import { connect } from 'react-redux';
import PropTypes from 'prop-types';
import { fetchMovies } from '../../actions/movies';
class Case extends Component {
static propTypes = {
movies: PropTypes.object.isRequired,
};
constructor(props) {
super(props);
}
componentDidMount() {
this.props.fetchMovies();
}
onChangeHandler = (e) => {
this.setState({
input: e.target.value,
});
};
render() {
console.log(this.props.movies);
return (
<div>
<div className="movies-root">
<div className="movies-wrapper">
<div className="movies-container safe-area">
<h1>mert</h1>
</div>
</div>
</div>
</div>
);
}
}
const mapStateToProps = (state) => {
return {
movies: state.movies,
};
};
const mapDispatchToProps = {
fetchMovies,
};
export default connect(mapStateToProps, mapDispatchToProps)(Case);
Do this in the connect statement:
export default connect(mapStateToProps,{fetchMovies})(Case);
And remove the mapDispatchToProps function from your code.
Dispatching props as an object is quite incorrect. Try this, and it should work.
That's because your mapDispatchToProps function should return an object and take dispatch as parameter. Each field in your returned object should contain a function that dispatches your action.
So try something like this:
const mapDispatchToProps = dispatch => {
return {
fetchMovies: () => dispatch(fetchMovies())
}
}
Although there's already an accepted answer, I'm not sure how correct it is, as it's completely valid to pass mapDispatchToProps the way you did with the latest react (16.13.1) and react-redux (7.2.1) versions (I'm not sure about earlier versions).
Now, assuming your question contains the whole code, there are two important things missing:
Creating the store:
import { createStore } from "redux";
const store = createStore(reducer);
and passing it to the Provider component:
<Provider store={store}>
If you go ahead and do as above, you'll see that this.props.fetchMovies emits the following error:
Actions must be plain objects. Use custom middleware for async actions.
To fix it, do as it says and add a middleware, e.g. thunk:
import { createStore, applyMiddleware } from "redux";
import thunk from "redux-thunk";
const store = createStore(rootReducer, applyMiddleware(thunk));
What follows is the full code. Note that I "split" fetchMovies into two functions: sync and async, for illustrating the difference usage between the two. I also modified your code (made is shorter, mostly) for this answer's readability. You can also see a live demo here:
File app.js
import React, { Component } from "react";
import { connect } from "react-redux";
import { fetchMoviesSync, fetchMoviesAsyncMock } from "./api";
class App extends Component {
componentDidMount() {
this.props.fetchMoviesSync();
this.props.fetchMoviesAsyncMock();
}
render() {
return (
<div>
<div className="movies-root">
<div className="movies-wrapper">
<div className="movies-container safe-area">
{this.props.movies.join("\n")}
</div>
</div>
</div>
</div>
);
}
}
const mapStateToProps = (state) => ({ movies: state.movies });
const mapDispatchToProps = {
fetchMoviesSync,
fetchMoviesAsyncMock
};
export default connect(mapStateToProps, mapDispatchToProps)(App);
File api.js
export const FETCHED_MOVIES = "FETCHED_MOVIES";
export const fetchMoviesSync = () => ({
type: FETCHED_MOVIES,
payload: ["movie1", "movie2", "movie3", "movie4"]
});
export const fetchMoviesAsyncMock = () => (dispatch) => {
dispatch({
type: FETCHED_MOVIES,
payload: ["movie5", "movie6", "movie7", "movie8"]
});
};
File reducer.js
const initialState = {
movies: [],
};
export default (state = initialState, action) => {
switch (action.type) {
case "FETCHED_MOVIES":
return {
...state,
movies: state.movies.concat(action.payload)
};
default:
return state;
}
};
File index.js
import React from "react";
import ReactDOM from "react-dom";
import Case from "./app";
import reducer from "./reducer";
import { createStore, applyMiddleware } from "redux";
import { Provider } from "react-redux";
import thunk from "redux-thunk";
let store = createStore(reducer, applyMiddleware(thunk));
ReactDOM.render(
<Provider store={store}>
<Case />
</Provider>,
document.getElementById("container")
);
File index.html
<body>
<div id="container"></div>
</body>
Hello i begin in react redux i try to play with an api my problem is at begining in my idea i hope make a select and in the select all the results for a day:
My Component ResultListItems:
import { connect } from 'react-redux';
import { fetchResults } from "../actions/index";
class ResultListItems extends Component {
componentWillMount(){
this.props.fetchResults();
}
render() {
return (
<div>
<h2>Coucou la liste resultats</h2>
<select></select>
</div>
);
}
}
const mapStateToProps = (state) => {
return {
results: state.resultsReducer.results
};
};
export default connect(mapStateToProps, null)(ResultListItems)
My Action in index.js in folder actionsat this moment i have a date in url
import axios from "axios";
export const GET_RESULTS = "GET_RESULTS";
const END_POINT = "http://data.nba.net/10s/20200203";
export function fetchResults() {
return function(dispatch) {
axios.get(`${END_POINT}`)
.then(axiosResponse => {
dispatch({ type: GET_RESULTS, payload: axiosResponse.data});
});
}
}
My reducer => reducer_results :
const initialResults ={
results: []
}
export default function (state = initialResults, action) {
switch (action.type) {
case GET_RESULTS:
return {
results: action.payload
};
}
return state
}
I import in index.js in reducer Folder:
import ReducerResults from "../reducers/reducer_results";
const rootReducer = combineReducers({
resultsReducer: ReducerResults
});
export default rootReducer;
And my container is results.js :
import { connect } from 'react-redux';
import ResultListItems from '../components/results_list_item'
class Results extends Component {
render() {
return (
<div>
<h1>App NBA</h1>
<ResultListItems />
</div>
);
}
}
export default connect()(Results);
You are not mapping your api call of fetchResults to props.Try the following. After mapping state to props, map dispatch as well to props in component ResultListItems.
const mapDispatchToProps = (dispatch, ownProps) => {
return {
fetchResults : () => dispatch(fetchResults()),
dispatch
}
}
then coonect it like this.
export default connect(mapStateToProps, mapDispatchToProps)(ResultListItems)
So, you haven't fetchResults in the list of props, this.props.fetchResults is undefined, because you haven't binded the action to the component's props. To deal with it you need to bind the actionCreator. Use a guide: https://blog.benestudio.co/5-ways-to-connect-redux-actions-3f56af4009c8
Or
just do like that:
componentWillMount(){
const {dispatch} = this.props;
dispatch(fetchResults());
}
I'm new to react-redux and i want to dispatch an action from container to component. Here is my code :
Container :
import { connect } from 'react-redux';
import addCountryComponent from '../../views/Country/AddCountry'
import { addCountry } from '../../actions/countryActions'
const mapDispatchToProps = (dispatch) => {
return {
addCountry:(country) => dispatch(addCountry(country))
}
}
const CountryContainer = connect(null, mapDispatchToProps)(addCountryComponent)
export default CountryContainer;
AddCountry Component :
import React, {Component} from 'react';
class AddCountry extends Component {
constructor(props) {
super(props);
}
componentDidMount() {
console.log(this.props);
}
render() {
return (
<div className="animated fadeIn">
asdas
</div>
);
}
}
export default AddCountry;
and Action file
import { ADD_COUNTRY } from './names'
export function addCountry(payload) {
console.log(payload, "actions")
return {
type: ADD_COUNTRY,
payload,
}
}
I can't see addCountry as a props, am i missing something ?
as you mentioned in the comment you should link your container to your route file.
that means your container gets called when a browser open this /add/country
const mapDispatchToProps = dispatch => {
return {
addCountry: (country) => dispatch({ type: ADD_COUNTRY ,country})
}}
TO used it on click event
you should make add reducer for this app
example
const balance =(state={country:""},action)=>{
switch(action.type){
case ADD_COUNTRY: return{ country: action.country;}
this.props.addCountry(country)
This because of mapDispatchToProps auto-add property addCountry to your addCountryComponent.
const mapDispatchToProps = (dispatch) => {
return {
// component that using this function will auto have addCountry in props of component
addCountry:(country) => dispatch(addCountry(country))
}
}
// this addCountryComponent using mapDispatchToProps so it have addCountry in props
const CountryContainer = connect(null, mapDispatchToProps)(addCountryComponent)
export default CountryContainer;
Did you try using bindActionCreators function of Redux?
import { bindActionCreators } from 'redux';
const mapDispatchToProps = (dispatch) => bindActionCreators({
addCountry: addCountry,
}, dispatch);
I am trying to expose a value in order to share it among components:
I have this reducer:
import createReducer from '../../../redux/createReducer';
import ActionTypes from '../constants/ActionTypes';
const initialState = {
currentService: 'otherservices',
};
export const handlers = {
[ActionTypes.SELECTED_SERVICE_ACTION](state, action) {
return {
...state,
currentService: action.payload,
};
},
};
export default createReducer(initialState, handlers);
And this action:
import ActionTypes from '../constants/ActionTypes';
export const selectedServiceAction = service => ({
type: ActionTypes.SELECTED_SERVICE_ACTION,
payload: service,
});
export default selectedServiceAction;
And I have this component:
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { translate } from 'react-i18next';
import { DropdownV2 } from 'carbon-components-react';
import PropTypes from 'prop-types';
import TableMultiSelectItems from './TableMultiSelectItems';
import { selectedServiceAction } from '../actions/cancellations';
class TableMultiselect extends Component {
constructor(props) {
super(props);
this.state = {
itemState: 'otherservices',
};
}
onChange = e => this.setState({ itemState: e.selectedItem.id });
render() {
const { t } = this.props;
// Array of dropdown's items
const menuItems = TableMultiSelectItems(t);
return (
<div>
<DropdownV2
items={menuItems}
onChange={this.onChange}
/>
</div>
);
}
}
const wrappedComponent = connect(
() => ({
serviceSelected: this.state.itemState,
}),
dispatch => ({
serviceSelectedHandler: serviceSelected => {
dispatch(selectedServiceAction(serviceSelected));
},
}),
)(TableMultiselect);
TableMultiselect.propTypes = {
t: PropTypes.func.isRequired,
serviceSelectedHandler: PropTypes.func.isRequired,
};
export default translate()(wrappedComponent);
What I need from the component above is to take the value returned by the onChange function (itemState: e.selectedItem.id });) and expose it in order to grab it in another component and do something like this:
//other imports
import the-Action-To-Get-The-OnChange-Value from "..."
const Cancellations = () => (
<React.Fragment>
{the-Action-To-Get-The-OnChange-Value.id === 'thisID' ?
<SLRequests/> : <SLDevices/>
}
</React.Fragment>
);
This is the first component I am trying to do with Redux, so I need some help trying to achieve what I need.
If you want something to be accessible globally, you will have to store it inside application state (redux store). In your particular case, you want to store e.selectedItem.id. So besides setting the state (however this is redundant because the variable will be accessible globally).
What you need to do? Inside onChange function you have to dispatch the action and pass the argument.
onChange = (e) => this.props.dispatch(selectedServiceAction(e.selectedItem.id));
Note: To access the dispatch function your component has to be connected.
Then it will be catched by reducer and will be saved in store.
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))
})
}
}