In my index.js the addCoin action is working.
import { addCoin } from './reducer/portfolio/actions'
const element = document.getElementById('coinhover');
const store = createStore(reducer, compose(
applyMiddleware(thunk),
window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__()
));
store.dispatch(addCoin('bitcoin'));
When store.dispatch is called I can see the updated state here.
However I do not want to call dispatch actions from my index.js, but from within my components.
My SearchCoin component:
import React from 'react'
import { connect } from 'react-redux'
import * as R from 'ramda'
import * as api from '../../services/api'
import { addToPortfolio, findCoins } from '../../services/coinFactory'
import { addCoin } from '../../reducer/portfolio/actions'
const mapDispatchToProps = (dispatch) => ({
selectCoin(coin) {
return () => {
dispatch(addCoin(coin))
}
}
});
class SearchCoin extends React.Component {
constructor(props) {
super(props)
this.state = {
searched: []
};
// console.log('props', props);
this.close = this.close.bind(this);
}
componentDidMount() {
this.coinInput.focus();
this.handleChange = this.handleChange.bind(this);
this.clickCoin = this.clickCoin.bind(this);
}
handleChange() {
const text = document.getElementById('coin-search').value;
const search = (text) => this.setState({ searched: findCoins(text) });
const clearSearch = () => this.setState({ searched: [] });
text.length > 1 ? search(text) : clearSearch();
}
clickCoin(coin) {
console.log('clickCoin', coin);
// api.getCoin(coin.id).then((res) => {
// const apiCoin = R.head(res.data);
// addToPortfolio(apiCoin);
// });
this.props.selectCoin(coin);
this.props.closeSearch();
}
close() {
this.props.closeSearch();
}
render() {
const searched = this.state.searched.map((coin) => {
return (
<li key={ coin.id } onClick={ ()=> this.clickCoin(coin) }>
<div className="coin-logo">
<img src={ coin.logo }/>
</div>
<span>{ coin.name }</span>
</li>
);
});
return (
<div id="search-coin-component">
<input type="text"
id="coin-search"
className="coin-search-input fl"
placeholder="Search"
onChange={ ()=> this.handleChange() }
ref={ (input) => { this.coinInput = input; } } />
<div className="icon-cancel-outline fl" onClick={ this.close }></div>
<div className="coin-select">
<ul>
{ searched }
</ul>
</div>
</div>
)
}
}
export default connect(null, mapDispatchToProps)(SearchCoin)
This is the onClick:
<li key={ coin.id } onClick={ ()=> this.clickCoin(coin) }>
At the bottom of the file I am using connect to add mapDispatchToProps
export default connect(null, mapDispatchToProps)(SearchCoin)
Here is the class method clickCoin which calls this.props.selectCoin
clickCoin(coin) {
console.log('clickCoin', coin);
this.props.selectCoin(coin);
this.props.closeSearch();
}
Finally selectCoin
import { addCoin } from '../../reducer/portfolio/actions'
const mapDispatchToProps = (dispatch) => ({
selectCoin(coin) {
return () => {
dispatch(addCoin(coin))
}
}
});
However when I click the button it seems like the dispatch is not fired as nothing happens to the redux state.
import * as R from 'ramda'
import * as api from '../../services/api'
import { addToPortfolio } from '../../services/coinFactory'
export const ADD_COIN = 'ADD_COIN'
export function addCoin(coin) {
console.log('addCoin', coin);
return dispatch =>
api.getCoin(coin)
.then((res) => addToPortfolio(R.head(res.data)))
.then((portfolio) => dispatch(add(portfolio)));
}
// action creator
export function add(portfolio) {
return {
type: ADD_COIN,
portfolio
}
}
The reducer
import { ADD_COIN } from './actions'
const initialState = [];
export default (state = initialState, action) => {
switch(action.type) {
case ADD_COIN:
return action.portfolio;
default:
return state;
}
}
the reducer/index.js
import { combineReducers } from 'redux'
import portfolio from './portfolio'
export default combineReducers({
portfolio
});
Apart from azium answer, you can use actions like this. It saves you some writing,
export default connect(null, {addCoin})(SearchCoin)
and you can use it like this,
clickCoin(coin) {
console.log('clickCoin', coin);
this.props.addCoin(coin);
this.props.closeSearch();
}
The problem is that you are wrapping your function with an extra function.
Change:
const mapDispatchToProps = (dispatch) => ({
selectCoin(coin) {
return () => { <--- returning extra function
dispatch(addCoin(coin))
}
}
})
to:
const mapDispatchToProps = (dispatch) => ({
selectCoin(coin) { dispatch(addCoin(coin)) }
})
Related
I am trying to use redux with react to do an api call to GET some data. When calling the function in my component, the reducer is not seeing the action.type and the function is returning a Promise resolved. I've not used redux-thunk before. The code as I have I feel should work but I am having difficulty finding the error. Here is the code.
Index.js
const store = createStore(rootReducer, composeWithDevTools(applyMiddleware(thunkMiddleware, devToolsEnhancer)));
ReactDOM.render(
<Provider store={store}>
<BrowserRouter>
<App />
</BrowserRouter>
</Provider>,
document.getElementById('root')
);
Action
import axios from 'axios';
export const GET_ALL_CASES = "GET_ALL_CASES";
const getCasesSuccess = (cases) => {
return {
type: GET_ALL_CASES,
cases
}
};
export const getAllCases = () => {
return (dispatch) => {
axios.get('https://corona.lmao.ninja/countries?sort=country')
.then(response => {
dispatch(getCasesSuccess(response.cases))
})
.catch(error => {
throw(error)
})
}
}
Reducer
import { GET_ALL_CASES } from '../actions';
const initialState = {
allCases: []
}
const rootReducer = (state = initialState, action) => {
switch (action.type) {
case GET_ALL_CASES:
return { ...state, allCases: [...action.cases]}
default:
return state;
}
}
export default rootReducer;
Component
class Second extends React.Component {
constructor(props) {
super(props);
this.state = { }
}
componentDidMount = () => {
getAllCases()
}
render() {
return (
<div>
{this.props.data[0]}
</div>
);
}
}
const mapStateToProps = (state) => (
{
data: state.allCases
}
)
const mapDispatchToProps = dispatch => {
return {
getAllCases: () => dispatch(getAllCases())
}
}
export default connect(mapStateToProps, mapDispatchToProps)(Second);
When calling the function, if I change it to this.props.getAllCases(), I get this error.
Unhandled Rejection (Error): Expected the reducer to be a function.
▶ 5 stack frames were collapsed.
getAllCases
C:/Users/Owner/Desktop/corona-app/src/containers/second.js:33
30 |
31 | const mapDispatchToProps = dispatch => {
32 | return {
> 33 | getAllCases: () => dispatch(getAllCases())
| ^ 34 | }
35 | }
36 |
There are different ways to use dipatchMapToProps or how ever its named.
But don't think too much on it as I do, use the easiest one
as an object
const dispatchToProps = {
fun1,
enter,
code,
herefun2
}
as function
const mapDispatchToProps = dispatch => {
return {
// dispatching plain actions
increment: () => dispatch({ type: 'INCREMENT' }),
decrement: () => dispatch({ type: 'DECREMENT' }),
reset: () => dispatch({ type: 'RESET' })
}
import React from "react";
import {connect} from "react-redux";
import {getAllCases} from "path-of-the-file";
class Second extends React.Component {
constructor(props) {
super(props);
this.state = {
someState: ""
}
}
componentDidMount () {
const { getAllCases } this.props;
getAllCases()
}
render() {
const {data} = this.props;
return (
<div>
{data[0]}
</div>
);
}
}
const mapStateToProps = (state) => {
return {
data: state.allCases
}
}
const mapDispatchToProps = { getAllCases }
export default connect(mapStateToProps, mapDispatchToProps)(Second);
```
So when I check Redux dev-tools i see that I've received my data and they are a part of the state, but when I try to use conditional rendering it wont render the page and gives error TypeError: Cannot read property 'Global Quote' of undefined !
If I just use this.props.data.TSLA it works fine and the page renders...
When I use this.props.data.TSLA["Global Quote"]["01. symbol"] page won't render! (the keys are strings in the JSON so I need to use square brackets).
I am also using Redux-Thunk !
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import { START_FETCH_DATA } from './redux/dataReducer';
class Fetcher extends Component {
componentDidMount() {
this.props.START_FETCH_DATA()
}
render() {
const { data, dataLoading } = this.props;
return (
<li className="tesla-container">
{ this.props.dataLoading ?
(<div className="ticker"> Loading! </div>)
:
(<div className="ticker">{
this.props.data.TSLA["Global Quote"]["01. symbol"] }</div>) }
</li>
)
}
const mapStateToProps = (state) => {
return {
data: state.data,
dataLoading: state.dataLoading
}
}
const mapDispatchToProps = (dispatch) => {
return {
START_FETCH_DATA: bindActionCreators(START_FETCH_DATA, dispatch)
}
}
export default connect(mapStateToProps, mapDispatchToProps)(Fetcher)
and here is the reducer + actions...
export const dataReducer = (state = {dataLoading: true}, action) => {
switch(action.type) {
case "START_FETCH_DATA":
return {...state, dataLoading: true}
case "FINISH_FETCH_DATA":
return {...state, dataLoading: false, data: action.payload}
default:
return state;
}};
export const START_FETCH_DATA = () => {
return (dispatch) => {
Promise.all(
[
fetch(`https://www.alphavantage.co/query?function=GLOBAL_QUOTE&symbol=TSLA&apikey=LOL`).then(data => data.json()),
fetch(`https://www.alphavantage.co/query?function=GLOBAL_QUOTE&symbol=AMZN&apikey=LOL`).then(data => data.json())
]
)
.then(([TSLA, AMZN]) => {
dispatch({ type: "FINISH_FETCH_DATA", payload: {TSLA, AMZN} })
})
}};
DEVTOOLS SCREENSHOT
https://imgur.com/a/2Tcrdpe
For starters, you should use the data provided by redux in render():
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import { START_FETCH_DATA, dataReducer } from './redux/dataReducer';
class Fetcher extends Component {
componentDidMount() {
this.props.START_FETCH_DATA()
}
render() {
return (
<li className="tesla-container">
{ this.props.dataLoading ?
(<div className="ticker"> Loading! </div>)
:
(<div className="ticker">{
this.props.data.TSLA["Global Quote"]["01. symbol"] }</div>) }
</li>
)
}
const mapStateToProps = (state) => {
return {
data: state.data,
dataLoading: state.dataLoading
}
}
const mapDispatchToProps = (dispatch) => {
return {
START_FETCH_DATA: bindActionCreators(START_FETCH_DATA, dispatch)
}
}
export default connect(mapStateToProps, mapDispatchToProps)(Fetcher)
I fixed my issue.
Fixed code below:
`const mapStateToProps = (state) => {
return {
dataLoading: state.dataReducer.dataLoading,
data: state.dataReducer.data
}
}`
instead of
`const mapStateToProps = (state) => {
return {
dataLoading: state.dataLoading,
data: state.data
}
}`
I have a component that displays data from the state. I'm using redux for state. I want to be able to click a button and filter the state. But I'm stuck on dispatching the action from the button.
Right now I have a button that is supposed to dispatch the action but it's not being called. I'm not sure if the mapsToDispatchProps is wrong or it's something else.
Here is the actions
import { GET_POLLS, SHOW_APPROVAL } from './types';
const URL = 'https://projects.fivethirtyeight.com/polls/polls.json';
export const getPolls = () => dispatch => {
return fetch(URL)
.then(res => res.json())
.then(polls => {
dispatch({ type: GET_POLLS, payload: polls })
})
}
export const getApproval = () => ({ type: SHOW_APPROVAL })
reducer
import {
GET_POLLS,
SHOW_APPROVAL
} from '../actions/types';
const pollReducer = (state = [], { type, payload }) => {
switch (type) {
case GET_POLLS:
return payload
case SHOW_APPROVAL:
return (payload.type === "trump-approval")
default:
return state
}
}
export default pollReducer;
types
export const GET_POLLS = 'GET_POLLS';
export const POLLS_LOADING = 'POLLS_LOADING';
export const SHOW_ALL = 'SHOW_ALL';
export const SHOW_APPROVAL = 'SHOW_APPROVAL';
list that displays data
import React, { Component } from 'react'
import { PollCard } from '../Components/PollCard'
// import FilterLink from './FilterLink'
import * as moment from 'moment';
import { connect } from 'react-redux'
import { getPolls, getApproval } from '../actions/index';
class PollList extends Component {
componentDidMount() {
this.props.getPolls();
}
render() {
console.log("rendering list")
const { polls } = this.props
const range = 30
var dateRange = moment().subtract(range, 'days').calendar();
var filteredPolls = polls.filter(e => Date.parse(e.endDate) >= Date.parse(dateRange)).reverse()
return (
<React.Fragment>
<button onClick={getApproval}>
Get Approval
</button>
{console.log("get approval", getApproval)}
{
filteredPolls && filteredPolls.map((poll) => (
<div key={poll.id}>
<PollCard poll={poll} />
{/* {(poll.type)} */}
</div>
))
}
</React.Fragment>
)
}
}
const mapStateToProps = state => ({
polls: state.polls
});
const mapDispatchToProps = {
getApproval
};
export default connect(
mapStateToProps,
mapDispatchToProps,
{ getPolls, getApproval }
)(PollList);
// export default PollList;
Your mapDispatchToProps() appears to be configured incorrectly. You need to define a function that returns an object, defining a key-value pair for each action you want to make available as a prop in your component.
const mapDispatchToProps = (dispatch) => {
return {
getApproval: () => {
dispatch(getApproval())
},
getPolls: () => {
dispatch(getPolls())
}
}
}
export default connect(
mapStateToProps,
mapDispatchToProp)(PollList);
Now getPolls is available as prop and you can use it in componentDidMount()
componentDidMount() {
this.props.getPolls();
}
You should also create an onClick handler for your getApproval action
handleClick = () => {
this.props.getApproval()
}
And then connect it to your onClick event-listener
<React.Fragment>
<button onClick={this.handleClick}>
Get Approval
</button>
console.log("get approval", getApproval)}
{
filteredPolls && filteredPolls.map((poll) => (
<div key={poll.id}>
<PollCard poll={poll} />
{/* {(poll.type)} */}
</div>
))
}
</React.Fragment>
Action File
export const getPolls = () => dispatch => {
fetch(URL)
.then(res => res.json())
.then(polls => {
dispatch({ type: GET_POLLS, payload: polls })
})
.catch(errors => {
dispatch({ type: "GET_ERRORS", payload: errors.response.data })
})
}
Reducer
import {
GET_POLLS,
SHOW_APPROVAL
} from '../actions/types';
const pollReducer = (state = [], { type, payload }) => {
switch (type) {
case GET_POLLS:
return payload
case SHOW_APPROVAL:
return state.filter((poll) => {
return poll.type === "trump-approval"
})
case "GET_ERRORS":
return payload
default:
return state
}
}
export default pollReducer;
You are not calling the action function.
// Either destructure it
const { polls, getApproval } = this.props;
<button onClick={getApproval}>
Get Approval
</button>
// Or use this.props.function
<button onClick={this.props.getApproval}>
Get Approval
</button>
// You don't need this
const mapDispatchToProps = {
getApproval
};
// You don't need this
const mapStateToProps = state => {
return {polls: state.polls};
};
export default connect(
mapStateToProps,
// Doing this is easier, cleaner & faster
{ getPolls, getApproval }
)(PollList);
Here you are doing it correctly;
componentDidMount() {
this.props.getPolls();
}
|I have the following component based on this:
**WarningModal.js**
import React from 'react';
import ReactDOM from 'react-dom';
import {connect, Provider} from 'react-redux';
import PropTypes from 'prop-types';
import {Alert, No} from './pure/Icons/Icons';
import Button from './pure/Button/Button';
import Modal from './pure/Modal/Modal';
import {setWarning} from '../actions/app/appActions';
import configureStore from '../store/configureStore';
const store = configureStore();
export const WarningModal = (props) => {
const {message, withCleanup} = props;
const [
title,
text,
leave,
cancel
] = message.split('|');
const handleOnClick = () => {
props.setWarning(false);
withCleanup(true);
}
return(
<Modal>
<header>{title}</header>
<p>{text}</p>
<Alert />
<div className="modal__buttons-wrapper modal__buttons-wrapper--center">
<button
onClick={() => withCleanup(false)}
className="button modal__close-button button--icon button--icon-only button--text-link"
>
<No />
</button>
<Button id="leave-warning-button" className="button--transparent-bg" onClick={() => handleOnClick()}>{leave}</Button>
<Button id="cancel-warning-button" onClick={() => withCleanup(false)}>{cancel}</Button>
</div>
</Modal>
);
}
WarningModal.propTypes = {
withCleanup: PropTypes.func.isRequired,
message: PropTypes.string.isRequired,
setWarning: PropTypes.func.isRequired
};
const mapStateToProps = state => {
console.log(state)
return {
isWarning: state.app.isWarning
}
};
const WarningModalContainer = connect(mapStateToProps, {
setWarning
})(WarningModal);
export default (message, callback) => {
const modal = document.createElement('div');
document.body.appendChild(modal);
const withCleanup = (answer) => {
ReactDOM.unmountComponentAtNode(modal);
document.body.removeChild(modal);
callback(answer);
};
ReactDOM.render(
<Provider store={store}>
<WarningModalContainer
message={message}
withCleanup={withCleanup}
/>
</Provider>,
modal
);
};
the issue I have is that 'setWarning' doesn't update the state, it does get called as I have a debugger inside the action and the reducer but the actual property doesn't not change to 'false' when:
props.setWarning(false);
gets called.
I use the following to trigger the custom modal:
const togglePromptCondition =
location.hash === '#access-templates' || location.hash === '#security-groups'
? promptCondition
: isFormDirty || isWarning;
<Prompt message={promptMessage} when={togglePromptCondition} />
To test this even further I have added 2 buttons in the application to toggle 'isWarning' (the state property I am talking about) and it works as expected.
I think that although WarningModal is connected in actual fact it isn't.
REDUCER
...
case SET_WARNING:
console.log('reducer called: ', action)
return {
...state,
isWarning: action.payload
};
...
ACTION
...
export const setWarning = status => {
console.log('action called')
return {
type: SET_WARNING,
payload: status
}
};
...
UPDATE
After having to incorporates the following:
const mapStateToProps = state => {
return {
isWarning: state.app.isWarning
}
};
const mapDispatchToProps = dispatch => {
return {
setWarning: (status) => dispatch({ type: 'SET_WARNING', payload: status })
}
};
I am now getting:
Maybe this could help?
You have to dispatch the actions in the action creator and the type of the action to dispatch should be always string.
Try this
const mapStateToProps = state => {
console.log(state)
return {
isWarning: state.app.isWarning
}
};
const mapDispatchToProps = dispatch => {
console.log(dispatch)
return {
setWarning: (status) => dispatch({ type: 'SET_WARNING', payload: status })
}
};
const WarningModalContainer = connect(mapStateToProps, mapDispatchToProps)(WarningModal);
REDUCER
...
case 'SET_WARNING':
console.log('reducer called: ', action)
return {
...state,
isWarning: action.payload
};
...
I'm new to redux so I might be missing something here...
redux/reducers/schools.js:
export const SET_SELECTED = 'schools/SET_SELECTED';
const initialState = {
selected: {},
schools: []
};
export default function schools(state = initialState, action) {
switch (action.type) {
case SET_SELECTED:
return {
...state,
selected: action.payload
};
default:
return state;
}
}
export function setSelected(school) {
return {
type: SET_SELECTED,
payload: school
};
}
containers/Search.js:
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { setSelected } from '../redux/reducers/schools';
import SchoolCard from '../components/SchoolCard';
class Search extends Component {
setSelectedSchool(school) {
this.props.dispatch(setSelected(school)); // Error here
}
renderShools(schools) {
return schools.map(school => {
return (
<div className="column is-8 is-offset-2" key={school.emis}>
<SchoolCard school={school} setSelected={this.setSelectedSchool} />
</div>
);
});
}
render() {
return (
<div className="container">
<div className="columns">{this.renderShools(this.props.schools)}</div>
</div>
);
}
}
export default connect(state => ({
schools: state.schools.schools
}))(Search);
When setSelectedSchool() in Search.js runs I get the following error:
Uncaught TypeError: Cannot read property 'dispatch' of undefined...
What am I doing wrong / what am I missing?
Code below represents an example how to map to props and then call dispatch :
import { addItem } from './actions/items';
class App extends Component {
render() {
return (
<div className="App">
// some more tags
</div>
);
}
};
const mapStateToProps = (state) => {
return {
items: state.items
};
};
const mapDispatchToProps = dispatch => {
return {
addItem: () => {
dispatch(addItem())
}
};
};
export default connect(mapStateToProps, mapDispatchToProps)(App);
Incase you do not do mapDispatchToProps then this.props.dispatch will be undefined.
export default connect(state => ({
schools: state.schools.schools
}),
(dispatch)=>{return {setSelected:(data)=>(dispatch)({type:"SET_SELECTED",payload:data})}}
)(Search);
You can give 2 objects to connect
1. mapStateToProps {
schools: state.schools.schools
}
2(dispatch)=>{return {setSelected:(data)=>(dispatch)({type:"SET_SELECTED",payload:data})}
You required dispatch (2) also in order to dispatch function
So change last line to(where you use connect) to
export default connect(state => ({
schools: state.schools.schools
}),
(dispatch)=>{return {setSelected:(data)=>(dispatch)({type:"SET_SELECTED",payload:data})}}
)(Search);
and the function call to
setSelectedSchool(school) {
this.props.setSelected(school); // Error here
}
You need to refract your code a bit
containers/Search.js
import React, { Component } from 'react';
import { connect } from 'react-redux';
import * as schoolActions from '../actions/schoolActions;
import SchoolCard from '../components/SchoolCard';
class Search extends Component {
setSelectedSchool=(school)=> {
this.props.setSelectedschool(school)
}
renderShools(schools) {
return schools.map(school => {
return (
<div className="column is-8 is-offset-2" key={school.emis}>
<SchoolCard school={school} setSelected={this.setSelectedSchool} />
</div>
);
});
}
render() {
return (
<div className="container">
<div className="columns">{this.renderShools(this.props.schools)}</div>
</div>
);
}
}
export default connect(
state => ({
schools: state.schoolReducer.schools,
}),
{ ...schoolActions }
)(Search);
Create actions/schoolActions.js
export function setSelectedschool(school){
return (dispatch)=>{
dispatch({'SET_SELECTED',school})
}
}
create reducers/schoolReducer.js
const initialState={
school:[] //assuming it's an array
}
const schoolReducer =(state=initialState,action)=>{
switch(action.type){
case 'SET_SELECTED':{
return {
...state,
school:action.school
}
}
}
}