Why doesn't the table appear? - javascript

components/App.js
import React from 'react';
import { connect } from 'react-redux'
import { add_task } from '../actions/index';
class App extends React.Component {
state = {
text: '',
time: ''
}
render_tasks = () => {
const { tasks } = this.props
return (
<table>
<tbody>
{tasks.map(task => {
return (
<tr key={task.id}>
<td>{task.text}</td>
<td>{task.time}</td>
</tr>
)
})
}
</tbody>
</table>
)
}
render() {
console.log('App props', this.props)
return (
<div className="App">
<input type='text'
onChange={(e) => this.setState({
text: e.target.value
})} />
<input type='time'
onChange={(e) => this.setState({
time: e.target.value
})} />
<button
onClick={() => this.props.add_task(
this.state.text, this.state.time)}>
Add
</button>
{this.render_tasks()}
</div>
);
}
}
function mapStateToProps(state) {
return {
tasks: state
}
}
function mapDispatchToProps(dispatch) {
return {
add_task : () => dispatch(add_task())
}
}
export default connect(mapStateToProps, mapDispatchToProps)(App);
actions/index.js
import { ADD_TASK } from './../types';
export const add_task = (text, time) => {
const action = {
type: ADD_TASK,
text,
time
}
console.log('Add action', action)
return action
}
reducers/index.js
import { ADD_TASK } from './../types';
const tasks = (state=[], action) => {
let tasks = [];
if (action.type === ADD_TASK) {
tasks = [...state, {
text: action.text,
time: action.time,
id: Math.random()
}]
console.log('Data from reducer', tasks)
return tasks
} else {
return state
}
}
export default tasks;
When I click the button I expect to get a table with the information I entered in the input fields, but nothing appears, I tried replacing the table part in render_tasks function in App.js with an unordered list and the map returns a list item including 2 spans one for the text and the other for the time but all I got is the dot of the list item!

In
add_task : () => dispatch(add_task())
You don't pass any arguments to add_task().
You can explicitly define the arguments:
add_task : (text, time) => dispatch(add_task(text, time))

Related

Why changes from local state don't go into global state?

