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

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()

Related

react/redux is registering 2 calls to a reducer even though the action only fires once

I can't figure out what's causing this behavior. I have an action that's only being fired once, the reducer of that type gets the appropriate payload, but then immediately receives another call, without the action having been fired, and causes an error to be thrown.
In my files I have the following setup:
function mapDispatchToProps(dispatch) {
return bindActionCreators({fetchTile, fetchSinglePlan, clearTile, clearPlans, clearPlan}, dispatch);
}
function mapStateToProps({user, tile, plans}){
return {
user,
tile:tile.tile,
plans:plans.plans,
}
}
export default withRouter(connect(mapStateToProps, mapDispatchToProps)(SingleTileDisplay));
I realize tile.tile isn't a great naming system, but that's not what's concerning me at the moment.
the action is called on this line:
componentDidMount(){
let tileId = this.props.match.params.tileId;
this.props.fetchTile(tileId)
.then(res=>{
console.log(res);
});
}
The following action then fires:
export const FETCH_TILE = 'FETCH_TILE';
export function fetchTile(tileId){
let url = `/tiles/single/${tileId}`;
let tileRequest = axios.get(url);
return ({
type: FETCH_TILE,
payload:tileRequest
});
}
and this is the reducer that handles everything:
import {FETCH_TILE} from "../actions/action_tile";
import {CLEAR_TILE} from "../actions/action_tile";
export default function(state={tile:null}, action){
switch (action.type){
case FETCH_TILE:
return {tile:action.payload.data.tile};
case CLEAR_TILE:
return Object.assign({}, action.payload);
}
return state;
}
When the component mounts everything behaves as expected, the action fires, the reducer receives the correct data. I put break points before the action and the reducer to see when they were firing. However, right after the reducer receives its data:
the reducer is called again and this error results

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.

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

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)
};
}

Error in redux when executing an action: Uncaught type error: cannot read property 'type' of undefined

Just new to React. I guess It's a basic question but I can't get why the reducer doesn't get fired or update the state:
My HomeView.js:
....
const { localeChange, counter, locale } = this.props
return (
<div>
<button onClick={() => increment(7)}>Increment</button>
<input type='text' value = {counter}/>
.....
</div>
)
const mapStateToProps = (state) => ({
locale: state.locale,
counter: state.counter
})
export default connect(mapStateToProps, {localeChange, increment})(HomeView)
My reducer (constant, action and reducer in the same file):
export const COUNTER_INCREMENT = 'COUNTER_INCREMENT'
export function increment (text) {
return { type: COUNTER_INCREMENT, text }
}
export default function counterIncrement (state = 0, action) {
switch (action.type) {
case 'INCREMENT':
return state + action.text
default:
return state
}
}
And finally my "parent" reducer:
import { combineReducers } from 'redux'
import { routeReducer as router } from 'react-router-redux'
import locale from './modules/locale'
import counter from './modules/counter'
export default combineReducers({
locale,
router,
counter
})
What I understand:
In my state I have a field named counter (it's there using redux dev tools).
When I click the button I dispatch increment action, so I really should dispatch an action like this:
{type: COUNTER_INCREMENT, text: 7}
However counterIncrement function (reducer) gets action as undefined: Uncaught type error: cannot read property 'type' of undefined.
Any way I could debug this properly? I put a breakpoint in the reducer counterIncrement but doesn't get fired.
Redux validate that every action you trigger actually contains a type property.
My guess is that you're calling dispatch() with undefined. Your example code doesn't contains any dispatch calls, so I won't be able to help further - but my guess is that it'll be trivial to figure out. Either you call dispatch() with the wrong arguments, or your action creator doesn't return an object with a type property.
Side note (unrelated to your question), but your action creator type label doesn't match the one inside your reducer.

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