React.js Redux actions and reducers do not get initated - javascript

I am having an issue with my React-Redux app. I have created Redux action & reducer and connected them to my React component. However, it seems like it never gets to my Redux action & reducer.
I put "debugger" in my action and reducer but those debuggers never get initiated.
Could you please help me fix this issue?
Am I missing something here?
Here the current codes in the following files:
Here is the code for my app.js:
import React, { Component } from 'react';
import { render } from 'react-dom';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';
import { createStore, applyMiddleware } from 'redux';
import thunk from 'redux-thunk';
import ReduxPromise from 'redux-promise';
import rootReducer from './reducers';
import App from './components/index';
const createStoreWithMiddleware = applyMiddleware(thunk, ReduxPromise)(createStore);
render(
<Provider store={createStoreWithMiddleware(rootReducer)}>
<App />
</Provider>,
document.getElementById('app')
);
Here is my code for index.js:
As you can see below, I put a debugger in the "componentDidMount()", and when In run it, it gets to that point.
import React, { Component } from 'react';
import { render } from 'react-dom';
import { connect } from 'react-redux';
import * as AllNewsApiActions from '../actions/newsApiActions';
import '../../css/style.css';
class App extends Component {
componentDidMount() {
this.props.getNewsApi();
debugger;
}
render() {
return (
<div>
<p>Hello from react</p>
<p>Sample Testing</p>
</div>
);
}
}
const mapStateToProps = (state) => ({
newNews: state.newNews
});
export default connect(mapStateToProps, {
...AllNewsApiActions
})(App);
Here is my action:
import * as types from './newsTypes';
const API_KEY = "6c78608600354f199f3f13ddb0d1e71a";
export const getNewsApi = () => {
debugger;
return (dispatch, getState) => {
dispatch({
type: 'API_REQUEST',
options: {
method: 'GET',
endpoint: `https://newsapi.org/v2/top-headlines?country=us&category=business&apiKey=${API_KEY}`,
actionTypes: {
success: types.GET_NEWS_API_SUCCESS,
error: types.GET_NEWS_API_ERROR
}
}
});
}
}
Here is my reducer:
import * as types from '../actions/newsTypes';
const initialState = {
newNews: []
};
const getNewsAPIReducer = (state = initialState, action) => {
switch(action.type) {
case types.GET_NEWS_API_SUCCESS:
debugger;
return { ...state, newNews: action.data };
case types.GET_NEWS_API_ERROR:
debugger;
return { ...state, error: action.data };
default: {
return state;
};
};
};
export default getNewsAPIReducer;
Here is the index.js file in the reducer folder:
import { combineReducers } from 'redux';
import NewsApiReducer from './newsApiReducer.js';
const rootReducer = combineReducers({
newNews: NewsApiReducer
});
export default rootReducer;

Related

Redux | Error: Expected the reducer to be a function

