I can't dispatch my actions as props (React & Redux) - javascript

Here is how I use connect:
function mapStateToProps(state, ownProps) {
return {
// we'll call this in our component -> this.props.listingData
listingData: state.locations.listingData,
//we'll call this in out component -> this.props.formState
formState: state.formStates.formState
};
}
function mapDispatchToProps(dispatch) {
return {
actions: bindActionCreators(locationActions, formStateActions, dispatch)
};
}
export default connect(mapStateToProps, mapDispatchToProps)(homePage);
Here is the button which I used:
<div onClick={this.stateToEntry} className="addButton">Add</div>
Here is the function to run:
stateToEntry() {
this.props.actions.stateToEntry();//formStateActions.stateToEntry();//dispatch an action to update the Redux store state
browserHistory.push('/location');//then redirect to the add/edit/delete page using browserHistory
}
I'm getting the error that this.props.actions.stateToEntry() is not a function. What's happening here actually & how do I solve this problem?
EDIT:
Here is the log data:
In other words its just adding the {} around it. I've tried using {formStateActions} alone and it didn't work but formStateActions worked.
For #LazarevAlexandr's, here is my actioncreator for formStateActions:
export function stateToEntry() {
return { type: types.STATE_TO_ENTRY, formState: 'entry-mode'};
}
export function stateToEdit() {
return { type: types.STATE_TO_EDIT, formState: 'edit-mode'};
}
export function stateToDelete() {
return { type: types.STATE_TO_DELETE, formState: 'delete-mode'};
}
My locationActions actioncreator is quite long so I'd rather not post it fully here. They are all functions, some are actioncreators which return actions and some returns a function for fetching data lists from an api.

bindActionsCreators gets only two parameters, so if want to pass multiple actions sets try this:
function mapDispatchToProps(dispatch) {
const actions = Object.assign({}, locationActions, formStateActions);
return {
actions: bindActionCreators(actions, dispatch)
};
}

Related

Chain connect/mapStateToProps/mapDispatchToProps functions for code reuse in react-redux

