Can't use map function after Dispatch Action, React + Redux - javascript

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)

Related

How do I reference a variable in redux store from a component index.js (react.js)

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

Dispatch is not a function in reducer

MY action
const fetchDataApi = (getState) => {
let { data } = getState()
return axios.get('http://api.openweathermap.org/data/2.5/weather?q=london,uk&appid=26aacf43db7ecfa2ecd85500eaee9920').then(thunkdata => {
console.log(thunkdata)
return {
[data]: thunkdata
}
})
}
const fetchgetDataCall = () => {
return (dispatch, getState) => {
return dispatch(fetchDataApi(getState))
}
}
export const getData = (dispatch) => {
dispatch(fetchgetDataCall())
return {
type: actionTypes.GETDATA,
}
}
In action.js i want to get data from my whether api and store in data, so i am using getstate to get data variable and assign data to it
My calender Component where i am connecting my callender to actionType
import React, { Component } from 'react';
// import 'moment/locale/it.js';
import { DatePicker, DatePickerInput } from 'rc-datepicker';
// import { ca } from 'date-fns/esm/locale';
import 'rc-datepicker/lib/style.css';
import { connect } from 'react-redux';
import { getData } from '../store/actions/actions'
const date = '2015-06-26' // or Date or Moment.js
class Callender extends Component {
//These is a method es7
onChangeandler = (jsDate, dateString, event) => {
// event.preventDefault()
console.log("[we are lokking at js date]",jsDate);
this.props.getWether();
console.log("[we are seeing the props storeDta]",this.props.storeData);
}
//Next method
render() {
return (
<div>
<DatePicker onChange={this.onChangeandler} value={date} />
</div>
)
}
}
const mapStateToProps = state =>({
storeData: state.data
})
const mapDispatchToProps = (dispatch) =>({
getWether: () => dispatch(getData())
})
export default connect(mapStateToProps,mapDispatchToProps)(Callender)
My reducer
import * as actionType from '../actions/actionTypes';
const intialState ={
time:null,
day:null,
data:null
}
// reducer
const reducer = (state=intialState, action) =>{
switch(action.type){
case actionType.GETDATA:
return {
...state,
data:action.data
}
case actionType.POSTDATA:
return {
...state
}
default :
return {
...state
}
}
}
export default reducer;
actionTypes.js
export const POSTDATA="POSTDATA";
export const GETDATA = "GETDATA";
1)I am calling my action creator in callender.js file
2) Where i am using thunk middleware to get data ,and store in data variable from redux store
3)I can't find the issue please help me
Your actions looks quite weird. The getData action creator disptaches fetchgetDataCall which dispatches fetchDataApi and that returns just some object { [data]: thunkdata} where property data are probably null in that moment. Hence there are not any properties type or data in your action object.
The second thing what your getData do is returning the object {type: actionTypes.GETDATA}, hence there is not any property data in your action object.
Try to do it something like this (updated according to #mbojko answer):
const getData = () => {
return (dispatch) => {
return axios.get('http://api.openweathermap.org/data/2.5/weather?q=london,uk&appid=26aacf43db7ecfa2ecd85500eaee9920').then(thunkdata => {
return dispatch({
type: actionTypes.GETDATA,
data: thunkdata
})
})
}
}
Compare your function signature
export const getData = (dispatch) => {
With how you call it:
const mapDispatchToProps = (dispatch) =>({
getWether: () => dispatch(getData())
})
The argument is missing (therefore dispatch is not defined and obviously not a function).
Should be dispatch(getData(dispatch)), probably.

React redux do not sync props in mapStateToProps

I am using React redux with firebase realtime database.
In App.js I am dispatching an action fetchAllPosts
App.js
class App extends Component {
componentDidMount() {
this.props.fetchAllPosts();
}
render() {
return (
<div className="App">
// something ...
</div>
);
}
}
const mapDispatchToProps = dispatch => {
return {
fetchAllPosts: () => {dispatch(allPosts())}
}
}
My action looks like this (I am using redux-thunk):
action
export function allPosts() {
return (dispatch) => {
firebase.database().ref('posts/').on('value', (snapshot) => {
dispatch({type: "ALL_POSTS", postsArray: snapshot.val(), loading: false})
})
}
}
Then I am combining reducers (I know in this case it is not necessary):
const rootReducer = combineReducers({
allPosts: postsReducer
})
My reducer looks like this:
reducer
const initialState = {
allPosts: []
}
const postsReducer = (state = initialState, action) => {
switch(action.type) {
case "ALL_POSTS" :
console.log("action payload all posts", action.postsArray)
return {
...state,
loading: false,
allPosts: action.postsArray
}
break;
default:
return state
}
return state
}
And finally: my SinglePostview component looks like this:
SinglePostview.js
import React, {Component} from 'react';
import {connect} from 'react-redux';
class SinglePostview extends Component {
render() {
console.log("ppp", this.props)
return (
<h2>{this.props.post.title}</h2>
)
}
}
const mapStateToProps = (state, ownprops) => {
const postId = ownprops.match.params.postid
return {
post: state.allPosts.allPosts[postId]
}
}
export default connect(mapStateToProps)(SinglePostview);
Here when the render method is executing, this.props.post is undefined and I have the error:
TypeError: Cannot read property 'title' of undefined.
The problem is: when the app loads for the first time, props.post is undefined (so I have an error) and after about 1 second it receives the value but it doesn't change anything - the error still exists and the value is not displaying.
Could anyone help me?
Assuming your reducer is fine, you can fix this by
changing this
render() {
return (
<h2>{this.props.post.title}</h2>
)
}
To this:
render() {
if (!this.props.post){
return null;
}
return (
<h2>{this.props.post.title}</h2>
)
}
or
render() {
return (
<h2>{this.props.post && this.props.post.title}</h2>
)
}
You are defining allPosts to be an array
const initialState = {
allPosts: []
}
But you are trying to access it like an object.
state.allPosts.allPosts[postId]
Hence, if your state.allPosts.allPosts is an array , try using the ES6 find() method to get a post from the array with the postId.
Assuming
state.allPosts.allPosts = [
{postId: 1,title:'abcd'},
{postId:2,title:'def'}
]
state.allPosts.allPosts.find(post => postId === post.postId)

Implementing Redux in React using the create-react-app -Simple question

Is is correct to pass a reducer as props when i'm using a rootreducer ?
This is my rootReducer.js :
import { combineReducers } from 'redux';
import simpleReducer from './simpleReducer';
import messageReducer from './messageReducer';
import NewReducer from './NewReducer';
export default combineReducers({
simpleReducer,messageReducer,NewReducer
});
And this is one of my action creators addMessage.js
export const addMessage = (message) => dispatch => {
dispatch({
type: 'ADD',
message: message
})
}
Here is the first reducer messageReducer.js
export default (state = [], action) => {
switch (action.type) {
case 'ADD':
return [
...state,
action.message
];
default:
return state;
}
};
And here is another one simpleReducer.js
export default (state = {}, action) => {
switch (action.type) {
case 'SIMPLE_ACTION':
return {
result: action.payload
}
default:
return state
}
}
And finally here is my last reducer NewReducer.js
export default (state = '', action) => {
switch (action.type) {
case 'AnyThing':
return action.WhatToDisplay;
default:
return state;
}
};
Here is my mapping in the App.js
const mapStateToProps = state => ({
...state
})
const mapDispatchToProps = dispatch => ({
simpleAction: () => dispatch(simpleAction()),
submitNewMessage: (message) => {
dispatch(addMessage(message))
},
NewAction: () => dispatch(NewAction())
})
And here is my ِApp Component.Notice my last 2 h2 tags as well as my ul tag .Without me adding the reducer at the end of the prop , it doesn't work.So
is what i'm doing right ? or is there another way to show the redux state in
my react ?.Note that i currently have no errors and the code functions well.I
just wana know if what i am doing is right or wrong and if there is a better
syntax to show the redux state in my create react app.
class App extends Component {
constructor(props) {
super(props);
this.state = {
input: ''
}
this.handleChange = this.handleChange.bind(this);
this.submitMessage = this.submitMessage.bind(this);
}
handleChange(event) {
this.setState({
input: event.target.value
});
}
submitMessage() {
this.props.submitNewMessage(this.state.input);
this.setState({
input: ''
});
}
simpleAction = (event) => {
this.props.simpleAction();
}
localNormalFunction=(event)=>{
this.props.NewAction()
}
render() {
return (
<div >
<h1>fjasgdasdsg</h1>
<button onClick={this.simpleAction}>Test redux action</button>
<pre>
{
JSON.stringify(this.props)
}
</pre>
<h2>Type in a new Message:</h2>
<input
value={this.state.input}
onChange={this.handleChange}/><br/>
<button onClick={this.submitMessage}>Submit</button>
<ul>
{this.props.messageReducer.map( (message,idx) => {
return (
<li key={idx}>{message}</li>
)
})
}
</ul><br/><br/>
<button onClick={this.localNormalFunction}>dsadsdsa</button>
<h2>{this.props.NewReducer}</h2>
<h2>{this.props.simpleReducer.result}</h2>
</div>
);
}
}
It is better practice to get only the props you need from redux in each component. If you pass the whole redux state in mapStateToProps then whenever anything in redux changes you will have everything rerendering even if nothing you use changed.
One common reason you might be getting errors is that you are trying to use the props in render and they get instantiated afterwards.
Try this give default values to the props if you can't get them from redux:
App.defaultProps = {
result: '',
NewReducer: '',
messageReducer: []
}
const mapStateToProps = state => ({
result: state.simpleReducer.result,
NewReducer: state.NewReducer,
messageReducer: state.messageReducer
})
and then change this.props.simpleReducer.result to this.props.result

Can't display data using combine reducer REACT

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

Categories