In the componentDidMount() method, i dispatched an async action that fetch some data from another API. In the redux dev tool, the fetch data success method is dispatched, and it has the right payload retrieved from response. However, this action did not seem to reach the reducer, as the state in my redux store was unchanged. I injected console.log() in my reducer and noticed that the reducer was not reached.
I viewed other relevant posts but i cant find any solutions.
I have provided the relevant code snippets and will appreciate any help! i am using thunk as the middleware.
//index.js
const rootReducer = combineReducers({
search: searchBarReducer,
});
const composeEnhancers = process.env.NODE_ENV === 'development' ? window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ : null || compose;
const store = createStore(rootReducer, composeEnhancers(applyMiddleware(thunk)));
const app = (
<Provider store={store}>
<BrowserRouter>
<App />
</BrowserRouter>
</Provider>
);
ReactDOM.render(app, document.getElementById("root"));
registerServiceWorker();
//action creator
export const suggestionsInit = (filter) => {
console.log("[suggestionsInit]")
return (dispatch) => {
let result = [];
axios
.get("")
.then((response) => {
response.data.forEach((element) => {
result.push(element.moduleCode);
});
dispatch(fetchSuggestionsSuccess(result));
})
//
};
};
export const fetchSuggestionsSuccess = (data) => {
return {
type: actionTypes.FETCH_SUGGESTIONS_SUCCESS,
data: data,
};
};
//reducer
const initialState = {
modules: [],
//
}
const reducer = (state = initialState, action) => {
switch (action.type) {
case actionTypes.SEARCHBAR_INT:
return {
...state,
modules: action.data
}
default: return state;
}
}
//associated component
componentDidMount() {
console.log("[componentDidMount]");
this.props.dispatchSuggestionsInit("modules");
}
...
//after class body
const mapStateToProps = (state) => {
return {
modules: state.search.modules,
};
};
const mapDispatchToProps = (dispatch) => {
return {
dispatchSuggestionsInit: (filter) => dispatch(actions.suggestionsInit(filter)),
};
};
export default connect(mapStateToProps, mapDispatchToProps)(withErrorHandler(SearchBar, axios));
//with error handler is just another higher order component and it does not affect the functionality.
In you reducer file you should be listening/checking for the success case of dispatch, reason being you when you dispatch the success data the action you are dispatching with action type of actionTypes.FETCH_SUGGESTIONS_SUCCESS and not INIT one.
const reducer = (state = initialState, action) => {
switch (action.type) {
case actionTypes.FETCH_SUGGESTIONS_SUCCESS:
return {
...state,
modules: action.data
};
default: return state;
}
};
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
So i'm doing a API GET request and set the data on reducer, but the component render twice, first before dispatch and another after, the first one is causing map function problem
what can i do to avoid render twice and solve map function problem?
App.js
componentDidMount(){
this.props.carregarLojas();
}
render(){
const { lojasTeste } = this.props;
//rendering 2 times
console.log(lojasTeste);
return(
<div>
lojasTeste.map((i, index) => (
<h1>{i.name}</h1>
))
</div>
)
}
const mapStateToProps = store => ({
lojasTeste: store.lojaState.lojasTeste
});
const mapDispatchToProps = dispatch => {
return {
carregarLojas: () => {
dispatch(carregarLojas());
}
};
};
export default connect(mapStateToProps, mapDispatchToProps)(App);
Action.js
export const setarLojas = (lojas) =>{
return {
type: SETAR_LOJAS,
data: lojas
}
}
export const carregarLojas = () => {
return (dispatch) => {
return API.get('loja')
.then(response => {
dispatch(setarLojas(response.data))
})
.catch(error => {
throw(error);
})
}
Reducer.js
const initialState ={
lojasTeste: {}
}
export const lojaReducer = (state = initialState, action) => {
switch (action.type){
case SETAR_LOJAS:
return {
...state,
lojasTeste: action.data
}
default:
return state;
}
}
The double render is totally normal:
Your component render once, then call the carregarLojas method which is async. When resolved, the method will update your redux store, which is connected with the props of your component (mapStateToProps). When a prop is updated, it cause automatically a rerender.
Also, for your map problem, you didn't initialized lojasTeste as an array, but as an object. You can't use map on an object (cf https://developer.mozilla.org/fr/docs/Web/JavaScript/Reference/Objets_globaux/Array/map)
I have an app which provides me with user details on requesting the static files (so at the very beginning - I don't have to login). I try to initialize the state with async action checking if the user has rights to see the app. But my Auth component, which has the App as a child doesn't re-render on changed props.
The store:
const store = createStore(
rootReducer,
composeWithDevTools(applyMiddleware(thunk))
);
store.dispatch(fetchUser());
export default store;
In the store, on initialization I dispatch an async action.
The action:
export const fetchUser = () => async dispatch => {
const response = await axios.get('/api/user/information');
dispatch({
type: FETCH_USER,
payload: response.data
});
};
The action is then passed to the reducer:
The reducer:
export const userReducer = (state = {}, action) => {
switch (action.type) {
case FETCH_USER:
return { ...state, user: action.payload };
}
return state;
};
The data from the reducer is then passed to the Auth component.
The Auth component:
class Auth extends Component {
public render() {
return this.props.user ? this.props.children : <p>Access denied</p>;
}
}
export default compose(Connectable)(Auth);
The props are passed from the connectablr hoc.
And connectable hoc:
const mapStateToProps = (state) => ({
user: state.user
});
const mapDispatchToProps = {};
export const Connectable = connect(
mapStateToProps,
mapDispatchToProps
);
So the app just stays on the "Access denied" because the user object is empty. Even more so - when the data is fetched the 'user' prop has another nested 'user' object and then there is data. I want to check if the user is not empty (and fix the double user nested object). But I have no idea why the changed props don't re-render the auth app. What can be the reason? Is it not possible to do async action when initializing the state?
change state.user to state.userReducer.user
const mapStateToProps = (state) => ({
user: state.userReducer.user
});
Your reducers design can be better.
https://egghead.io/courses/getting-started-with-redux
export const userReducer = (state = {}, action) => {
switch (action.type) {
case FETCH_USER:
return { ...state, user: action.payload };
}
return state;
};
if you want init user, I get you a example.
import * as actions from '../../actions';
const mapStateToProps = (state) => ({
user: state.user
});
export const Connectable = connect(
mapStateToProps,
actions
);
and
class Auth extends Component {
componentDidMount() {
this.props.fetchUser()
}
render() {
return this.props.user ? this.props.children : <p>Access denied</p>;
}
}
export default compose(Connectable)(Auth);
without redux it works so that not a api connection problem
I have an express app connected to react with proxy I have already managed to display my data in react but now i want to make that in redux soo:
There is my problem, i have maked all the reducers/action, store and combine reducer but I didn't see any datas in my page and i haven't any errors
There is my code :
Action
export const api = ext => `http://localhost:8080/${ext}`;
//
// ─── ACTION TYPES ───────────────────────────────────────────────────────────────
//
export const GET_ADVERTS = "GET_ADVERTS";
export const GET_ADVERT = "GET_ADVERT";
//
// ─── ACTION CREATORS ────────────────────────────────────────────────────────────
//
export function getAdverts() {
return dispatch => {
fetch("adverts")
.then(res => res.json())
.then(payload => {
dispatch({ type: GET_ADVERTS, payload });
});
};
}
export function getAdvert(id) {
return dispatch => {
fetch(`adverts/${id}`)
.then(res => res.json())
.then(payload => {
dispatch({ type: GET_ADVERT, payload });
});
};
}
reducer
import { combineReducers } from "redux";
import { GET_ADVERTS, GET_ADVERT } from "../actions/actions";
const INITIAL_STATE = {
adverts: [],
advert: {}
};
function todos(state = INITIAL_STATE, action) {
switch (action.type) {
case GET_ADVERTS:
return { ...state, adverts: action.payload };
case GET_ADVERT:
return { advert: action.payload };
default:
return state;
}
}
const todoApp = combineReducers({
todos
});
export default todoApp;
index.js
//imports
const store = createStore(todoApp, applyMiddleware(thunk));
ReactDOM.render(
<Provider store={store}>
<BrowserRouter>
<App />
</BrowserRouter>
</Provider>,
document.getElementById("app")
);
My advertlist page :
//imports..
class Adverts extends Component {
componentDidMount() {
this.props.getAdverts();
}
render() {
const { adverts = [] } = this.props;
return (
<div>
<Header />
<h1>Adverts</h1>
{adverts.map(advert => (
<li key={advert._id}>
<a href={"adverts/" + advert._id}>
{advert.name} {advert.surname}
</a>
</li>
))}
<Footer />
</div>
);
}
}
const mapStateToProps = state => ({
adverts: state.adverts
});
export default connect(
mapStateToProps,
{ getAdverts }
)(Adverts);
I think your problem is here:
function mapStateToProps(state) {
return {
**adverts: state.adverts**
};
}
It should work if you change state.adverts to state.todos.adverts:
function mapStateToProps(state) {
return {
adverts: state.todos.adverts
};
}
Because your reducer is called todos, and it has state { adverts }, that's why you cannot access adverts even tho they are obtained.
You can check out working version here: https://codesandbox.io/s/olqxm4mkpq
The problem is, when you just create a store with one reducer without using combine reducer, it is possible to refer it directly in the ContainerS, like this:
const mapStateToProps = state => {
return{
*name of var*: state.adverts /*direct refers to adverts*/
}
}
But, when it use combined-reducer , it has to refer to an exact reducer that you want to use.like this :
const mapStateToProps = state => {
return{
*name of var* : state.todos.adverts (indirect refers to adverts from combined-reducer todos)
}
}
I am implementing a project where the data going to be shared in different components. So I decided to use redux-react for state management.
I used redux react async api call to get data from api. However I got undefined when the component mounted for the first time and returned actual data.
However, when I tried to implement some function on returned data, I got this error:
"Cannot read property of undefined"
I can see the state in redux developer tools and it has data and the logs function display action correctly. I can not understand why I am getting undefined. Here is my code:
const initialState = {
candidate: {},
companies: [],
offers: [],
moreStatehere:...
}
Reducer for the candidate
export default function profileReducer(state = initialState, action) {
switch(action.type) {
case FETCH_POSTS_FAILURE:
return Object.assign({}, state, {
didInvalidate: true
})
case REQUEST_PROFILE:
return Object.assign({}, state, {
isFetching: true,
didInvalidate: false
})
case RECEIVE_PROFILE:
return {
...state,
candidate: action.data
}
default:
return state;
}
}
root reducer
const rootReducer = combineReducers({
profiles: profileReducer
})
export default rootReducer;
create store
const composeEnhanser = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__||compose;
const loggerMiddleware = createLogger()
export default function configureStore() {
return createStore(
rootReducer,
composeEnhanser(applyMiddleware(thunkMiddleware,
loggerMiddleware))
);
}
index.js
const store = configureStore();
const app = (
<Provider store= {store}>
<BrowserRouter>
<App/>
</BrowserRouter>
</Provider>
)
ReactDOM.render(app, document.getElementById('root'));
registerServiceWorker();
action creator/api call
export function feachProfiles() {
return function (dispatch) {
dispatch(requestProfile)
return fetch(API_URL)
.then(
response => response.json(),
error => console.log('An error occurred.', error)
)
.then(json =>
dispatch(receiveProfile(json))
)
}
}
componentuse
class CandidatesList extends Component {
constructor (props){
super (props)
}
componentWillMount() {
this.props.feachProfiles();
}
handleClick() {
}
componentWillUnmount() {
}
render() {
const candidate = this.props.profiles.map(profile=>(
<div> </div>
));
return (
<div>
<ViewCandidate
/>
</div>
);
}
}
const mapStateToProps = state => {
return {
profiles: state.profiles.candidate || []
}
}
const mapDispatchToProps = (dispatch) => {
return {
feachProfiles: bindActionCreators(feachProfiles, dispatch)
}
}
export default connect(mapStateToProps, mapDispatchToProps)(CandidatesList);
action RECEIVE_PROFILE #
redux-logger.js:1 prev state {profiles: {…}}
redux-logger.js:1 action {type: "RECEIVE_PROFILE", data: {…}}
redux-logger.js:1 next state {profiles: {…}}
make sure to write this just before map function
if (this.props.profiles.length === 0) return null;
this.props.profiles should have array length of greater than 0
const candidate = this.props.profiles.map(profile=>(
<div> </div>
));