I have this error:
TypeError: Cannot read property 'map' of undefined
And don't know how can this.props.movies be undefined while I have initialState declared as [] and then it must straightly go to render?
So there are files:
Reducer.js
import * as types from "./ActionTypes.js";
const initialState = { movies: [] };
export const reducer = (state = initialState, action) => {
console.log(state.movies);
switch (action.type) {
case types.GET_MOVIES:
return {
...state,
movies: action.value
};
default:
return state;
}
};
Movies.js
import React, { Component } from "react";
import { connect } from "react-redux";
import MovieItem from "./MovieItem";
const mapStateToProps = state => ({
movies: state.movies
});
class Movies extends Component {
render() {
let movieItems = this.props.movies.map(movie => {
return <MovieItem movie={movie} />;
});
return <div className="Movies">{movieItems}</div>;
}
}
export default connect(mapStateToProps, null)(Movies);
And even if I put if-statement like
if (this.props.movies){
...
}else return 1;
it never rerenders
It should be:
const mapStateToProps = state => ({
movies: state.reducer.movies
});
Because your initial state is a object:
initialState = { movies: [] };
And you are using combineReducers like this:
const rootReducer = combineReducers({ reducer: reducer });
Now all the initialState of that reducer will be accessible by state.reducer.propertyName.
Note: Instead of using reducer, better to use some intuitive name like moviesReducer. Later in future if you will add more reducers then it will help to identify the the data.
Related
I am trying to learn the react and for that I am trying to create a sample todo app. I have a python flask backend which servers as REST server and react as web server.
Everything works find an I am able to show todos and delete particular todo as well. However now I have started learning Redux, and that seems really confusing.
I am not sure how to make call to my rest server. Following just returns promise, not sure how to get the data, rather than promise.
store.js
import axios from 'axios'
import { createStore } from 'redux'
export const ADD_TODO = 'ADD_TODO'
let nextTodoId = 0
export const addTodo = text => ({
type: 'ADD_TODO',
id: nextTodoId++,
text
})
export const listTodo = todos => ({
type: 'LIST_TODO',
todos
})
const add_todo = (id, text) => {
return axios.post("http://localhost:5001/todos", {id:id, data:text})
.then(Response=>{
store.dispatch(addTodo(Response.data));
})
}
const fetch_data = () => {
return axios.get("http://localhost:5001/todos")
.then(Response=>{
store.dispatch(listTodo(Response.data))
})
}
const initialState ={
todos: {},
new_todo: ''
}
function todoApp(state = initialState, action) {
console.log("reducer called...")
switch (action.type) {
case ADD_TODO:
return Object.assign({}, state, {
new_todo: action.text
})
default:
return state
}
}
const store = createStore(todoApp)
export default store
app.js
import React, {Component} from 'react'
import {connect} from 'react-redux'
class App extends Component{
render(){
return(
<div>
<button onClick={this.props.addTodo('testing')}>fetch_Data</button>
</div>
);
}
}
export default connect() (App)
index.js
ReactDOM.render(<Provider store={store}> <App /> </Provider>,
document.getElementById('root'));
Firstly, you should export the actions you have created which will then be imported and used in the components using the connect HOC.
You can dispatch the 'fetch_data' action to get the data in your component. Also, you can dispatch 'addTodo' action to add new todo in the list.
export const ADD_TODO = 'ADD_TODO';
export const GET_TODO = 'GET_TODO';
export const fetch_data = () => {
return (dispatch) => axios.get("http://localhost:5001/todos")
.then(response => {
dispatch({type: GET_TODO, todos: response.data});
})
}
export const addTodo = text => ({
type: 'ADD_TODO',
id: nextTodoId++,
text: text
});
Use the actions constants like ADD_TODO, GET_TODO to save or to update the redux state in reducers
const todoApp = (state = initialState, action) => {
console.log("reducer called...")
switch (action.type) {
case ADD_TODO:
const todos = {...state.todos};
todos[action.id] = action.text;
return Object.assign({}, state, {
todos: todos
});
case GET_TODO:
return Object.assign({}, state, {
todos: action.todos
});
default:
return state
}
}
Importing the actions and then call the function you have added in the 'mapDispatchToProps' to dispatch the actions.
import React, {Component} from 'react'
import {connect} from 'react-redux';
import { addTodo, fetch_data } from "../store";
class App extends Component{
render(){
return(
<div>
<button onClick={this.props.addTodo(todoId, 'testing')}>fetch_Data</button>
</div>
);
}
}
const mapStateToProps = (state) => ({
todos: state.todoApp.todos
});
const mapDispatchToProps = (dispatch) => ({
addTodo: (id, text) => dispatch(addTodo(id, text)),
fetch_data: () => dispatch(fetch_data())
});
export default connect(mapStateToProps, mapDispatchToProps)(App);
redux is based on actions and reducers, basically reducers are pure functions which means no side effects as for example api calls, I'd advice you read more about redux and how to use redux with redux-chunk for making api calls
You make this work like this. You need to dispatch action when you have response.
const fetch_data = () => {
return axios.get("http://localhost:5001/todos")
.then(Response=>{
store.dispatch(addTodo(Response.data));
})
}
export const addTodo = text => ({
type: 'ADD_TODO',
id: nextTodoId++,
text: text
})
I am trying to pass the state to as props through mapStateToProps(), and even though the state change is being reflected in the state tree (redux tool inspector) and the 'data' being passed down as the action payload is successfully logging in the reducer.js, the state is not being passed down as props in my container.
my reducer.js:
import { fromJS } from 'immutable'
import { GOT_INDUSTRY_DATA } from './constants.js';
const initialState = fromJS({
data: []
})
function industryDataReducer(state = initialState, action){
switch (action.type) {
case GOT_INDUSTRY_DATA: {
const { data } = action
console.log('reducer data',data)
return state.set('data', fromJS(data))
}
default:
return state
}
}
export default industryDataReducer
in my container:
export class IndustryPage extends React.PureComponent {
render(){
const { data } = this.props
console.log(data)
return{
...
}
}
}
function mapStateToProps({data}) {
return {
data
}
}
const withConnect = connect(mapStateToProps, mapDispatchToProps)
const withSaga = injectSaga({ key: 'industry', saga })
const withReducer = injectReducer({ key: 'industry', reducer })
export default compose (
withStyles(styles),
withSaga,
withReducer,
withConnect
)(IndustryPage);
After combining two reducers together (EditButton and TodoApp), my app everytime start crash. Before it, when I just use only one reducer TodoApp I did not have any problem with reducers. But now I cannot figure out what is wrong, because every time I get the error in map function of component below . Error "TypeError: Cannot read property 'map' of undefined".
So, what is I forgot? Also I cannot get the state in nested components or containers of App. It's strange too, but in App I can do that by console.log() for example.
/* REDUCERS */
import { combineReducers } from 'redux'
import { ADD_TODO, EDIT_TODO, DELETE_TODO, FILTER_TODO_UP, FILTER_TODO_DOWN } from '../Variables/Variables'
const initialState = {
todos: []
}
function EditButton(state, action) {
if (typeof state === 'undefined') {
return 'Edit';
}
switch (action.type) {
case EDIT_TODO:
return state = "Edit" ? "Done" : "Edit"
default:
return state
}
}
function TodoApp(state, action) {
if (typeof state === 'undefined') {
return initialState;
}
switch (action.type) {
case ADD_TODO:
return Object.assign({}, state, {
todos: [
...state.todos,
{
id: action.id,
text: action.text,
done: action.done
}
]
});
case EDIT_TODO:
return Object.assign({}, state, {
todos: [
...state.todos,
{
id: action.id,
text: action.text,
done: action.done
}
]
});
case DELETE_TODO:
return Object.assign({}, {
todos: state.todos.filter(todos => todos.id !== parseInt(action.id))
});
case FILTER_TODO_UP:
return Object.assign({}, {
todos: [
...state.todos.sort((a, b) => b.id - a.id)
]
});
case FILTER_TODO_DOWN:
return Object.assign({}, {
todos: [
...state.todos.sort((a, b) => a.id - b.id)
]
});
default:
return state;
}
}
export default combineReducers({TodoApp, EditButton})
/* APP */
import React, { Fragment } from 'react';
import TodoFormAdd from '../Containers/TodoFormAdd';
import TodoListAdd from '../Containers/TodoListAdd';
import TodoFormFilterAdd from '../Containers/TodoFormFilterAdd';
class App extends React.Component {
constructor(props) {
super(props);
}
render() {
return(
<Fragment>
// console.log(this.props.state.getState()) - work!
<TodoFormAdd />
<TodoListAdd store={this.props.store} />
<TodoFormFilterAdd />
</Fragment>
);
}
}
export default App;
/* CONTAINER */
import { connect } from 'react-redux';
import TodoList from '../Components/TodoList/TodoList';
import { DeleteTodo } from '../Actions/AddTodo'
// console.log(this.props.state.getState()) - does not work!
const mapStateToProps = state => ({
todos: state.todos
});
const mapDispatchToProps = dispatch => ({
todoFormDelete: todo => dispatch(DeleteTodo(todo))
});
export default connect(
mapStateToProps,
mapDispatchToProps)(TodoList)
/* COMPONENT */
import React from 'react';
import TodoIteam from '../TodoIteam/TodoIteam'
class TodoList extends React.Component {
handleDelete = (e) => {
let target = e.target;
let closestDelete = target.closest('span');
let closestEdit = target.closest('button');
if (closestDelete) {
let index = closestDelete.parentNode.getAttribute('index');
this.props.todoFormDelete(index);
} else {
return
}
}
render(props) {
// console.log(this.props.state.getState()) - does not work!
return (
<ul onClick={this.handleDelete}>{this.props.todos.map((iteam, index) =>
// this where I get an error
<TodoIteam key={index} index={iteam.id} {...iteam} />
)}
</ul>
);
}
}
export default TodoList;
As you are using ES6 property shorthand notation in combineReducers :
combineReducers({TodoApp, EditButton})
This is equivalent to writing combineReducers({ TodoApp: TodoApp, EditButton: EditButton })
But inside your CONTAINER you are accessing state.todos there is nothing called todos coming from state instead its TodoApp and Hence you get error in your .map():
this.props.todos.map((iteam, index) {}
EDIT :
As you are returning an object containing an array from your reducers called todos so to access correct state you need to use reducer Name followed by an array name you are returning which would be TodoApp.todos
So inside your Container you need to access correct reducer
const mapStateToProps = state => ({
todos: state.TodoApp.todos // Notice TodoApp is used instead of todos
});
You can read more about combineReducers on Redux Documentation
I am learning react-redux, so I decided to implement what I have been learning. But I am have a bug challenge. So I console.logged this.props.users from mapStateToProps function.
I believe there's something I not doing right which I don't understand. Please an explanation in other to move on. Thanks you so much for helping out.
Here is my code.
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { fetchUsers } from '../actions/userAction';
import UserList from '../components/UserList';
class UserPage extends Component {
constructor(props) {
super(props);
}
componentWillMount() {
this.props.fetchUsers();
}
componentDidMount() {
console.log(this.props.users);
}
render() {
return (
<div>
<h2>Users Page</h2>
<UserList users={this.props.users} />
</div>
);
}
}
const mapStateToProps = state => {
return {
users: state.userReducer.users
};
};
const mapDispatchToProps = dispatch => {
return {
fetchUsers: () => dispatch(fetchUsers())
};
};
export default connect(mapStateToProps, mapDispatchToProps)(UserPage);
So this is what I get from the chrome console - Empty arrays.
props showing empty arrays
But when I check the React DevTool and Redux DevTool, they display the expected Props and States respectively. Below are the snapshot of the dev tools
React devtool shows the correct Props
Redux devtool show the correct States and Actions
userAction.js
import axios from 'axios';
import * as types from './actionTypes';
export let fetchingUser = () => {
return {
type: types.FETCHING_USERS
};
};
export let fetchedUser = payload => {
return {
type: types.FETCHED_USER,
payload
};
};
export let fetchUser_error = () => {
return {
type: types.FETCH_USER_ERROR
};
};
export let fetchUsers = () => {
let url = 'https://eventcity.herokuapp.com/api/v1/users';
return dispatch => {
dispatch(fetchingUser());
return axios
.get(url)
.then(response => {
const users = response.data.data;
dispatch(fetchedUser(users));
})
.catch(err => {
dispatch(fetchUser_error());
});
};
};
userReducer.js
import * as types from '../actions/actionTypes';
import initialState from './initialState';
const userReducer = (state = initialState, action = {}) => {
switch (action.type) {
case types.FETCHING_USERS:
return { ...state, users: [], error: null, loading: true };
case types.FETCHED_USER:
return { ...state, users: action.payload, error: null, loading: false };
case types.FETCH_USER_ERROR:
return {
...state,
users: [],
error: { message: 'Error loading data from the API' },
loading: false
};
default:
return state;
}
};
export default userReducer;
configureStore.js
import { createStore, applyMiddleware } from 'redux';
import thunk from 'redux-thunk';
import { composeWithDevTools } from 'redux-devtools-extension';
import rootReducer from '../reducer/rootReducer';
const configureStore = () => {
return createStore(rootReducer, composeWithDevTools(applyMiddleware(thunk)));
};
export default configureStore;
rootReducer.js
import { combineReducers } from 'redux';
import userReducer from './userReducer';
const rootReducer = combineReducers({
userReducer
});
export default rootReducer;
I think you might want to check this
https://github.com/reactjs/react-redux/issues/129. Your problem is using componentDidMount and componentWillMount without having a better understanding of what they are used for.
The problem is not with redux, all you need to understand is that your fetchUsers request is async and componentDidMount function is only executed once after the component has rendered and it may so happen that the data is not present by the time componentDidMount function is executed and hence your console.log(this.props.users); return empty array, Log it in the render method and you will see the correct data
class UserPage extends Component {
constructor(props) {
super(props);
}
componentWillMount() {
this.props.fetchUsers();
}
render() {
console.log(this.props.users);
return (
<div>
<h2>Users Page</h2>
<UserList users={this.props.users} />
</div>
);
}
}
I've just started implementing Redux in a React application, and it's the first time i try to, so please bear with me.
My problem is that i can't access the data in my component this this.props.questions
I have a simple action which is supposed to async fetch some data
export function fetchQuestions(url) {
const request = axios.get('url');
return (dispatch) => {
request.then(({data}) => {
dispatch({ type: 'FETCH_QUESTIONS', payload: data });
console.log(data);
});
};
}
Which is picked up my reducer questions_reducer
export default function(state = [], action) {
switch(action.type) {
case 'FETCH_QUESTIONS':
console.log('Getting here');
return state.concat([action.payload.data]);
console.log('But not here');
}
return state;
}
My index reducer looks like this:
import { combineReducers } from 'redux';
import fetchQuestions from './question_reducer';
const rootReducer = combineReducers({
questions: fetchQuestions
});
export default rootReducer;
I pass it to my store where i apply the thunk middleware and finally into <Provider store={store}> which wraps my app, but the prop just returns undefined in my React component
configureStore:
import { createStore, applyMiddleware } from 'redux';
import thunk from 'redux-thunk';
import rootReducer from '../reducers';
export default function configureStore(initialState) {
return createStore(
rootReducer,
initialState,
applyMiddleware(thunk)
);
}
I don't know if the console.log is to be trusted but it logs from my questions_reducer before the data is returned from the dispatch in my action
EDIT (Component)
class QuestionsRoute extends Component {
constructor(props) {
super(props);
this.state = {
};
}
componentDidMount() {
this.props.fetch('someUrl);
setTimeout(function(){ console.log(this.props.questions) },
1500);
}
render() {
{console.log(this.props.questions)}
return (
<div>
<1>Hello</1>
{this.props.questions !== undefined ?
<p>We like props</p>: <p>or not</p>
}
</div>
);
}
};
const mapStateToProps = (state) => {
return {
questions: state.questions,
};
};
const mapDispatchToProps = (dispatch) => {
return {
fetch: () => dispatch(fetchQuestions())
};
};
export default connect(mapStateToProps, mapDispatchToProps)(QuestionsRoute);
In your reducer
export default function(state = [], action) {
switch(action.type) {
case 'FETCH_QUESTIONS':
return state.concat([action.payload.data]);
}
return state;
}
You should probably instead have return state.concat([action.payload]);
Since from dispatch({ type: 'FETCH_QUESTIONS', payload: data }); we see that payload is data, it doesn't contain it.
Update: I'd recommend setting up redux-devtools / redux-devtools-extension / react-native-debugger so you can visually see your actions and store state live - makes things like this a lot easier to debug!