This.props.action is not a function - reactjs

Just started writing custom stuff with Redux and I'd like to pass an input into my action.
(Also using this tutorial: https://www.youtube.com/watch?v=kNkTQtRUH-M)
Basically, I'd like to pass a phone number (in the example: '+1**********') to my action using this.props.testUserPhone('+1**********'), but I get a this.props.testUserPhone is not a function error.
What am I doing wrong here? I feel like there's a specific way to do functions with parameters that I'm missing, or my binding is wrong or something.
phonepage.js
#connect(
state => ({
testUserPhone: state.phonepage.testUserPhone
}),
{ testUserPhone }
)
class PhonePage extends Component {
componentDidMount() {
this.props.testUserPhone('+1**********')
}
render() {
console.log(this.props.testUserPhone('+1**********'))
return(
// Render stuff
)
}
}
actions.js
import { UserAPI } from '../../constants/api'
const userAPI = new UserAPI()
export const TEST_USER_PHONE = 'TEST_USER_PHONE'
export const testUserPhone = (user) => ({
type: TEST_USER_PHONE,
payload: userAPI.testPhone(user)
})
reducer.js
import {
TEST_USER_PHONE
} from './actions'
const INITIAL_STATE = {
testedByPhone: {
data: [],
isFetched: false,
error: {
on: false,
message: null
}
}
}
export default (state = INITIAL_STATE, action) => {
switch(action.type) {
case '${TEST_USER_PHONE}_PENDING':
return INITIAL_STATE
case '${TEST_USER_PHONE}_FULFILLED':
return {
testedByPhone: {
data: action.payload,
isFetched: true,
error: {
on: false,
message: null
}
}
}
case '${TEST_USER_PHONE}_REJECTED':
return {
testedByPhone: {
data: [],
isFetched: true,
error: {
on: true,
message: 'Error when fetching testedByPhone'
}
}
}
default:
return state
}
}
reducers.js
import { combineReducers } from 'redux'
import {
phonereducer
} from '../scenes'
export default combineReducers({
phonepage: phonereducer
})

I wasn't paying attention and missed that you need babel transform legacy decorators to actually use this.
npm install --save-dev babel-plugin-transform-decorators-legacy
and then
{
"presets": ["react-native"],
"plugins": ["transform-decorators-legacy"]
}

Your code has a binding issue: the action named 'testUserPhone' which you have created in action.js is not imported in your component where you are trying to call it.
import {testUserPhone} from 'action.js'//plz correct the path according to your file structure
#connect(
state => ({
testUserPhone: state.phonepage.testUserPhone
}),(dispatch)=>{
return {testUserPhone:(user)=>{
dispatch(testUserPhone(user))
}
}
)
class PhonePage extends Component {
componentDidMount() {
this.props.testUserPhone('+1**********')
}
render() {
console.log(this.props.testUserPhone('+1**********'))
return(
// Render stuff
)
}
}
The above code is just one way to dispatch an action from a container, there are more ways to do the same.
#connect take to parameters mapStateToProps and mapDispatchToProps. Both are function ,in mapStateToProps you specify which part of global state you need in your component and in mapDispatchToProps you specify
which action you want to pass as a prop to your component ,So that you can change the global state by dispatching appropriate actions.

Related

Why can't I call this.state in my redux reducer?

