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
Related
I am trying to add user metadata to my store when mounting a screen. However, when I send the action to the reducer, the store is not modified.
I would expect props after sending the action to be as follows:
{addUserMetaData: ƒ addUserMetaData(user_object),
user: {firestore_doc: {name: "Joe"}}
}
What am i missing here?
To reproduce, react-native-init mwe then add the following code. I've added an image of the app logs below.
App.js
import React, { Component} from 'react';
import { View } from 'react-native';
import Screen from './src/screen';
import { createStore } from 'redux';
import { Provider } from 'react-redux';
const userReducer = function userReducer(state = {}, action) {
console.log('action', action);
switch (action.type) {
case "ADD_USER_METADATA":
return { ...state, firestore_doc: action.payload };
default:
return { ...state };
}
};
const store = createStore(userReducer);
export default class App extends Component {
render() {
return (
<Provider store={store}>
<View>
<Screen />
</View>
</Provider>
);
}
};
src/screen.js
import React, { Component } from 'react';
import { Text, View } from 'react-native';
import { connect } from 'react-redux';
const addUserMetaData = (user) => ({
type: "ADD_USER_METADATA",
payload: user
})
class Screen extends Component {
constructor(props) {
super(props);
}
componentDidMount() {
const user = { name: "Joe" };
console.log('props', this.props);
this.props.dispatch(addUserMetaData(user));
console.log('props after action', this.props);
}
render() {
return (
<View>
<Text>Welcome to react native</Text>
</View>
)
}
}
const mapStateToProps = state => {
return { user: state };
};
export default connect(mapStateToProps)(Screen);
Fixed https://snack.expo.io/#janithar/c3RhY2
Lines I changed
return { ...state, firestore_doc: action.payload };
Please added state.firestore_doc instead of state because in reducer action.payload assign the data in firestore_doc state so you are not getting data from state.user
const mapStateToProps = state => {
return { user: state.firestore_doc };
};
I'm trying to edit an object and replace it in array using React and Redux like this:
case EDIT_LANGUAGE:
let languages = [...state.languageSkills];
languages[languages.findIndex(el => el.id === action.payload.id)] = action.payload;
return {
...state,
languageSkills: languages
};
'languages' array looks find before return statement, but state is not re-rendered. I guess I'm mutating state somehow. Other actions (delete, get, set) are working fine. Any tips?
EDIT. This is relevant part of the component that should render
import { setLanguages, getLanguages } from '../../actions';
import {connect} from 'react-redux';
import {bindActionCreators} from "redux"
import React, { Component } from 'react';
class UserProfile extends Component {
constructor(props) {
super(props);
}
render() {
const languageSkillItems = this.props.languageSkills.map((languageSkill) => {
return (
<LanguageSkillItem key={languageSkill.id} item={languageSkill} />
)
});
return (
<div className="profile">
<Language languageSkillItems={languageSkillItems} />
</div>
)
}
}
const mapStateToProps = (state) => {
return {
languageSkills: state.languageSkills
};
};
const mapDispatchToProps = dispatch => {
return {
...bindActionCreators({ setLanguages, getLanguages }, dispatch)
}
};
export default connect(
mapStateToProps,
mapDispatchToProps
)(UserProfile
);
You need to create a new array reference, easiest way is just to use map, like so:
case EDIT_LANGUAGE:
const languageSkills = state.languageSkills.map(el => {
if(el.id === action.payload.id) {
return action.payload;
}
return el;
});
return {
...state,
languageSkills
};
The action dispatch is not working, The function works and I get the console.log but the store isn't changing. Any ideas?
import React from 'react';
import { connect } from 'react-redux';
import { NavLink } from 'react-router-dom';
import RemoveTodo from './RemoveTodo';
import { remove } from '../actions/Todo';
import { store } from '../app';
class TodosSummary extends React.Component {
constructor(props) {
super(props);
}
onDelete = ({id}) => {
store.dispatch(remove({id}))
console.log(store.getState());
};
render () {
return (
<ul>
{this.props.target.map(({todo, significance, id}) => {
return (
<li
key={id}>{todo} - impact is {significance}
<button onClick={this.onDelete}>Remove</button>
</li>
);
})}
</ul>
</div>
);
}
};
const mapStateToProps = (state) => {
return {
target: state.target
}; };
export default connect(mapStateToProps)(TodosSummary)
This is the action, taking the todo id
export const remove = ({id}) => ({
type: 'REMOVE_TODO',
id
});
And that's the reducer, filtering the state and bringing back the filtered array
const todosReducer = (state = todosReducerDefaultState, action) => {
switch(action.type) {
case 'ADD_TODO':
return [
...state,
action.target
];
case 'REMOVE_TODO':
return (
state.filter(({ id }) => id !== action.id)
);
I see you want to access target property inside state. So, the reducer should be like this:
case 'ADD_TODO':
return {
...state,
target: [...state.target, action.target]
};
case 'REMOVE_TODO':
return {
...state,
target: state.target.filter(({ id }) => id !== action.id)
};
See if this works.
Return updated state like this.
case 'REMOVE_TODO':
return [...state.filter(({ id }) => id !== action.id)];
It's not suggested to change the state directly, please change the addToDo to the following.
Object.assign({}, state, {
todoList: state.target
});
Can you please provide the code snippet for the default state as per your code 'todosReducerDefaultState'?
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.
I am trying to show message 'hello brozz..' from the reducer to a component render() method.ComponentDidMount() get the this.props.message as please see the image. And my reducer is like
export const geod = (state ={}, action) => {
console.log('inside reducer');
switch(action.type){
case 'INITIAL_MESSAGE':
return [
...state, {
message:'hello brozz..'
}
]
case 'CLICK_MESSAGE':
return [
...state, {
message: ''
}
]
default:
return state;
}
};
But I can not use this.props.message as <div><div>{this.props.message}</div></div> inside the render method. I am getting the error
Objects are not valid as a React child (found: object with keys {}). If you meant to render a collection of children, use an array instead or wrap the object using createFragment(object) from the React add-ons
My component is,
import React, { Component } from 'react';
import logo from './logo.svg';
import './App.css';
import { initialMessageAction, clickButtonAction } from './redux.js';
import { connect } from 'react-redux';
import { Message } from './messageComponent';
class App extends Component {
componentWillMount() {
console.log('component will mount');
let data = this.props.initialMessageAction();
}
componentDidUpdate() {
console.log('comp Did Update')
console.log(this.props.message)
}
render() {
return (
<div className="App">
<div className="App-header">
</div>
<p className="App-intro">
react-redux-data-flow
</p>
<div>{this.props.message}</div>
<div>{this.props? <div>hi ..<button onClick={ () => this.props.clickButtonAction() }>hide message</button></div>: ''}</div>
</div>
);
}
}
const mapStateToProps = (state, ownProperty) => ({
message:state.geod,
});
const mapDispatchToProps = {
initialMessageAction,
clickButtonAction
}
export default connect(
mapStateToProps,
mapDispatchToProps
)(App);
const initialState = {message: null};
export const geod = (state = initialState, action) => {
console.log('inside reducer');
switch(action.type){
case 'INITIAL_MESSAGE':
return Object.assign({}, state, {message:'hello brozz..'});
case 'CLICK_MESSAGE':
return Object.assign({}, state, {message: ''});
default:
return state;
}
};
And in stateMap { message: state.geod.message }.
As initial state You use an empty object and you see it in error.