Say I have two redux connected components. The first is a simple todo loading/display container, with the following functions passed to connect(); mapStateToProps reads the todos from the redux state, and mapDispatchToProps is used to request the state to be provided the latest list of todos from the server:
TodoWidgetContainer.js
import TodoWidgetDisplayComponent from '...'
function mapStateToProps(state) {
return {
todos: todoSelectors.getTodos(state)
};
}
function mapDispatchToProps(dispatch) {
return {
refreshTodos: () => dispatch(todoActions.refreshTodos())
};
}
connect(mapStateToProps, mapDispatchTo)(TodoWidgetDisplayComponent);
The second redux component is intended to be applied to any component on a page so that component can indicate whether a global "loading" icon is displayed. Since this can be used anywhere, I created a helper function that wraps MapDispatchToProps in a closure and generates an ID for each component, which is used to make sure all components that requested the loader indicate that they don't need it anymore, and the global loader can be hidden.
The functions are basically as follows, with mapStateToProps exposing the loader visibility to the components, and mapDispatchToProps allowing them to request the loader to show or hide.
Loadify.js
function mapStateToProps(state) {
return {
openLoader: loaderSelectors.getLoaderState(state)
};
}
function mapDispatchToProps() {
const uniqId = v4();
return function(dispatch) {
return {
showLoader: () => {
dispatch(loaderActions.showLoader(uniqId));
},
hideLoader: () => {
dispatch(loaderActions.hideLoader(uniqId));
}
};
};
}
export default function Loadify(component) {
return connect(mapStateToProps, mapDispatchToProps())(component);
}
So now, if I have a component that I want to give access to the loader, I can just do something like this:
import Loadify from '...'
class DisplayComponent = new React.Component { ... }
export default Loadify(DisplayComponent);
And it should give it a unique ID, allow it to request the loader to show/hide, and as long as there is one component that is requesting it to show, the loader icon will show. So far, this all appears to be working fine.
My question is, if I would like to apply this to the todos component, so that that component can request/receive its todos while also being allowed to request the loader to show while it is processing, could I just do something like:
TodoWidgetContainer.js
import Loadify from '...'
import TodoWidgetDisplayComponent from '...'
function mapStateToProps(state) {
return {
todos: todoSelectors.getTodos(state)
};
}
function mapDispatchToProps(dispatch) {
return {
refreshTodos: () => dispatch(todoActions.refreshTodos())
};
}
const TodoContainer = connect(mapStateToProps, mapDispatchTo)(TodoWidgetDisplayComponent);
export default Loadify(TodoContainer);
And will redux automatically merge the objects together to make them compatible, assuming there are no duplicate keys? Or will it take only the most recent set of mapStateToProps/mapDispatchTo unless I do some sort of manual merging? Or is there a better way to get this kind of re-usability that I'm not seeing? I'd really rather avoid having to create a custom set of containers for every component we need.
connect will automatically merge together the combination of "props passed to the wrapper component", "props from this component's mapState", and "props from this component's mapDispatch". The default implementation of that logic is simply:
export function defaultMergeProps(stateProps, dispatchProps, ownProps) {
return { ...ownProps, ...stateProps, ...dispatchProps }
}
So, if you stack multiple levels of connect around each other , the wrapped component will receive all of those props as long as they don't have the same name. If any of those props do have the same name, then only one of them would show up, based on this logic.
Alright, here is what I would do. Create a higher order component (HOC) that adds a new spinner reference to your reducer. The HOC will initialize and destroy references to the spinner in redux by tying into the life cycle methods. The HOC will provide two properties to the base component. The first is isLoading which is a function that takes a boolean parameter; true is on, false is off. The second property is spinnerState that is a readonly boolean of the current state of the spinner.
I created this example without the action creators or reducers, let me know if you need an example of them.
loadify.jsx
/*---------- Vendor Imports ----------*/
import React from 'react';
import { connect } from 'react-redux';
import v4 from 'uuid/v4';
/*---------- Action Creators ----------*/
import {
initNewSpinner,
unloadSpinner,
toggleSpinnerState,
} from '#/wherever/your/actions/are'
const loadify = (Component) => {
class Loadify extends React.Component {
constructor(props) {
super(props);
this.uniqueId = v4();
props.initNewSpinner(this.uniqueId);;
this.isLoading = this.isLoading.bind(this);
}
componentWillMount() {
this.props.unloadSpinner(this.uniqueId);
}
// true is loading, false is not loading
isLoading(isOnBoolean) {
this.props.toggleSpinner(this.uniqueId, isOnBoolean);
}
render() {
// spinners is an object with the uuid as it's key
// the value to the key is weather or not the spinner is on.
const { spinners } = this.props;
const spinnerState = spinners[this.uniqueId];
return (
<Component isLoading={this.isLoading} spinnerState={spinnerState} />
);
}
}
const mapStateTopProps = state => ({
spinners: state.ui.spinners,
});
const mapDispatchToProps = dispatch => ({
initNewSpinner: uuid => dispatch(initNewSpinner(uuid)),
unloadSpinner: uuid => dispatch(unloadSpinner(uuid)),
toggleSpinner: (uuid, isOn) => dispatch(toggleSpinnerState(uuid, isOn))
})
return connect(mapStateTopProps, mapDispatchToProps)(Loadify);
};
export default loadify;
Use Case Example
import loadify from '#/location/loadify';
import Spinner from '#/location/SpinnerComponent';
class Todo extends Component {
componentWillMount() {
this.props.isLoading(true);
asyncCall.then(response => {
// process response
this.props.isLoading(false);
})
}
render() {
const { spinnerState } = this.props;
return (
<div>
<h1>Spinner Testing Component</h1>
{ spinnerState && <Spinner /> }
</div>
);
}
}
// Use whatever state you need
const mapStateToProps = state => ({
whatever: state.whatever.youneed,
});
// use whatever dispatch you need
const mapDispatchToProps = dispatch => ({
doAthing: () => dispatch(doAthing()),
});
// Export enhanced Todo Component
export default loadify(connect(mapStateToProps, mapDispatchToProps)(Todo));

React dispatching action does nothing