To-Do-List
When I try to edit my created task, I see some modifications, but only in local State. When I look at the data of the global state, nothing change, the data remains the same as after creating the tasks object.
It is also interesting to note that when case EDIT_TASK has worked , action.id = values from Input, and action.task = undefined
P.S: Put all the component code below, maybe there was a mistake somewhere.
P.S: Sorry for ENG
Component's code
import React from 'react'
import s from "./../../App.module.css";
class Item extends React.Component {
state = {
statusChange: false,
task: this.props.task
}
activeStatusChange = () => {
this.setState( {
statusChange: true
}
);
}
deActivateStatusChange = () => {
this.setState( {
statusChange: false
}
);
this.props.editTask(this.props.task)
}
onStatusChange = (e) => {
this.setState({
task: e.target.value
})
}
render(){
return (
<div className={s.item}>
<span onClick={this.props.editStatus} className={s.statusTask}>
{this.props.status ? <img src="https://img.icons8.com/doodle/48/000000/checkmark.png"/>
: <img src="https://img.icons8.com/emoji/48/000000/red-circle-emoji.png"/>}
</span>
{ this.state.statusChange
? <input onChange={this.onStatusChange} autoFocus={true} onBlur={this.deActivateStatusChange} value={this.state.task} />
: <span className={this.props.status === true ? s.task : s.taskFalse} onClick={this.activeStatusChange}> {this.state.task} </span>}
<span onClick={this.props.deleteTask} className={s.close}><img src="https://img.icons8.com/color/48/000000/close-window.png"/></span>
</div>
)
}
}
export default Item;
Reducer's code
import React from 'react'
import shortid from 'shortid';
const ADD_TASK = 'ADD_TASK'
const EDIT_TASK = 'EDIT_TASK'
const initialState = {
tasks: []
};
const mainReducer = (state = initialState, action) => {
switch (action.type) {
case ADD_TASK: {
return {
...state,
tasks: [{
id: shortid.generate(),
task: action.task,
status: false
}, ...state.tasks]
}
}
case EDIT_TASK: {
return {
...state,
tasks: state.tasks.filter((t) => t.id === action.id ? {...t, task: action.newTask} : t)
}
}
default:
return state
}
}
//window.store.getState().mainReducer.tasks
export const addTask = task => ({type: 'ADD_TASK', task});
export const editTask = (id,newTask) => ({type: 'EDIT_TASK', id, newTask})
export default mainReducer;
Parent's component:
import React from "react";
import s from "./../../App.module.css";
import CurrentTasks from "../current-tasks";
import FilterButtonTasks from "../filter-button-tasks";
import ListTasks from "../tasks-list";
class SetForm extends React.Component {
constructor(props) {
super(props);
this.state = {
text: ''
}
}
onInputChange = event => {
this.setState({
[event.target.name]: event.target.value
})
}
handleSubmit = event => {
event.preventDefault();
if(this.state.text === '') {
return undefined
}
this.props.addTask(this.state.text)
this.setState({
text: ''
})
}
filterTasks = (tasks, activeFilter) => {
switch (activeFilter) {
case 'done': {
return tasks.filter(task => task.status);
}
case 'active': {
return tasks.filter(task => !task.status)
}
default:
return tasks;
}
}
render() {
const currentTasks = this.filterTasks(this.props.tasks, this.props.filter);
return (
<div>
<form onSubmit={this.handleSubmit}>
<div>
<input name={"text"} onChange={this.onInputChange} value={this.state.text}placeholder={"Set your task"} className={s.setTask}/>
<button onClick={this.handleSubmit} className={s.add}>ADD</button>
<button onClick={this.props.removeAllTasks} className={s.clearAll}>Clear</button>
</div>
</form>
<CurrentTasks tasks={this.props.tasks}/>
<ListTasks currentTasks={currentTasks} editStatus={this.props.editStatus} deleteTask={this.props.deleteTask} editTask={this.props.editTask}/>
<FilterButtonTasks currentTasks={currentTasks} changeFilter={this.props.changeFilter} removeAllDone={this.props.removeAllDone}/>
</div>
)
}
}
export default SetForm;
one more:
import React from 'react'
import Item from './SetItem/item'
const ListTasks = ({currentTasks,editStatus,deleteTask,editTask}) => {
return (
currentTasks.map(t => (<Item editStatus={() => editStatus(t.id)}
deleteTask={() => deleteTask(t.id)}
key={t.id} task={t.task} status={t.status} editTask={editTask}/>))
)
}
export default ListTasks;
Since, you are only updating the local state onStatusChange the state does not get updated in global state. So on deActivateStatusChange you need to call this.props.editTask with updated state, that is this.state.task
deActivateStatusChange = () => {
this.setState({
statusChange: false
});
this.props.editTask(this.state.task); // change is here
};
The problem is in your EDIT_TASK reducer:
Change
state.tasks.filter((t) => t.id === action.id ? {...t, task: action.newTask} : t)
To
state.tasks.map((t) => t.id === action.id ? {...t, task: action.newTask} : t)
map will update the object, not filter
Code should be:
case EDIT_TASK: {
return {
...state,
tasks: state.tasks.map((t) => t.id === action.id ? {...t, task: action.newTask} : t)
}
}
Also it seems like you are not passing id and newTask to editTask action:
const ListTasks = ({ currentTasks, editStatus, deleteTask, editTask }) => {
return currentTasks.map(t => (
<Item
editStatus={() => editStatus(t.id)}
deleteTask={() => deleteTask(t.id)}
key={t.id}
task={t.task}
status={t.status}
editTask={(newTask) => editTask(t.id, newTask)} // change current code to this
/>
));
};

How to target specific element after mapping and passing onClick function as props