I'm trying to use Redux, Redux-Thunk in my app. I did it before and it worked but here it keeps showing Error: Expected the reducer to be a function
scr/App.js;
import React, { Component } from 'react';
import { Router } from 'react-router-dom';
import { createBrowserHistory } from 'history';
import { Chart } from 'react-chartjs-2';
import { ThemeProvider } from '#material-ui/styles';
import { chartjs } from './helpers';
import theme from './theme';
import 'react-perfect-scrollbar/dist/css/styles.css';
import './assets/scss/index.scss';
import Routes from './Routes';
import { Provider } from 'react-redux';
// redux
import { createStore, applyMiddleware, compose, combineReducers } from 'redux';
import thunk from 'redux-thunk';
import { composeWithDevTools } from 'redux-devtools-extension/developmentOnly';
import * as actionTypes from './store/actions/actionTypes';
import rootReducer from './store/reducers/rootReducer';
const browserHistory = createBrowserHistory();
Chart.helpers.extend(Chart.elements.Rectangle.prototype, {
draw: chartjs.draw
});
const store = createStore(
rootReducer,
compose(applyMiddleware(thunk), composeWithDevTools)
);
export default class App extends Component {
render() {
return (
<Provider store={store}>
<ThemeProvider theme={theme}>
<Router history={browserHistory}>
<Routes />
</Router>
</ThemeProvider>
</Provider>
);
}
}
when I tried to log rootReducer it logged an empty line for the initial load then the app breaks then it logs the rootReducer
src/store/rootReducer.js
import { combineReducers } from 'redux';
import searchReducer from './searchReducer';
const rootReducer = combineReducers({
searchReducer: searchReducer
});
export default rootReducer;
src/store/reducers/searchReducer.js
import * as actionTypes from '../actions/actionTypes';
const initialState = {
loading: false,
result: [],
error: ''
};
const searchReducer = (state = initialState, action) => {
switch (action.type) {
case actionTypes.SEARCH_TRIGGER:
return {
...state,
loading: true,
result: [],
error: ''
};
case actionTypes.SEARCH_ERROR:
return {
...state,
loading: false,
result: [],
error: action.error
};
case actionTypes.SEARCH_SUCCESS:
return {
...state,
loading: false,
error: '',
result: action.result
};
default:
return state;
}
};
export default searchReducer;
src/store/actions/actionTypes.js
// create campaign actions
export const UPDATE_STEPPER_STATE = 'UPDATE_STEPPER_STATE';
export const SEARCH_TRIGGER = 'SEARCH_TRIGGER';
export const SEARCH_SUCCESS = 'SEARCH_SUCCESS';
export const SEARCH_ERROR = 'SEARCH_ERROR';
src/store/actions/stepperActions.js
import * as actionTypes from './actionTypes';
export const updateStepperState = data => {
return {
action: actionTypes.UPDATE_STEPPER_STATE,
data
};
};
exported in index.js inside the same directory
export { updateStepperState } from './stepperActions';
packages
"react-redux": "^7.1.3",
"redux": "^4.0.4",
"redux-devtools-extension": "^2.13.8",
"redux-thunk": "^2.3.0",
"thunk": "0.0.1",
It looks like your path to searchReducer is incorrect.
Change this:
import searchReducer from './searchReducer';
to this:
import searchReducer from './reducers/searchReducer';
in your rootReducer.js file.
It's giving you the error Expected the reducer to be a function because searchReducer is returning undefined.
hey try creating store like this
const store = createStore(
rootReducer,
composeWithDevTools(applyMiddleware(thunk))
);
i think you can not use multiple store enhancers together,you either choose compose or composeWIthDevTools,and create store based on environment like for development use composeWithDevTools and for production use compose

Redux store isn't updating