I have a problem when trying to fetch initial data for my app from an api.
My problem is that after the console.log(url) in the action, nothing happens. I see the url in the console, but the rest of the code in getInitialRuns() doesn't seem to be executed, atleast not the way I expect. I get no error messages.
When using Postman, I can succesfully get a response from that API endpoint, so the API should be fine.
I have actions that looks like this:
function requestRuns(){
console.log('request')
return {
type: 'getInitialRuns'
}
}
export function getInitialRuns(){
var url = 'http://localhost:32118/api/Runs';
console.log(url);
return dispatch => {
dispatch(requestRuns())
return fetch(url)
.then(response => response.json().then(body => ({response, body})))
.then(({response, body}) => {
if(!response.ok){
console.log('fail')
}
else{
console.log('success')
}
})
}
The component that calls the action looks like this:
class RunList extends Component{
constructor(props) {
super(props)
}
componentWillMount(){
getInitialRuns()
}
render() {
const {runs, isFetching, error} = this.props
return (
<ul className="run-list">
{runs.map((run) =>
<Run key={run.Id} id={run.Id} date={run.DateString} day={run.Day} distance={run.Distance} duration={run.DurationString} avgpace={run.AvgPaceString} calories={run.Calories}/>
)}
</ul>
)
}
}
RunList.propTypes = {
isFetching: PropTypes.bool.isRequired,
runs: PropTypes.instanceOf(Immutable.List),
error: PropTypes.object
}
function mapStateToProps(state) {
return{
runs: state.runs,
isFetching: state.isFetching,
error: state.error
}
}
export default connect(mapStateToProps)(RunList)
My store is set up like this:
import { createStore, applyMiddleware } from 'redux';
import {composeWithDevTools} from 'redux-devtools-extension';
import runs from './reducers/runs';
import thunk from 'redux-thunk';
export default createStore(runs,composeWithDevTools( applyMiddleware(thunk) ))
And these are my reducers
import Immutable from 'immutable'
let initialState = {
runs: Immutable.List([]),
isFetching: false,
error: undefined
};
export default (state = initialState, action) => {
switch(action.type) {
case 'addRun':
return state.unshift(action.run)
case 'deleteRun':
return Object.assign({}, state, {
runs: state.runs.filter((run) => run.Id !== action.id)
})
case 'getInitialRuns':
console.log('initial')
return Object.assign({}, state, {
isFetching: true
})
case 'getInitialRunsSuccess':
console.log('success')
return Object.assign({}, state, {
isFetching: false,
runs: action.runs
})
case 'getInitialRunsFailure':
return Object.assign({}, state, {
isFetching: false,
error: action.error
})
default:
return state
}
}
In order to dispatch an action on redux, you should provide a mapDispatchToProps function to connect. From redux docs:
(..) You can define a function called mapDispatchToProps() that receives the dispatch() method and returns callback props that you want to inject into the presentational component
const mapDispatchToProps = (dispatch) => {
return {
onTodoClick: (id) => {
dispatch(toggleTodo(id))
}
}
}
I see you are using some advanced libraries like Immutablejs. I suggest
you start with reading the awesome redux documentation as it will take you step by step. And until you're familiar with basic redux concepts, avoid any other library.
Here are some notes i hope are useful for you: (they are taken from redux docs)
Notes
An action object which is a payload of information that send data from your application to your store. It is of this form:
var ADD_TODO = {
type: ADD_TODO,
text: 'Build my first Redux app'
}
It's recommended to name you actions types in Upper case, like ADD_TODO.
dispatch accepts an action object (see the example above).
It is recommended to use action creators which are functions that return an action object. It makes them easily testable and portable
Action creators are usually named in lower case: addTodo().
I hope this helps a bit
You should be returning a new state if you want Redux to notice any state change.
Your reducer cases should look something like that:
return {
...previousState,
newValues
}
'addRun'
return state.unshift(action.run)
should be
return {...state, runs: state.run.unshift(action.run) }
The function getInitialRuns returns a function, so calling it doesn't execute anything of the returned function. Btw I'm not sure executing it would be of any use for your app.
Reducers are synchronous, so if you need to do anything asynchronous you would need a middleware, like redux-thunk or redux-observable.
Flow should be:
State -> Views -> Actions -> Middleware -> Actions -> Reducers -> State and back to Views
Please look at the docs at http://redux.js.org/docs/advanced/AsyncActions.html
Also you can enjoy the excellent free courses on egghead:
https://egghead.io/courses/getting-started-with-redux
https://egghead.io/courses/building-react-applications-with-idiomatic-redux
On github you'll find a lot of material on the courses too.

Why is my Reducer not receiving my Actions? (Using React & Redux)

These are my current action creators, they run fine & return actions as usual. I've tested with some logging, its working here:
export function stateToEntry() {
return { type: types.STATE_TO_ENTRY, formState: 'entry-mode'};
}
export function stateToEdit() {
return { type: types.STATE_TO_EDIT, formState: 'edit-mode'};
}
export function stateToDelete() {
return { type: types.STATE_TO_DELETE, formState: 'delete-mode'};
}
This is my current reducer which doesn't receive my actions. I've tested here, it seems that I can't even log into the console:
import * as types from '../actions/actionTypes';
export default function formStateReducer(state = [], action) {
switch(action.type) {
case types.STATE_TO_ENTRY:
console.log('entry-mode');
return {formState: action.formState};
case types.STATE_TO_EDIT:
//console.log('edit-mode');
return {formState: action.formState};
case types.STATE_TO_DELETE:
//console.log('delete-mode');
return {formState: action.formState};
default:
return state;
}
}
Here is my combined reducer. The locations reducer works fine but I'm getting a null on my formState so it's linked correctly inside the store. :
const rootReducer = combineReducers({
locations,
formStates
});
export default rootReducer;
What could I have probably missed?
From the docs: bindActionCreators ... Turns an object whose values are action creators, into an object with the same keys, but with every action creator wrapped into a dispatch call so they may be invoked directly.
So, you're using it wrong, try this:
let foo = {
location: bindActionCreators(locationActions, dispatch),
form: bindActionCreators(formActions, dispatch)
}
// later in your code -- will dispatch that action automatically
foo.location.someActionFromLocationActionsObject()

Accessing "getState" from within action creator using redux-thunk?

I have a mapDispatchToProps function like this one below.
export function mapDispatchToProps(dispatch, ownProps) {
return {
handleChangeLang: changeLocaleDispatcher(dispatch),
handleChange: handleChangeDispatcher(dispatch),
handleSubmit: handleSubmitDispatcher(dispatch)
}
}
I just added redux-thunk to my project so I can access state from my action creators (think that's the correct term).
It doesn't seem there's any documentation in redux-thunk outlining how to access getState from mapDispatchToProps. The docs dispatch using the store.dispatch method.
Your initial syntax is wrong, and the "hacky" example is also not how you should go about it.
A sample would look like:
import {thunkAction1, thunkAction2} from "myActions";
import {bindActionCreators} from "redux";
const mapDispatchToProps(dispatch) => {
return {
manuallyBoundAction : (...args) => dispatch(thunkAction1(...args)),
autoBoundAction : bindActionCreators(thunkAction2, dispatch),
multipleActionsTogether : bindActionCreators({thunkAction1, thunkAction2}, dispatch)
}
};
And no, getState itself is not accessible inside mapDispatchToProps. It's available inside the thunk, but not mapDispatch.
You may want to read this answer on why thunks exist, as well as the Redux docs on async actions.
So while I don't see your current action creator in your question, I will assume it's ES6/ES2015 javascript.
The below should give you freedom to grab any state. Since you have redux-thunk, you should be good to go.
export function setActivity(activityName) {
return function (dispatch, getState) {
dispatch({
type: SET_ACTIVITY,
description: 'Set the name of the activity being done now',
currentActivityName: activityName,
currentActivityData: getState().activityStore.activities[activityName]
});
};
}
activityStore is same as what you've defined for that particular reducer, see below.
export const reducers = {
activityStore: ActivityStore.reducer,
someOtherStore: SomeOtherStore.reducer
};
Hacky fix :/
let ProvidedApp = (serverProps) => {
let store = getStore(serverProps)
let ConnectedApp = connect(
mapStateToProps,
mapDispatchToProps(store)
)(App)
return (
<Provider store={store}>
<ConnectedApp/>
</Provider>
)
}
export function mapDispatchToProps(store) {
return (dispatch, ownProps) => {
return {
handleChangeLang: store.dispatch(changeLocaleDispatcher),
handleChange: store.dispatch(handleChangeDispatcher),
handleSubmit: store.dispatch(handleSubmitDispatcher)
}
}
}

React : Action creator not calling reducer

I am making an async call in my action creator and calling my reducer with the result, but for some reason i could not fathom the reducer is not being called.
actions.js (action and action creator)
export const GET_FORMS = 'GET_FORMS'
export function getForms() {
$.get("server url", function(result) {
return {
type: GET_FORMS,
formlist: result.data.forms
}
})
}
reducers.js
import { GET_FORMS } from '../actions/actions'
export default function formAction(state = {forms:[]}, action) {
console.log("received action"+action.type)
switch (action.type) {
case GET_FORMS:
console.log("Action:"+action);
return Object.assign({},state,{
forms:action.formlist
})
default:
return state
}
}
App.js
import React, { Component, PropTypes } from 'react'
import ReactDOM from 'react-dom';
import { bindActionCreators } from 'redux'
import { connect } from 'react-redux'
import auth from '../auth'
import FormTable from '../components/FormTable'
import * as FormActions from '../actions/actions'
class App extends Component {
constructor(props) {
super(props);
this.state = {forms: []};
}
componentDidMount() {
// for some reason if i write this.props.actions.getForms () i get an error
// Uncaught Error: Actions must be plain objects. Use custom middleware for
// async actions.
FormActions.getForms();
}
render() {
const {forms,actions} = this.props
console.log(forms);
return (
<FormTable results={forms} actions = {actions} />
)
}
}
App.PropTypes = {
forms: PropTypes.array.isRequired
}
function mapStateToProps() {
const {
forms:forms
} = {forms:[]}
return {
forms
}
}
function mapDispatchToProps(dispatch) {
return {
actions: bindActionCreators(FormActions, dispatch)
}
}
export default connect(
mapStateToProps,
mapDispatchToProps
)(App)
There are a few things here.
Your action creator doesn't return the action that it's creating. $.get does not return an action, so mapping it to dispatch isn't productive.
Reasonably, this may lead confused about how to return an action from an async action creator. Since it's async, by nature it can't return the final result (unless you're using es7 async await). The pattern you're attempting is identical to this:
class App extends Component {
...
componentDidMount() {
// Instead of calling getForms, do the same directly
$.get(url, function(res) {
return { type: GET_FORMS, forms: res };
});
}
render() { ... }
}
From the $.get api we know that $.get returns a jqXHR object
, not any kind of action. So you have to use the result in the callback its self--you can't just return it. Here's an example of what componentDidMount could then look like (note the callback is bound to this):
componentDidMount() {
$.get(url, function(res) {
this.props.dispatch({ type: GET_FORMS, forms: res });
}.bind(this))
}
Doing async in your component or action creator is an anti-pattern, or at least discouraged. In this case, you're defining the async inside an action creator, which is great. But you're executing the async operation as well, which is bad. Instead you should employ some kind of middleware to handle async operations. Middleware are just functions that take actions and do stuff with them after they are dispatched, and the most common one for this purpose is redux-thunk. Using redux-thunk, you simply return a function that accepts dispatch as an argument. In this case, your action creator would look like this:
export function getForms() {
return function(dispatch) {
$.get(url, function(result) {
dispatch({ type: GET_FORMS, forms: result });
})
}
}
This is very similar to what you're already doing, except that you're creating a thunk--which is just a function that defines another function for execution later--instead of actually doing the async operation now, you're defining it for execution later.
You're not actually dispatching anything.
The simplest problem to fix, but definitely a barrier :) In componentDidMount, you're calling the action creator that you've imported, but you're not calling dispatch. You're 50% of the way there though, in that you passed the action creators to connect, but even though you did that, you didn't call the bound action creators that connect passes into prop--you called the unbound ones.
So instead of calling FormActions.getForms(), you need to call this.props.actions.formActions(). The former is not bound, but the latter is. Calling the latter is the same as doing this.props.dispatch(FormActions.getForms()).
And finally: you don't need to define mapDispatchToProps in this case. You can pass objects to that parameter of connect. See below:
// Instead of this
export default connect(mapStateToProps, mapDispatchToProps)(App);
// do this:
export default connect(mapStateToProps, FormActions)(App);
// creates the bound action creator 'this.props.getForms()'
This one is mostly a style choice, but I think this is a good pattern :) For multiple action creator source objects, you can do this:
export default connect(mapStateToProps, { ...FormActions, ...UserActions })(App);
You should use the redux-thunk middle ware:
https://github.com/gaearon/redux-thunk
essentially as your code stands - you aren't returning an FSA compliant action (and actually aren't returning anything right now). You need to thunk the dispatch and then return the result of the promise.
More on async action creators here: http://redux.js.org/docs/advanced/AsyncActions.html

Categories

Resources