I am facing such problem, i got my array of records fetched from an API, mapped it into single elements and outputting them as single components. I have function which changes state of parent Component, passes value to child component and child component should hide/show div content after button is clicked.
Of course. It is working, but partially - my all divs are being hidden/shown. I have set specific key to each child component but it doesn't work.
App.js
import React, { Component } from 'react';
import './App.css';
import axios from 'axios';
import countries from '../../countriesList';
import CitySearchForm from './CitySearchForm/CitySearchForm';
import CityOutput from './CityOutput/CityOutput';
import ErrorMessage from './ErrorMessage/ErrorMessage';
class App extends Component {
state = {
country: '',
error: false,
cities: [],
infoMessage: '',
visible: false
}
getCities = (e) => {
e.preventDefault();
const countryName = e.target.elements.country.value.charAt(0).toUpperCase() + e.target.elements.country.value.slice(1);
const countryUrl = 'https://api.openaq.org/v1/countries';
const wikiUrl ='https://en.wikipedia.org/w/api.php?action=query&prop=extracts&exintro&explaintext&format=json&category=city&redirects&origin=*&titles=';
const allowedCountries = new RegExp(/spain|germany|poland|france/, 'i');
if (allowedCountries.test(countryName)) {
axios
.get(countryUrl)
.then( response => {
const country = response.data.results.find(el => el.name === countryName);
return axios.get(`https://api.openaq.org/v1/cities?country=${country.code}&order_by=count&sort=desc&limit=10`)
})
.then( response => {
const cities = response.data.results.map(record => {
return { name: record.city };
});
cities.forEach(city => {
axios
.get(wikiUrl + city.name)
.then( response => {
let id;
for (let key in response.data.query.pages) {
id = key;
}
const description = response.data.query.pages[id].extract;
this.setState(prevState => ({
cities: [...prevState.cities, {city: `${city.name}`, description}],
infoMessage: prevState.infoMessage = ''
}))
})
})
})
.catch(error => {
console.log('oopsie, something went wrong', error)
})
} else {
this.setState(prevState => ({
infoMessage: prevState.infoMessage = 'This is demo version of our application and is working only for Spain, Poland, Germany and France',
cities: [...prevState.cities = []]
}))
}
}
descriptionTogglerHandler = () => {
this.setState((prevState) => {
return { visible: !prevState.visible};
});
};
render () {
return (
<div className="App">
<ErrorMessage error={this.state.infoMessage}/>
<div className="form-wrapper">
<CitySearchForm getCities={this.getCities} getInformation={this.getInformation} countries={countries}/>
</div>
{this.state.cities.map(({ city, description }) => (
<CityOutput
key={city}
city={city}
description={description}
show={this.state.visible}
descriptionToggler={this.descriptionTogglerHandler} />
))}
</div>
);
}
}
export default App;
CityOutput.js
import React, { Component } from 'react';
import './CityOutput.css';
class CityOutput extends Component {
render() {
const { city, descriptionToggler, description, show } = this.props;
let descriptionClasses = 'output-record description'
if (show) {
descriptionClasses = 'output-record description open';
}
return (
<div className="output">
<div className="output-record"><b>City:</b> {city}</div>
<button onClick={descriptionToggler}>Read more</button>
<div className={descriptionClasses}>{description}</div>
</div>
)
}
};
export default CityOutput;
Put the visible key and the toggle function in the CityOutput instead of having it in the parent
import React, { Component } from "react";
import "./CityOutput.css";
class CityOutput extends Component {
state = {
visible: true
};
descriptionTogglerHandler = () => {
this.setState({ visible: !this.state.visible });
};
render() {
const { city, description } = this.props;
let descriptionClasses = "output-record description";
if (this.state.visible) {
descriptionClasses = "output-record description open";
}
return (
<div className="output">
<div className="output-record">
<b>City:</b> {city}
</div>
<button onClick={() => this.descriptionTogglerHandler()}>Read more</button>
<div className={descriptionClasses}>{description}</div>
</div>
);
}
}
export default CityOutput;
There are two ways of how I would approach this,
The first one is setting in your state a key property and check and compare that key with the child keys like:
state = {
country: '',
error: false,
cities: [],
infoMessage: '',
visible: false.
currKey: 0
}
descriptionTogglerHandler = (key) => {
this.setState((prevState) => {
return { currKey: key, visible: !prevState.visible};
});
};
// then in your child component
class CityOutput extends Component {
render() {
const { city, descriptionToggler, description, show, currKey, elKey } = this.props;
let descriptionClasses = 'output-record description'
if (show && elKey === currKey) {
descriptionClasses = 'output-record description open';
}
return (
<div className="output">
<div className="output-record"><b>City:</b> {city}</div>
<button onClick={() => descriptionToggler(elKey)}>Read more</button>
<div className={descriptionClasses}>{description}</div>
</div>
)
}
};
The other way is to set an isolated state for every child component
class CityOutput extends Component {
constructor(props) {
this.state = {
show: false
}
}
function descriptionToggler() {
const {show} = this.state;
this.setState({
show: !show
})
}
render() {
const { city, descriptionToggler, description } = this.props;
let descriptionClasses = 'output-record description'
if (this.state.show) {
descriptionClasses = 'output-record description open';
}
return (
<div className="output">
<div className="output-record"><b>City:</b> {city}</div>
<button onClick={descriptionToggler}>Read more</button>
<div className={descriptionClasses}>{description}</div>
</div>
)
}
};
I hope this helps ;)