I am trying to update the redux store with a list of groupsNames.
The axios call inside the action works and there is a response.
When I dispatch the payload the redux store doesnt get updated with response data.
FileName: actions/index.js
CODE:
export const getGroups = () => async dispatch => {
axios.get(url + "groups")
.then(response => {
console.log(response.data);
dispatch({
type: GET_GROUPS,
payload: response.data
})
},
function(error) {
console.log(error)
}
);
};
Filename: reducers/groupsReducer.js
CODE:
import _ from 'lodash';
import {
GET_GROUPS,
} from '../actions/types';
export default (state = {}, action) => {
console.log(action.payload);
console.log("inside groupsREducer");
switch (action.payload) {
case GET_GROUPS:
console.log(action.payload);
return {...state, ..._.mapKeys(action.payload, 'id')};
default:
return state;
}
}
//mapKeys is a function from lowdadsh that takes an array and returns an object.
FileName: reducers/index.js
CODE:
import { combineReducers } from 'redux';
import authReducer from './authReducer';
import { reducer as formReducer } from 'redux-form';
import postsReducer from './postsReducer';
import groupsReducer from './groupsReducer';
export default combineReducers ({
auth: authReducer,
form: formReducer,
posts: postsReducer,
groups: groupsReducer,
});
FileName: GroupList.js
CODE:
import React from "react";
import { connect } from "react-redux";
import { getGroups } from '../../actions';
class GroupList extends React.Component {
componentDidMount() {
this.props.getGroups();
}
render() {
return <div>
<h1>GroupList</h1>
</div>;
}
}
export default connect(null, { getGroups })(GroupList);
FileName: App.js
CODE:
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';
import { createStore, applyMiddleware, compose} from 'redux';
import reduxThunk from 'redux-thunk';
import reducers from './reducers';
import Landing from './components/Landing';
const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
const store = createStore(
reducers,
composeEnhancers(applyMiddleware(reduxThunk))
);
ReactDOM.render(
<Provider store={store}>
<Landing />
</Provider>,
document.querySelector('#root')
);```
Ive connected to ReduxDevToolsExtension in chrome and when i check the state in there, it shows empty.
Your are using the wrong argument for your switch-case.
Replace switch (action.payload) with switch (action.type)

Redux Thunk for simple async requests

I am building a simple app to retrieve some recipes from an API URL.
I am reading the documentation of Thunk to implement it but I cannot understand how to set the async get request.
What is strange is that if I console.log the action passed into the reducer it definitely retrieves the correct object (a list of recipes for shredded chicken).
When I pass the action onto the the reducer, instead, it throws the error:
"Unhandled Rejection (Error): Given action "FETCH_RECIPES", reducer "recipes" returned undefined. To ignore an action, you must explicitly return the previous state. If you want this reducer to hold no value, you can return null instead of undefined."
Is there any error in my action creator? is the API call properly done?
Store
import React from 'react';
import { render } from 'react-dom'
import { Provider } from 'react-redux'
import { createStore, applyMiddleware } from 'redux'
import SearchBar from './components/App';
import thunk from 'redux-thunk';
import 'bootstrap/dist/css/bootstrap.css';
import rootReducer from './reducers';
const store = createStore(rootReducer, applyMiddleware(thunk));
render(
<Provider store={store}>
<SearchBar />
</Provider>,
document.getElementById('root')
)
Component
import React, { Component } from 'react';
import { connect } from 'react-redux';
import './App.css';
import { Button } from 'reactstrap';
import { Form } from 'reactstrap';
import { bindActionCreators } from 'redux';
import {fetchRecipe } from '../actions';
class SearchBar extends Component {
constructor(props) {
super(props)
this.state = { term: ''};
this.typeRecipe = this.typeRecipe.bind(this)
this.onFormSubmit = this.onFormSubmit.bind(this);
}
onFormSubmit(e) {
e.preventDefault()
this.props.fetchRecipe(this.state.term)
}
typeRecipe(e) {
this.setState({term: e.target.value});
}
render() {
return (
<div className="SearchBar">
<Form onSubmit={this.onFormSubmit}>
<input type='text'
value={this.state.term}
placeholder='ciao'
onChange={this.typeRecipe}
/>
<br/>
<Button id='ciao' className='btn-success'>Submit</Button>
</Form>
</div>
);
}
}
function mapDispatchToProps(dispatch) {
return bindActionCreators({ fetchRecipe }, dispatch);
}
export default connect(null, mapDispatchToProps)(SearchBar);
Action creator
import axios from 'axios';
export const FETCH_RECIPES = 'FETCH_RECIPES';
const API_KEY = 'xxx';//not the real one.
export function fetchRecipe() {
const request = axios.get(`http://food2fork.com/api/search?key=${API_KEY}&q=shredded%20chicken`);
return (dispatch) => {
request.then(({data}) =>{
dispatch({ type: FETCH_RECIPES, payload: data})
})
}
}
reducer
import { FETCH_RECIPES } from '../actions';
export default function (state = {}, action) {
switch(action.type) {
case FETCH_RECIPES:
const newState = action.payload.data;
return newState;
default:
return state
}
}
combineReducer (index)
import recipeReducer from '../reducers/recipes_reducer';
import { combineReducers } from 'redux';
const rootReducer = combineReducers({
recipes: recipeReducer
});
export default rootReducer;
Mistake is in the reducer return statement.
export default function (state = {}, action) {
switch(action.type) {
case FETCH_RECIPES:
const newState = {...state, data : action.payload.data};
return newState;
default:
return state
}
}
Here we are adding data key to the reducer state, to access this you can use do this in you container :
export default connect((state)=>{
var mapStateToProps = {};
if(state.recipes.data) {
mapStateToProps['recipes'] = state.recipes.data
}
return mapStateToProps;
}, mapDispatchToProps)(SearchBar);
and recipes data will be available as this.props.recipes.

How would I set up my reducer properly?