I made a reducer that fetches admins, and I want it to display certain admins when I call it in my reducer but I am getting Undefined.
I am still very new to redux so apologies for my mistakes.
I tried to include all the relevant folders:
App.js
import React, { Component } from 'react';
import { connect } from 'react-redux';
import * as actions from '../store/actions';
class App extends Component {
async componentDidMount() {
fetch(constants.adminUrl + '/admins/data', {
method: 'GET'
}).then((res) => {
return res.json()
}).then(async (res) => {
this.props.setAdminsInColumns(res.admins)
}).catch((error) => {
toast.error(error.message)
})
}
render() {
return (
{/* SOME CODE */}
);
}
}
let app = connect(null, actions)(App);
export default app;
columnsReducer.js
import { FETCH_ADMINS } from '../actions/types'
import { Link } from 'react-router-dom'
import constants from '../../static/global/index'
import React from 'react';
import { toast } from 'react-toastify'
const initialState = {
admins: [],
{
Header: "Responsible",
accessor: "responsibleAdmin",
style: { textAlign: "center" },
// Place where I want to fetch certain admins and get undefined
Cell: props => <span>{props.value && this.state.admins.name ? this.state.admins.find(admin => admin.id === props.value).name : props.value}</span>
}
}
export default function (state = initialState, action) {
switch (action.type) {
case FETCH_ADMINS:
return { ...state, admins: action.admins}
default:
return state
}
}
index.js
import { FETCH_ADMINS } from "./types"
/**
* starts loader for setting admin
*/
export const setAdminsInColumns = (admins) => async dispatch => {
dispatch({ type: FETCH_ADMINS, admins })
}
types.js
export const FETCH_ADMINS = 'fetch_admins'
When I console.log(action.admins) inside the switch case FETCH_ADMINS in the columnsReducer.js file, I can see all the admin information I want, is there a way to make the state global in the columnsReducer.js file so I can read it?
Any help is appreciated!
use mapStateToProps in the connect method. like below
let mapStateToProps = (state)=>{
return {
admins :[yourcolumnsReducer].admins
}
}
let app = connect(mapStateToProps, actions)(App);
//you can use this.props.admins inside your component
MapStateToProps reference

Can't get state from redux's store

I'm developing React/Redux application and I've got problem with getting one particular state from redux store after dispatching an action. I don't have any idea why is that happening, because I haven't experienced such issue with other states. Here is my code:
Reducer
import {SET_CURRENT_USER, SET_LECTURES} from '../actions/actionTypes';
import isEmpty from 'lodash/isEmpty';
const initialState = {
isAuthenticated: false,
user: {},
lectures: []
}
export default (state = initialState, action = {}) => {
switch(action.type) {
case SET_CURRENT_USER:
return {
isAuthenticated: !isEmpty(action.user),
user: action.user
};
case SET_LECTURES:
return {
lectures: action.lectures
}
default: return state;
}
}
Action creator and dispatching action
import { SET_LECTURES } from './actionTypes';
export const setLectures = (lectures) => {
return {
type: SET_LECTURES,
lectures
}
}
export const lecture = (lectures) => {
return dispatch => {
console.log(lectures);
dispatch(setLectures(lectures));
}
}
The problem is with SET_LECTURES action type, in particular lectures property of action object. In the component from which I want to get state lectures, I do mapStateToProps as follows:
const mapStateToProps = function(state) {
return {
notifications: state.notifications,
lectures: state.lectures
}
}
/*
*Code of the class
*/
export default connect(mapStateToProps, null)(AddQuestionForm);
I've skipped code which triggers dispatching action type SET_LECTURES, because it's working fine. I've also used React Developer Tools for tracking states, and there is lectures state. I just can't get this state from my component, when I do console.log(this.props.lectures) from ComponentDidMount(), it shows undefined. Could you explain what am I doing wrong here? I would appreciate any help.
You forgot about dispatch:
export const lectureAction = lectures => dispatch => {
return dispatch => {
dispatch(setLectures(lectures));
}
}
In Component:
import { bindActionCreators } from 'redux';
const mapStateToProps = function(state) {
return {
notifications: state.notifications
}
}
// use map dispatch for actions:
const mapDispatchToProps = dispatch =>
bindActionCreators({
lectures: lectureAction
}, dispatch);
/*
*Code of the class
*/
// connect map dispatch here:
export default connect(mapStateToProps, mapDispatchToProps)(AddQuestionForm);
Now you have an access to this.props.lectures(someParams) function in your Component which dispatch an action.

How to pass internal state to global state using Redux