Trying to make the todo list update every time a new todo is entered

I am creating a basic todo application in react with flux. I am trying to make it so that every time a new todo is entered in the API, the list is update in real time rather than me having to reload the page the and application making another request. I have tried using componentDidUpdate; however that doesn't seem to work in this case. How should I go about implementing that?
My Todos.js
import React from "react";
import * as TodoActions from "../actions/TodoActions";
import TodoStore from "../stores/TodoStore";
import './Todos.css'
export default class Todos extends React.Component {
constructor() {
super();
this.addItem = this.addItem.bind(this);
this.deleteTodo = this.deleteTodo.bind(this)
this.getTodos = this.getTodos.bind(this);
this.state = {
todos: TodoStore.getAll(),
};
TodoActions.receiveTodos()
}
componentWillMount() {
TodoStore.addChangeListener(this.getTodos);
}
componentWillUnmount() {
TodoStore.removeChangeListener(this.getTodos);
}
componentDidMount() {
TodoActions.receiveTodos();
}
componentDidUpdate() {
TodoActions.receiveTodos();
}
getTodos() {
this.setState({
todos: TodoStore.getAll(),
});
}
deleteTodo(id) {
TodoActions.deleteTodo(id);
}
addItem(e) {
e.preventDefault();
TodoActions.createTodo(this._inputElement.value)
}
render() {
const { todos } = this.state;
const TodoComponents = todos.map((todo) => {
return (
<div key={todo.id} className="todo-list">
<div className="todo-name">{todo.name}</div>
<div className="todo-btn"><button type="button" onClick={() => this.deleteTodo(todo.id)}>Delete</button></div>
</div>
)
});
return (
<div className="main-container">
<h1 className="title">Todos</h1>
<ul>{TodoComponents}</ul>
<form onSubmit={this.addItem}>
<input ref={(a) => this._inputElement = a} placeholder="Enter Task" className="input-form"/>
<button type="submit" className="input-btn">Add</button>
</form>
</div>
);
}
}
My TodoStore.js
import { EventEmitter } from "events";
import AppDispatcher from "../dispatcher";
let _todos = []
function setTodos(todos) {
_todos = todos;
}
class TodoStore extends EventEmitter {
emitChange = () => {
this.emit("change");
}
addChangeListener = (callback) => {
this.on("change", callback)
}
removeChangeListener = (callback) => {
this.removeListener("change", callback)
}
getAll = () => {
return _todos;
}
handleActions = (action) => {
switch(action.type) {
case "RECEIVE_TODOS": {
setTodos(action.todos);
this.emit("change")
break;
}
}
}
}
const todoStore = new TodoStore;
AppDispatcher.register(todoStore.handleActions.bind(todoStore));
export default todoStore;
My TodoActions.js
import AppDispatcher from "../dispatcher";
import apiClient from "../api-client"
export function createTodo(name) {
apiClient.createTodo({name: name}).then((result) => {
AppDispatcher.dispatch({
type: "CREATE_TODO",
name: result,
});
})
}
export function deleteTodo(id) {
apiClient.deleteTodo(id).then((result) => {
AppDispatcher.dispatch({
type: "DELETE_TODO",
id,
});
})
}
export function receiveTodos() {
apiClient.getAllTodos().then(result => {
AppDispatcher.dispatch({
type: "RECEIVE_TODOS",
todos: result.payload
});
})
}