I'm trying to toggle my modal via redux but for some reason I'm getting an error that says: TypeError: Cannot read property 'isModalOpen' of undefined (pointing to the line isOpen: state.global.isModalOpen inside List.js) which means my root reducer isn't defined properly. But I'm sure I've defined it properly inside index.js file. What am I doing wrong and how would I be able to resolve this and use these actiontypes properly in order for it to work?
Note: The <Modal/> component is an npm package I installed, I didn't create it on my own.
Here's my List.js file:
import React, { Component } from 'react';
import Aux from '../../../../hoc/Aux';
import { connect } from 'react-redux';
import Modal from 'react-modal';
class List extends Component {
state = {
isOpen: false
}
componentWillMount() {
Modal.setAppElement('body');
}
toggleModal = () => {
this.setState({isActive:!this.state.isActive});
}
closeModal = () => {
this.setState({isActive: false});
}
render() {
return (
<Aux>
<CheckoutButton clicked={this.toggleModal}/>
<Modal isOpen={this.state.isOpen}>
<button onClick={this.closeModal}>Close</button>
</Modal>
</Aux>
);
}
}
const mapStateToProps = state => {
return {
isOpen: state.global.isModalOpen
}
};
const mapDispatchToProps = dispatch => {
return {
closeModalRedux: () => dispatch({type: CLOSE_MODAL})
}
};
export default connect(mapStateToProps, mapDispatchToProps)(List);
Here's my reducer.js file:
import React from 'react';
import * as actionTypes from '../action/NoNameAction';
const initialState = {
isModalOpen: false
};
const global = (state = initialState, action) => {
switch(action.type) {
case actionTypes.OPEN_MODAL:
return {
...state,
isModalOpen: true,
}
case actionTypes.CLOSE_MODAL:
return {
...state,
isModalOpen: false,
}
default:
return state;
}
};
export default global;
Here's my index.js file:
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import registerServiceWorker from './registerServiceWorker';
import { Provider } from 'react-redux';
import { createStore, combineReducers } from 'redux';
import modalOpenReducer from './store/reducer/reducer';
const rootReducer = combineReducers({
isOpen: modalOpenReducer
});
const store = createStore(rootReducer);
ReactDOM.render(<Provider store={store}><App/></Provider>, document.getElementById('root'));
registerServiceWorker();
change the reducer file name to global.js and in the root.js import it as global instead of reducer.And also in the component,change to this.props.isOpen

React-Redux Uncaught Error: Expected the reducer to be a function

The github repo for this project: https://github.com/leongaban/react_starwars
Expected
My basic app runs without errors, and in the chrome devtools Redux tab, I see a list of star wars characters
Results
The main index.js
import React from 'react'
import ReactDOM from 'react-dom'
import App from './components/App'
import { createStore, applyMiddleware, compose } from 'redux'
import { Provider } from 'react-redux'
import thunk from 'redux-thunk'
import reducer from './reducer'
import { getCharacters } from './reducer/characters/actions'
const store = createStore(reducer, compose(
applyMiddleware(thunk),
window.devToolsExtension ? window.devToolsExtension : f => f
));
store.dispatch(getCharacters())
const container = document.getElementById('app-container');
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>
, container);
My src > reducer > characters > actions.js
import { API_URL } from '../../constants'
export const SET_CHARACTERS = 'SET_CHARACTERS'
export function getCharacters() {
return dispatch => {
fetch(`${API_URL}/people`)
.then(res => res.json())
.then(res => res.results)
.then(characters =>
dispatch(setCharacters(characters))
)
}
}
export function setCharacters(characters) {
return {
type: SET_CHARACTERS,
characters
}
}
reducer > characters > index.js
import { SET_CHARACTERS } from './actions'
const initialState = [];
export default (state = initialState, action) => {
switch(action.type) {
case SET_CHARACTERS:
return action.characters;
default:
return state;
}
}
reducer > index.js
import { combineReducers } from 'redux'
import characters from './characters'
export default combineReducers({
characters
})
try
const createFinalStore = compose(
applyMiddleware(thunk)
)(createStore)
const store = createFinalStore(reducer, initialState /* or {} */);
i don't think the createStore redux api wants you to pass the middleware as the second argument. that should be the initial/preloaded state
http://redux.js.org/docs/api/createStore.html

Categories

Resources