I'm using Redux in an application for the first time and having trouble understanding how to pass a component's internal state to the global state object.
export default class ComponentOne extends Component {
constructor() {
this.state = {
number: 0
}
handleNumber = (e) => {
this.setState({
number: e.target.value
})
}
render() {
console.log(this.state.number)
return (
<div>
<input onChange={this.handleNumber} type="number">
</div>
)
}
}
}
function mapStateToProps(state) {
return {
number: state
}
}
export default connect(mapStateToProps, { HANDLE_NUMBER_CHANGE })(ComponentOne);
My Actions & Reducers:
const HANDLE_NUMBER_CHANGE = state => {
return {
type: 'HANDLE_NUMBER_CHANGE'
}
}
export default (state = 0, action) {
switch(action.type) {
case 'HANDLE_NUMBER_CHANGE':
//Im lost here - trying to save internal state
default:
return state;
}
}
My store is set up properly, using redux-thunk for middleware.
When I log store.getState() - it is logging 0 regardless of my components internal state.
Can anybody explain how this works?
When you have global state you dont need to save it to the local state. It is accessible to the component as this.props.value.
The way to set global state is by passing the value to the action creator, which returns it in the action. The reducer gets it in the action object and saves it.
There are many simple examples available. Here is one.
Here is your code after changes:
(I didn't run it - there might be errors, but I believe that you will be able to fix them by yourself; I have divided the code between several files - this is how usually how this is done. Look in the example in the above link if you have problems)
// file: src/components/ComponentOne.js
import React from 'react';
import { connect } from 'react-redux';
import { handleNumber } from '../actions';
class ComponentOne extends Component {
constructor(props) {
super(props);
this.handleNumber = this.handleNumber.bind(this);
}
render() {
console.log(this.state.number)
return (
<div>
<input onChange={(e) => this.props.handleNumber(e.target.value)} type="number" />
</div>
);
}
}
function mapStateToProps(state) {
return {
number: state
}
}
export default connect(mapStateToProps, { handleNumber })(ComponentOne);
// end of file
/// separate file: src/reducers/index.js ////
import { combineReducers } from 'redux';
import dataReducer from './dataReducer';
export default combineReducers({
number: dataReducer
});
// end of file
// separate file: src/reducers/dataReducer.js
const DataReducer = (state = 0, action) => {
switch(action.type) {
case 'HANDLE_NUMBER_CHANGE':
return action.payload;
default:
return state;
}
};
export default DataReducer;
// end of file
// separate file: src/actions/index.js
export function handleNumber(value) {
return ({
type: 'HANDLE_NUMBER_CHANGE',
payload: value
});
}
I don't see the logic in making your internal state equal your store. I'm not saying you're wrong, but it doesn't seem to fit within the redux paradigm. However...
Action should be...
export function HANDLE_NUMBER_CHANGE = number => {
return {
type: 'HANDLE_NUMBER_CHANGE'
payload: number
}
}
Reducer should look like...
export default (state = {number: 0}, action) {
switch(action.type) {
case 'HANDLE_NUMBER_CHANGE':
return (state = {
...state,
number: action.payload,
});
Lastly, you'll need to call a dispatch from your onChange function.
dispatch(HANDLE_NUMBER_CHANGE(e.target.value).
If you do not pass the value to the action, there is no way for the reducer to add it to the store.
If you are managing your ComponentOne state using redux then you dont need
react state.
ComponentOne
export default class ComponentOne extends Component {
constructor() {
handleNumber = (e) => {
this.props.updateNumber(e.target.value);//call dispatch method
}
render() {
return (
<div>
<input onChange={this.handleNumber} type="number">
</div>
)
}
}
}
function mapStateToProps(state) {
return {
number: state.number //map updated number here
}
}
function mapDispatchToProps(state) {
return {
updateNumber(number){
dispatch({type: 'HANDLE_NUMBER_CHANGE',number});//dispatch action
}
}
}
export default connect(mapStateToProps, mapDispatchToProps)(ComponentOne);
reducers:
export default (state = 0, action) {
switch(action.type) {
case 'HANDLE_NUMBER_CHANGE':
return {
...state,number : actio.number//update number here
}
default:
return state;
}
}

React + Redux subscribe to action outside of mapStateToProps

I have the mapStateToProps workflow down, but what if I want to respond to actions in a way that doesn't fit well into the state => props paradigm? For instance:
this.props.dispatch(saveArticle(...))
// on successful save, redirect to article page
If I'm just using regular old XHRs rather than actions, it would look something like this:
saveArticle(...).then(article => this.router.push(`/articles/${article.id}`))
It's not clear how this would fit in with the standard React/Redux workflow; I've seen people suggest that the saveArticle() action creator could fire off the router change, but I want to keep those separate; I should be able to save an article without being forced to redirect.
A workaround could be to do it in mapStateToProps; have the action set a flag or something, like articleWasSaved, and have the component that does the saving look for that prop and redirect if it sees it, but that seems really ugly, especially if multiple things are looking for that update, since it would likely require the component(s) to clear the flag.
Is there a simple/standard solution I'm missing?
Redux-thunk allows you to dispatch functions as actions. It is ideally to dispatch async operations.
Here I've created an example I think It will be useful for you:
actions.js
export const tryingAsyncAction = () => {
return {
type: 'TRYING_ASYNC_ACTION'
}
}
export const actionCompleted = () => {
return {
type: 'ACTION_COMPLETED'
}
}
export const errorAsyncAction = (error) => {
return {
type: 'ERROR_ASYNC_ACTION',
error
}
}
export function someAsynAction() {
return dispatch => {
dispatch(tryingAsyncAction())
ApiService.callToAsyncApi(...)
.then(() => {
dispatch(actionCompleted())
}, (cause) => {
dispatch(errorAsyncAction(cause))
})
}
}
reducer.js
const initialState = {
tryingAction: false,
actionCompleted: false,
error: null,
shouldRedirect: false,
redirectUrl: null
}
export default function reducer(state = initialState, action) {
switch (action.type) {
case 'TRYING_ASYNC_ACTION':
return Object.assign({}, state, {
tryingAction: true
})
case 'ACTION_COMPLETED':
return Object.assign({}, state, {
tryingAction: false,
actionCompleted: true,
shouldRedirect: true,
redirectUrl: 'someUrl'
})
case 'ERROR_ASYNC_ACTION':
return Object.assign({}, state, {
tryingAction: false,
actionCompleted: false,
error: action.error
})
default:
return state
}
}
Your createStore file
import { createStore, applyMiddleware } from 'redux'
import thunkMiddleware from 'redux-thunk' //npm install --save redux-thunk
//Other imports...
const store = createStore(
reducer,
applyMiddleware(
thunkMiddleware
)
)
YourComponent.js
componentWillReceiveProps(nextProps){
if(nextProps.shouldRedirect && nextProps.redirectUrl)
this.router.push(`/articles/${article.id}`)
}
Let me know if there is something you dont understand. I will try to clarify
You could make use of react-thunk in this case.
actions/index.js
export function saveArticle(data) {
return (dispatch, getState) => (
api.post(data).then(response => {
dispatch({ type: 'SAVE_ARTICLE', payload: response })
return response;
})
)
}
reducer/index.js
import { combineReducers } from 'redux';
const initialState = {
list: [],
current: null,
shouldRedirect: false,
redirectTo: null
};
export function articles(state = initialState, action) {
switch(action.type) {
case 'SAVE_ARTICLE':
return {
shouldRedirect: true,
redirectTo: '/some/url',
current: action.payload,
list: [...state.list, action.payload]
};
default: return state;
}
}
export default combineReducers({ articles });
store/index.js
import { createStore, applyMiddleware } from 'redux';
import thunk from 'redux-thunk';
import rootReducer from './reducers/index';
// Note: this API requires redux#>=3.1.0
const store = createStore(
rootReducer,
applyMiddleware(thunk)
);
component/index.js
import React, { Component } from 'react';
import { connect } from 'react-redux';
import * as Actions from 'actions/index';
class MyComponent extends Component {
_handleSubmit = () => {
// get form values somehow...
// const values = getFormValues();
this.props.saveArticle(values).then(response => {
// you can handle you redirect here as well,
// since saveArticle is returning a promise
});
};
componentWillReceiveProps(nextProps) {
// you can handle the redirection here listening to changes
// on shouldRedirect and redirectTo that will be triggered
// when the action 'SAVE_ARTICLE' is dispatched
if(nextProps.shouldRedirect && nextProps.redirectTo) {
this.routes.push(nextProps.redirectTo);
}
}
render() {
// just an example
return (
<form onSubmit={this._handleSubmit}>
{ /* ... other elements here */ }
</form>
)
}
}
export default connect(
state => ({
articles: state.articles.list,
article: state.articles.current,
redirectTo: state.articles.redirectTo,
shouldRedirect: state.articles.shouldRedirect
}),
Actions
)(MyComponent);
PS: I'm using some babel syntax sugar here, so make sure you're the following presets are set in your .babelrc.
es2015
stage-2
stage-0
react

Access query parameters in async call (react redux)

My react redux (4.4.5) project uses react-router-redux(4.0.5) and redux-async-connect (0.1.13). Before I load my container component, I want to asynchronously load data from my API. The url contains a query parameter named "category" which is used to fetch the messages. ie. user/cornel/messages?category=react-redux
The parameters linked to my location/path are in state.routing.locationBeforeTransitions, but these are not up to date when in the async call. I can get the path parameters from the params parameter that is passed to the async function, but this does not contain the query parameters.
#statics({
reduxAsyncConnect(params, store) {
const { dispatch } = store;
return Promise.all([
dispatch(loadMessages(category)) <-- need the query parameter "category" here
]);
}
})
#connect(state => ({
messages: state.user.messages
}))
export default class HomeContainer extends Component {
static propTypes = {
dispatch: PropTypes.func
messages: PropTypes.array.isRequired
};
render() {
const { messages } = this.props;
return (
...
}
}
}
Anyone has any idea how I should access the query parameter so it works both client and server side?
Thanks in advance!
You should be able to get search from redux state as the following if you are using react-redux-router.
#statics({
reduxAsyncConnect(params, store) {
const { dispatch } = store;
return Promise.all([
dispatch(loadMessages(category)) <-- need the query parameter "category" here
/* you might get
store.getState().
routing.locationBeforeTransitions.search
from here too */
]);
}
})
#connect(state => ({
messages: state.user.messages,
/* get search from redux state */
search : state.routing.locationBeforeTransitions.search
}))
export default class HomeContainer extends Component {
static propTypes = {
dispatch: PropTypes.func
messages: PropTypes.array.isRequired
};
render() {
const { messages } = this.props;
return (
...
}
}
}
Let me know if it is not available for you.
EDIT
Here is a piece of code that doesn't use reduxAsyncConnect and accomplishing what you want to do.
// CONSTANTS
const
GET_SOMETHING_FROM_SERVER = 'GET_SOMETHING_FROM_SERVER',
GET_SOMETHING_FROM_SERVER_SUCCESS = 'GET_SOMETHING_FROM_SERVER_SUCCESS',
GET_SOMETHING_FROM_SERVER_FAIL = 'GET_SOMETHING_FROM_SERVER_FAIL';
// REDUCER
const initialState = {
something : [],
loadingGetSomething: false,
loadedGetSomething:false,
loadGetSomethingError:false
};
export default function reducer(state = initialState, action) {
switch(action.type) {
case GET_SOMETHING_FROM_SERVER:
return Object.assign({}, state, {
loadingGetSomething: true,
loadedGetSomething:false,
loadGetSomethingError:false
something : [] // optional if you want to get rid of old data
});
case GET_SOMETHING_FROM_SERVER_SUCCESS:
return Object.assign({}, state, {
loadingGetSomething: false,
loadedGetSomething:true,
something : action.data
});
case GET_SOMETHING_FROM_SERVER_FAIL:
return Object.assign({}, state, {
loadingGetSomething: false,
loadGetSomethingError: action.data
});
default:
return state;
}
};
// ACTIONS
/* ----------------- GET SOMETHING ACTIONS START ----------------- */
import Fetcher from 'isomorphic-fetch'; // superagent , axios libs are okay also
export function getSomething() {
return {
type : GET_SOMETHING_FROM_SERVER
}
};
export function getSomethingSuccess(data) {
return {
type : GET_SOMETHING_FROM_SERVER_SUCCESS,
data
}
};
export function getSomethingFail(data) {
return {
type : GET_SOMETHING_FROM_SERVER_FAIL,
data
}
};
export function getSomethingAsync(paramsToBeSentFromComponents){
return function(dispatch) {
const fetcher = new Fetcher();
dispatch(getSomething()); // so we can show a loading gif
fetcher
.fetch('/api/views', {
method : 'POST',
data : {
// use paramsToBeSentFromClient
}
})
.then((response) => {
dispatch( getSomethingSuccess(response.data));
})
.catch((error) => {
return dispatch(getSomethingFail({
error : error
}))
});
}
}
/* ----------------- GET SOMETHING ACTIONS END ----------------- */
// COMPONENT
import React, {Component} from 'react';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import * as somethignActions from './redux/something';
#connect((state) => ({
pathname : state.routing.locationBeforeTransitions.pathname,
something : state.something
}))
export default class SettingsMain extends Component{
constructor(props){
super(props);
this.somethingActions = bindActionCreators(somethingActions, this.props.dispatch);
}
componentDidMount(){
// you are free to call your async function whenever
this.settingActions.getSomething({ this.props.pathname...... })
}
render(){
return ( /* your components */ )
}
}

Resources