Console.log() after setState() doesn't return the updated state

I've created a simple todo list to learn react and i'm trying to add some additional features. At the moment i'm trying to add buttons that toggle the list of items, so it either shows all the tasks or just those that are completed.
I've written a function to change the state of my visabilityFilter so I can later use this to toggle the items in the list, but it isn't behaving how it should be.
I console log the visabilityFilter variable but it always shows the wrong state before changing to the correct state. e.g. the 'show all' button will console log 'show completed' then if you press it again it will console log 'show all'
App.js
import React, { Component } from 'react';
import './App.css';
import TodoList from './components/TodoList.js'
import VisabilityFilter from './components/VisabilityFilter.js'
export const SHOW_ALL = 'show_all'
export const SHOW_COMPLETED = 'show_completed'
class App extends Component {
constructor (props) {
super(props)
this.state = {
inputValues: {
'newTodo': ''
},
todos: [
{
task: 'My First Todo',
completed: false
}
],
visabilityFilter: SHOW_ALL
}
this.addTodo = this.addTodo.bind(this)
this.handleInputChange = this.handleInputChange.bind(this)
this.handleKeyUp = this.handleKeyUp.bind(this)
this.toggleCompleted = this.toggleCompleted.bind(this)
this.removeTodo = this.removeTodo.bind(this)
this.checkCompleted = this.checkCompleted.bind(this)
this.setVisabilityFilter = this.setVisabilityFilter.bind(this)
}
handleInputChange (e) {
const { inputValues } = this.state
const { id, value } = e.target
this.setState({
inputValues: {
...inputValues,
[id]: value
}
})
}
handleKeyUp (e) {
var code = e.key
if(code === 'Enter') {
this.addTodo(e);
}
}
toggleCompleted (e, index) {
const { todos } = this.state
todos[index].completed = !todos[index].completed
todos.sort((a, b) => b.completed - a.completed)
this.setState({ todos })
}
removeTodo (e, index) {
const { todos } = this.state
this.setState ({ todos: todos.filter((todo, i) => i !== index) })
}
addTodo (e) {
const { todos, inputValues } = this.state
const { dataset } = e.target
if (inputValues[dataset.for] === '') return
const newTodo = { task: inputValues[dataset.for], completed: false }
todos.push(newTodo)
this.setState({
todos,
inputValues: { ...inputValues, [dataset.for]: '' }
})
}
checkCompleted (e, index) {
const { todos } = this.state
return { todos } && todos[index].completed
}
setVisabilityFilter (e) {
const { visabilityFilter } = this.state
const { dataset } = e.target
this.setState({
visabilityFilter: dataset.for
})
console.log ({ visabilityFilter })
}
render() {
const { todos, inputValues, visabilityFilter } = this.state
return (
<div className="App">
<TodoList
todos={todos}
inputValues={inputValues}
addTodo={this.addTodo}
handleInputChange={this.handleInputChange}
removeTodo={this.removeTodo}
toggleCompleted={this.toggleCompleted}
handleKeyUp={this.handleKeyUp}
checkCompleted={this.checkCompleted}
/>
<VisabilityFilter setVisabilityFilter={this.setVisabilityFilter} />
</div>
);
}
}
export default App;
VisabilityFilter.js
import React from 'react'
import { func } from 'prop-types'
import { SHOW_ALL, SHOW_COMPLETED } from '../App'
const VisabilityFilter = props => {
return (
<div>
<button data-for={SHOW_COMPLETED} onClick={ props.setVisabilityFilter } >
Show Completed Tasks
</button>
<button data-for={SHOW_ALL} onClick={ props.setVisabilityFilter }>
Show All Tasks
</button>
</div>
)
}
VisabilityFilter.propTypes = {
setVisabilityFilter: func.isRequired
}
export default VisabilityFilter
setState() is async (React docs), so the state changes won't be applied immediately. If you want to log out the new state,setState() takes in a function as the second argument and performs that function when the state is updated. So:
this.setState({
abc: xyz
},
() => console.log(this.state.abc),
)
Or you can also use componentDidUpdate(), which is recommended
In the functional components, you can use useEffect to track changes in state.
useEffect(() => {
console.log(someState);
},[someState);

React Redux adding items to list carries over to other created lists.

I've been battling with this for too long, can someone point me to the right direction?
The issue: When i create a list I am able to update/delete it. I am also able to add items to it and update/delete these items. When I add another list, the items from the former gets carried over to the latter then I am unable to edit the items. If I delete the List and don't refresh the browser the items are still in the list. I need a way to tie the two together in a way that a list only knows about its items
Thanks in advance for the help.
/actions/lists.js
export const CREATE_LIST = 'CREATE_LIST'
export function createList(list) {
return {
type: CREATE_LIST,
id: uuid.v4(),
items: list.items || [],
...list
}
}
export const CONNECT_TO_LIST = 'CONNECT_TO_LIST'
export function connectToList(listId, itemId) {
return {
type: CONNECT_TO_LIST,
listId,
itemId
}
}
export const DISCONNECT_FROM_LIST = 'DISCONNECT_FROM_LIST'
export function disconnectFromList(listId, itemId) {
return {
type: DISCONNECT_FROM_LIST,
listId,
itemId
}
}
/actions/items.js
export const CREATE_ITEM = 'CREATE_ITEM'
export function createItem(item) {
return {
type: CREATE_ITEM,
item: {
id: uuid.v4(),
...item
}
}
}
export const UPDATE_ITEM = 'UPDATE_ITEM'
export function updateItem(updatedItem) {
return {
type: UPDATE_ITEM,
...updatedItem
}
}
/reducers/lists.js
import * as types from '../actions/lists'
const initialState = []
export default function lists(state = initialState, action) {
switch (action.type) {
case types.CREATE_LIST:
return [
...state,
{
id: action.id,
title: action.title,
items: action.items || []
}
]
case types.UPDATE_LIST:
return state.map((list) => {
if(list.id === action.id) {
return Object.assign({}, list, action)
}
return list
})
case types.CONNECT_TO_LIST:
const listId = action.listId
const itemId = action.itemId
return state.map((list) => {
const index = list.items.indexOf(itemId)
if(index >= 0) {
return Object.assign({}, list, {
items: list.items.length > 1 ? list.items.slice(0, index).concat(
list.items.slice(index + 1)): []
})
}
if(list.id === listId) {
return Object.assign({}, list, {
items: [...list.items, itemId]
})
}
return list
})
case types.DISCONNECT_FROM_LIST:
return state.map((list) => {
if(list.id === action.listId) {
return Object.assign({}, list, {
items: list.items.filter((id) => id !== action.itemId)
})
}
return list
})
default:
return state
}
}
/reducers/items.js
import * as types from '../actions/items'
const initialState = []
export default function items(state = initialState, action) {
switch (action.type) {
case types.CREATE_ITEM:
return [ ...state, action.item ]
case types.UPDATE_ITEM:
return state.map((item) => {
if(item.id === action.id) {
return Object.assign({}, item, action)
}
return item
})
case types.DELETE_ITEM:
return state.filter((item) => item.id !== action.id )
default:
return state
}
}
/components/List.jsx
import React from 'react'
import { connect } from 'react-redux'
import { bindActionCreators } from 'redux'
import Items from './Items'
import Editor from './Editor'
import * as listActionCreators from '../actions/lists'
import * as itemActionCreators from '../actions/items'
export default class List extends React.Component {
render() {
const { list, updateList, ...props } = this.props
const listId = list.id
return (
<div {...props}>
<div className="list-header"
onClick={() => props.listActions.updateList({id: listId, isEditing: true})}
>
<div className="list-add-item">
<button onClick={this.addItem.bind(this, listId)}>+</button>
</div>
<Editor
className="list-title"
isEditing={list.isEditing}
value={list.title}
onEdit={title => props.listActions.updateList({id: listId, title, isEditing: false})}
/>
<div className="list-delete">
<button onClick={this.deleteList.bind(this, listId)}>x</button>
</div>
</div>
<Items
items={this.listItems}
onValueClick={id => props.itemActions.updateItem({id, isEditing: true})}
onEdit={(id, text) => props.itemActions.updateItem({id, text, isEditing: false})}
onDelete={itemId => this.deleteItem(listId, itemId)}
/>
</div>
)
}
listItems() {
props.list.items.map(id => state.items[
state.items.findIndex(item => item.id === id)
]).filter(item => item)
}
deleteList(listId, e) {
e.stopPropagation()
this.props.listActions.deleteList(listId)
}
addItem(listId, event) {
event.stopPropagation()
const item = this.props.itemActions.createItem({
text: 'New Shopping Item'
})
this.props.listActions.connectToList(listId, item.id)
}
deleteItem(listId, itemId) {
this.props.listActions.disconnectFromList(listId, itemId)
this.props.itemActions.deleteItem(itemId)
}
}
function mapStateToProps(state) {
return {
lists: state.lists,
items: state.items
}
}
function mapDispatchToProps(dispatch) {
return {
listActions: bindActionCreators(listActionCreators, dispatch),
itemActions: bindActionCreators(itemActionCreators, dispatch)
}
}
export default connect(mapStateToProps, mapDispatchToProps)(List)
/components/List.jsx
import React from 'react'
import List from './List.jsx'
export default ({lists}) => {
return (
<div className="lists">{lists.map((list) =>
<List className="list" key={list.id} list={list} id={list.id} />
)}</div>
)
}
/components/Items.jsx
import React from 'react'
import { connect } from 'react-redux'
import Editor from './Editor'
import Item from './Item'
export default class Items extends React.Component {
render () {
const {items, onEdit, onDelete, onValueClick, isEditing} = this.props
return (
<ul className="items">{items.map(item =>
<Item
className="item"
key={item.id}
id={item.id}
isEditing={item.isEditing}>
<Editor
isEditing={item.isEditing}
value={item.text}
onValueClick={onValueClick.bind(null, item.id)}
onEdit={onEdit.bind(null, item.id)}
onDelete={onDelete.bind(null, item.id)}
/>
</Item>
)}</ul>
)
}
}
export default connect(
state => ({
items: state.items
})
)(Items)
/components/Item.jsx
import React from 'react'
export default class Item extends React.Component {
render() {
const { id, isEditing, ...props } = this.props
return (
<li {...props}>{props.children}</li>
)
}
}
/components/App.jsx
class App extends React.Component {
handleClick = () => {
this.props.dispatch(createList({title: "New Shopping List"}))
}
render() {
const lists = this.props.lists
return (
<div>
<button
className="add-list"
onClick={this.handleClick}>Add Shopping List</button>
<Lists lists={lists}/>
</div>
)
}
}
export default connect(state => ({ lists: state.lists }))(App)
Assuming this is all meant to operate on a single array at a time, this part looks pretty suspicious:
case types.CREATE_LIST:
return [
...state,
{
id: action.id,
title: action.title,
items: action.items || []
}
]
That ...state is expanding whatever existing array there is into the new array that you're returning, and it doesn't sound like that's the behavior you actually want. My first guess is that when you create a new list, you probably want to just return the one new item inside, not the entire old list contents plus that new item.
Some of your other immutable-style update code also looks sort of complex. I know that "update a thing in the middle of an array" isn't always easy to deal with. You might want to take a look at this SO post on updating immutable data, which lists several ways to approach it. I also have a links repo that catalogs Redux-related libraries, and it has a list of immutable data management libraries that might make things easier for you.
Though #markerikson 's tip was part of the issue i was having, it didn't solve it completely. I had to fix my mapStateToProps to this
function mapStateToProps(state, props) {
return {
lists: state.lists,
listItems: props.list.items.map(id => state.items[
state.items.findIndex(item => item.id === id)
]).filter(item => item)
}
}
and remove the connect from previous implementation for the items alone in my Items.jsx

Categories

Resources