The common cause for my issue when researching this is mutating the state and not returning a new object of the state which causes redux to not recognize a change. However, this is not and has never been an issue and i'm well aware of it. I'm returning a new object. In the logger which you can see in the attached image it displays the successful api call resolved and the nextState is updated but never rendered. Refreshing the page acts exactly the same even though i expected to possibly need to do so upon initial landing to root page.
Component:
import pokemonReducer from '../../reducers/pokemon_reducer';
import PokemonIndexItem from './pokemon_index_item';
import {Route} from 'react-router-dom';
import PokemonDetailContainer from './pokemon_detail_container';
class PokemonIndex extends React.Component {
componentDidMount() {
this.props.requestAllPokemon();
}
render() {
const pokemon = this.props.pokemon;
return (
<section className="pokedex">
<Route path='/pokemon/:pokemonID' component={PokemonDetailContainer} />
<ul>{pokemon && pokemon.map(poke => <li>{poke.name}{poke.id}</li>)}</ul>
</section>
);
}
}
export default PokemonIndex;
and the container:
import {connect} from 'react-redux';
import { selectAllPokemon } from '../../reducers/selectors';
import PokemonIndex from './pokemon_index';
import { requestAllPokemon } from '../../actions/pokemon_actions';
const mapStateToProps = state => ({
pokemon: selectAllPokemon(state)
});
const mapDispatchToProps = dispatch => ({
requestAllPokemon: () => dispatch(requestAllPokemon())
});
export default connect(mapStateToProps, mapDispatchToProps)(PokemonIndex);
the reducer:
import { RECEIVE_ALL_POKEMON, RECEIVE_SINGLE_POKEMON} from '../actions/pokemon_actions';
const pokemonReducer = (initialState = {}, action) => {
Object.freeze(initialState);
switch(action.type) {
case RECEIVE_ALL_POKEMON:
return Object.assign({}, initialState, action.pokemon);
case RECEIVE_SINGLE_POKEMON:
let poke = action.payload.pokemon
return Object.assign({}, initialState, {[poke.id]: poke})
default:
return initialState;
}
};
export default pokemonReducer;
secondary reducer:
import { combineReducers } from 'redux';
import pokemonReducer from './pokemon_reducer'
const entitiesReducer = combineReducers({
pokemon: pokemonReducer,
});
export default entitiesReducer;
rootreducer:
import {combineReducers} from 'redux';
import entitiesReducer from './entities_reducer';
const rootReducer = combineReducers({
entities: entitiesReducer
});
export default rootReducer;
as requested here is the selectors defined in reducers folder
export const selectAllPokemon = (state) => {
Object.values(state.entities.pokemon);
};
export const selectSinglePokemon = (state) => {
Object.values(state.entities.pokemon)
};
and here is the actions created:
export const RECEIVE_ALL_POKEMON = "RECEIVE_ALL_POKEMON";
export const RECEIVE_SINGLE_POKEMON = "RECEIVE_SINGLE_POKEMON";
import * as APIUtil from '../util/api_util';
export const receiveAllPokemon = (pokemon) => (
{
type: RECEIVE_ALL_POKEMON,
pokemon
}
);
export const requestAllPokemon = () => (dispatch) => {
APIUtil.fetchAllPokemon()
.then(
pokemon =>
{ dispatch(receiveAllPokemon(pokemon));}
);
};
export const receiveSinglePokemon = data => (
{
type: RECEIVE_SINGLE_POKEMON,
data
}
);
export const requestSinglePokemon = id => (dispatch) => {
APIUtil.fetchSinglePokemon(id)
.then(pokemon => {dispatch(receiveSinglePokemon(pokemon));
return pokemon;});
};
nextstate showing in console
As you stated in your question, your redux state is getting properly set but your new state is never being rendered and I think this has to do with your selector. It looks to me that you forgot to return your computed state.
export const selectAllPokemon = (state) => {
Object.values(state.entities.pokemon);
};
// will return undefined
For returning your state you have two options:
Explicit return
export const selectAllPokemon = (state) => {
return Object.values(state.entities.pokemon);
};
Implicit return
export const selectAllPokemon = (state) => (
Object.values(state.entities.pokemon);
);
I refer to this article or look at the examples I created in playground to get a better unstanding of implicit and explicit return in arrow functions.
Related
I'm a newbie in redux and react.js,
I am trying to make a button disappear on a component in react.js by putting an if condition on the state variable (articlesTable/index.js), which is connected to the redux library function on another file (actions/actionArticles.js), when a button on articlesTable/index.js is clicked, the component is connected with actions/actionArticles.js and dispatch a function in actions/actionArticles.js, which is called loadMoreData().
The function I am trying to configure the state in redux is,
in articlesActions.js
export const loadMoreArticles = () => async (dispatch, getState) => {
const lastArticleKey = Object.keys(getState().articlesMap).pop();
const lastArticle = getState().articlesMap[lastArticleKey];
console.log("articleMap", getState().articlesMap);
console.log("Last article", lastArticleKey, lastArticle);
let filteredArticles = {};
const uid = getState().auth.uid;
const userLevel = getState().profile.userLevel;
} else {
const filteredArticlesArray = [];
var lastArticleReached = false;
...
var lastArticleInArray = filteredArticlesArray[filteredArticlesArray.length-1];
if (lastArticleInArray[0]===lastArticleKey) {
console.log("Bingo, last article reached!");
lastArticleReached = true;
}
else if (lastArticleInArray[0]!== lastArticleKey)
{
console.log("Not last article");
lastArticleReached = false;
}
filteredArticles = Object.fromEntries(filteredArticlesArray.reverse());
}
dispatch({type: LAST_ARTICLE_REACHED, payload: lastArticleReached})
...
};
I dispatch this function with
dispatch({ type: LOAD_MORE_ARTICLES, payload: filteredArticles });
in the code snippet above
The root reducer looks like this,
reducers/index.js
import { combineReducers } from 'redux';
import { reducer as formReducer } from 'redux-form';
import articlesStatusReducer from './articlesStatusReducer';
const rootReducer = combineReducers({
...
articlesStatus: articlesStatusReducer,
form: formReducer,
...
});
export default rootReducer;
In articleStatusReducer,
import {LAST_ARTICLE_REACHED} from "../actions/types";
export default function(state = {}, action) {
switch(action.type) {
case(LAST_ARTICLE_REACHED):
console.log(action.payload);
return action.payload;
default:
return state;
}
}
In the articlesTable/index.js, I connect like this
const mapStateToProps = (state) => {
return {
articlesMap: state.articlesMap,
appStatus: state.appStatus,
profile: state.profile,
lastArticleReached: state.articlesStatus,
}
};
const mapDispatchToProps = (dispatch) => {
return {
getArticlesWithData: () => dispatch(getArticlesWithData()),
loadMore: () => dispatch(loadMoreArticles())
}
};
export default compose(
withRouter,
connect(mapStateToProps, mapDispatchToProps)
)(ArticlesTable)
For some reason, articleStatus isn't recognised and when I do
console.log(this.props.articleStatus)
state.articleStatus is undefined
How can I reference state.articleStatus which should be boolean ?
Edit:
For some reason when I put it in a conditional JSX brackets in the render method, it prints out undefined
render () => {
{
console.log(this.props.lastArticleReached),
!this.props.lastArticleReached
: <Button> </Button>
?
<div><div>
}
}``
In function mapStateToProps, you should map state.articleStatus to a props.
somethings like this:
const mapStateToProps = (state) => {
return {
articlesMap: state.articlesMap,
appStatus: state.appStatus,
profile: state.profile,
lastArticleReached: state.articlesStatus,
articleStatus: state.articleStatus
}
};
So this.props.articleStatus will works . :)
The problem is in your reducer. Each case of your reducer must return the state but in your case, your return action.payload.
try something like this.
case(LAST_ARTICLE_REACHED):
console.log(action.payload);
return {...state, articleStatus: action.payload};
like this, articlesStatus became an object with one props, articleStatus, your boolean.
I tried another name for the props but with similar method as Thomas Caillard,
Reducer.js
case(REACH_LAST_ARTICLE):
return {...state, lastArticleReached: action.payload}
in component index.js
const mapStateToProps = (state) => {
return {
...
lastArticleReached: state.articlesMap.lastArticleReached
...
}
};
Thanks for all the helps so far
I am trying to make a call that changes redux state but i am having problems with dispatching the action. I am sure all imports are correct. I think the main problem is in mapStateToProps but just cant seem to find it.
Call
onClick={() => this.props.ethereum}
mapStateToProps and other...
const mapStateToProps = state => {
return({
depositMenu: state.depositMenu
})
}
const mapDispatchToProps = dispatch => {
return ( {
visa: () => dispatch(visa()),
bitcoin: () => dispatch(bitcoin()),
ethereum: () => dispatch(ethereum())
})
}
export default connect(
mapStateToProps,mapDispatchToProps
)(Deposit)
Actions
export const visa= () => {
return {
type: 'VISA'
}
}
export const bitcoin = () => {
return {
type: 'BITCOIN'
}
}
export const ethereum = () => {
return {
type: 'ETHEREUM'
}
}
Reducer
const MainPageDeposit = (state = 'visa', action) => {
switch (action.type) {
case 'VISA':
return state = 'visa';
case 'ETHEREUM':
return state = 'ethereum';
case 'BITCOIN':
return state = 'bitcoin';
default:
return state;
}
}
export default MainPageDeposit;
And combine reducers
import MainPageDeposit from './MainPageDeposit';
import { combineReducers } from 'redux';
const allReducers = combineReducers({
depositMenu: MainPageDeposit,
})
export default allReducers;
I think you should change onClick={() => this.props.ethereum} to onClick={this.props.ethereum}
I am new to redux, I am trying to dispatch action on click event of button in react component.
But i cant update state i have in reducer.
types.js
export const FETCH_USERS_SUCCESS='FETCH_USERS_SUCCESS';
action.js
import {FETCH_USERS_SUCCESS} from './types';
export const fetchUsersSuccess = () =>
{
return (
{
type:FETCH_USERS_SUCCESS,
payload:'natalie',
}
);
}
reducer.js
import {FETCH_USERS_SUCCESS} from './types';
const initialState={
users:'mike',
error:null,
}
const reducer =(state=initialState,action)=> {
switch(action.type){
case FETCH_USERS_SUCCESS:
return {
...state,
users:action.payload,
}
default: return state
}
}
export default reducer;
and this is my app.js
import React from 'react';
import './App.css';
import { connect } from 'react-redux'
import { fetchUsersSuccess } from './action';
class App extends React.Component {
render(){
return (
<div>
<h1>Hello {this.props.users}</h1>
<button type="button" value="submit" onClick={this.props.handleSubmit}>submit</button>
</div>
);
}
}
const mapStateToProps = state => {
return {
users:state.users
}
}
const mapDispatchToProps=dispatch=>{
return {
handleSubmit: () => {dispatch(fetchUsersSuccess)}
}
}
export default connect(mapStateToProps, mapDispatchToProps)(App)
I am able to display initial user once but clicking on button does not change state.
Can anyone suggest why i cant update user on click event in react-redux?
Thanks.
https://react-redux.js.org/using-react-redux/connect-mapdispatch#two-forms-of-mapdispatchtoprops
mapDispathToProps has two forms: object and function. Redux documentation recommend to use object. In your case.
const mapDispatchToProps = {
handleSubmit: fetchUsersSuccess,
};
Another approach is to return a dispatch function which will then return the action which is done by using redux-thunk middleware. It is normally used for async operations.
So, your fetchUsersSuccess should be
export const fetchUsersSuccess = (dispatch) =>
{
return function() {
dispatch(
{
type:FETCH_USERS_SUCCESS,
payload:'natalie',
}
);
}
}
ES6
export const fetchUsersSuccess = (dispatch) =>
() => dispatch(
{
type:FETCH_USERS_SUCCESS,
payload:'natalie',
}
);
And then at the component level, do a currying like this.
const mapDispatchToProps=dispatch=>{
return {
handleSubmit: fetchUsersSuccess(dispatch)
}
}
Now you can call the handleSubmit or bind them already.
I have a route to a component HandlingIndex:
<Route strict path={handlingCasePath} component={HandlingIndex} />
HandlingIndex is wrapped with a trackRouteParam component. trackRouteParam component looks like this:
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { withRouter } from 'react-router-dom';
import { bindActionCreators } from 'redux';
import { connect } from 'react-redux';
import { parseQueryString } from '../../utils/urlUtils';
const defaultConfig = {
paramName: '',
parse: a => a,
paramPropType: PropTypes.any,
storeParam: () => undefined,
getParamFromStore: () => undefined,
isQueryParam: false,
paramsAreEqual: (paramFromUrl, paramFromStore) => paramFromUrl === paramFromStore
};
/**
* trackRouteParam
*
* Higher order component that tracks a route parameter and stores in the application
* state whenever it changes.
* #param config
*/
const trackRouteParam = config => (WrappedComponent) => {
class RouteParamTrackerImpl extends Component {
constructor() {
super();
this.updateParam = this.updateParam.bind(this);
}
componentDidMount() {
this.updateParam();
}
componentDidUpdate(prevProps) {
this.updateParam(prevProps.paramFromUrl);
}
componentWillUnmount() {
const { storeParam } = this.props;
storeParam(undefined);
}
updateParam(prevParamFromUrl) {
const { paramFromUrl, storeParam, paramsAreEqual } = this.props;
if (!paramsAreEqual(paramFromUrl, prevParamFromUrl)) {
storeParam(paramFromUrl);
}
}
render() {
const {
paramFromUrl,
paramFromStore,
storeParam,
paramsAreEqual,
...otherProps
} = this.props;
return <WrappedComponent {...otherProps} />;
}
}
const trackingConfig = { ...defaultConfig, ...config };
RouteParamTrackerImpl.propTypes = {
paramFromUrl: trackingConfig.paramPropType,
paramFromStore: trackingConfig.paramPropType,
storeParam: PropTypes.func.isRequired,
paramsAreEqual: PropTypes.func.isRequired
};
RouteParamTrackerImpl.defaultProps = {
paramFromUrl: undefined,
paramFromStore: undefined
};
const mapStateToProps = state => ({ paramFromStore: trackingConfig.getParamFromStore(state) });
const mapDispatchToProps = dispatch => bindActionCreators({ storeParam: trackingConfig.storeParam }, dispatch);
const mapMatchToParam = (match, location) => {
const params = trackingConfig.isQueryParam ? parseQueryString(location.search) : match.params;
return trackingConfig.parse(params[trackingConfig.paramName]);
};
const mergeProps = (stateProps, dispatchProps, ownProps) => ({
...ownProps,
...stateProps,
...dispatchProps,
paramFromUrl: mapMatchToParam(ownProps.match, ownProps.location),
paramsAreEqual: trackingConfig.paramsAreEqual
});
const RouteParamTracker = withRouter(connect(mapStateToProps, mapDispatchToProps, mergeProps)(RouteParamTrackerImpl));
RouteParamTracker.WrappedComponent = WrappedComponent;
Object.keys(RouteParamTracker).forEach((ownPropKey) => {
RouteParamTracker[ownPropKey] = WrappedComponent[ownPropKey];
});
return RouteParamTracker;
};
export default trackRouteParam;
In the component HandlingIndex, I am trying to get a param caseNumber from the url. Just showing the relevant parts here from the component:
const mapStateToProps = state => ({
selectedCaseNumber: getSelectedCaseNumber(state)
});
export default trackRouteParam({
paramName: 'caseNumber',
parse: caseNumberFromUrl => Number.parseInt(caseNumberFromUrl , 10),
paramPropType: PropTypes.number,
storeParam: setSelectedCaseNumber,
getParamFromStore: getSelectedCaseNumber
})(connect(mapStateToProps)(requireProps(['selectedCaseNumber'])(HandlingIndex)));
Action creator for the setSelectedCaseNumber is:
export const setSelectedCaseNumber= caseNumber=> ({
type: SET_SELECTED_CASE_NUMBER,
data: caseNumber
});
So, when I am going to the route 'case/1234', where the parameter is caseNumber: 1234 where I am setting the selectedCaseNumber I see that the data field is NaN. On inspecting the console, I can see that I in the function:
const mapMatchToParam = (match, location) => {
const params = trackingConfig.isQueryParam ? parseQueryString(location.search) : match.params;
return trackingConfig.parse(params[trackingConfig.paramName]);
};
I can see that match.params is an empty object.
I am not sure why is that, why I am getting an empty object?
In trackRouteParam HOC,
At line:
const RouteParamTracker = withRouter(connect(mapStateToProps, mapDispatchToProps, mergeProps)(RouteParamTrackerImpl));
You try edit:
const RouteParamTracker = connect(mapStateToProps, mapDispatchToProps, mergeProps)(withRouter(RouteParamTrackerImpl));
Hope can help you!
I am having a wierd issue in react-redux , i am getting all the state instead of the state that i passed
this is my code:
Action.js
import socketIOClient from 'socket.io-client'
const DATA_URL = "LINK TO API";
export const GET_DATA ='GET_DATA';
export const LIVE_DATA = 'LIVE_DATA';
const parseData= arr => arr.reverse().map((i)=>([i[0],i[1],i[3],i[4],i[2],i[5]]))
export const getData = () => dispatch =>{
fetch(DATA_URL).then(res => res.json())
.then(data => dispatch({
type:GET_DATA,
payload:parseData(data)
}
))
}
export const getLive = () => dispatch => {
var checkTime=0;
const socket = socketIOClient('http://localhost:3001');
socket.on("candle",(res)=>{
if(checkTime <= res[0]){
checkTime = res[0];
dispatch({
type:LIVE_DATA,
payload:res
})
}
})
}
api.js
import {GET_DATA,LIVE_DATA} from '../actions/index';
const INITIAL_STATE = {all:[],live:[]}
export default (state = INITIAL_STATE, action) => {
switch (action.type) {
case GET_DATA:
return Object.assign({},state,{all:action.payload})
case LIVE_DATA:
return Object.assign({},state,{live:action.payload})
default:
return state;
}
}
reducer.js
import { combineReducers } from 'redux';
import all from './api';
import live from './api';
const reducers = combineReducers({
candle:all,
livedata:live
});
export default reducers;
As you can see i am passing all to candle and live to livedata
But in my Reduxdevtools i can access everything from both candle and livedata as you can see in the screen shot
This is how i dispatch the action on my component
App.js
const mapStateToProps = state => ({
data: state.candle.all,
live: state.livedata.live
})
Can someone tell me what need to be changed so i could be able to access only
live in the livedata state and all only in the candle state
Thank you
This happened because you're accessing the same reducer but with different names. You should create separate reducer for each.
Like this:
candleReducer.js
import {GET_DATA} from '../actions';
const INITIAL_STATE = { all:[] }
export default (state = INITIAL_STATE, action) => {
switch (action.type) {
case GET_DATA:
return Object.assign({},state,{all:action.payload})
default:
return state;
}
}
liveReducer.js
import {LIVE_DATA} from '../actions';
const INITIAL_STATE = { live:[] }
export default (state = INITIAL_STATE, action) => {
switch (action.type) {
case LIVE_DATA:
return Object.assign({},state,{ live:action.payload })
default:
return state;
}
}
and then import them into combine reducers:
import { combineReducers } from 'redux';
import all from './candleReducer';
import live from './liveReducer';
const reducers = combineReducers({
candle:all,
livedata:live
});
export default reducers;
You are using the same reducer function for two 'part' of the state.
In your case you are duplicating the same logic in two parts, so the same reducer is called and it react to the same actions and update two entries in the state with the same logic.
You should consider to write two separate reducer for candle and livedata, so each of them will react to a specific action and modify correct entry in the state.
But if candle and livedata are releated to the same domain you should consider to put in one reducer and of course in one section of state, so you will end up in this situation
const reducers = combineReducers({
apiData:liveAndCandleReducer,
});
In apiData you will have
{
all:[],
live: [],
}
It's totally up to you and your application logic.