Это мое приложение "напоминания".
Моя проблема: я хочу сделать кнопку Редактировать, чтобы редактировать элемент и обновлять его. У каждого элемента есть случайный идентификатор, чтобы указать его действие.
процесс следующий:
Раньше я получал этот список напоминаний в моем редукторе, затем создавал функцию, а затем .filter () в списке при удалении элемента всякий раз, когда нажимается кнопка 'X', а для функции редактирования я использовал filter (<давая id здесь === id>), но это очистило список, кроме того, который был удален! Я пробовал с .find (), но это возвращает объект, а не массив: поэтому у меня возникла проблема в моем App.jsx, потому что я отображаю этот массив.
import React, { Component } from 'react';
import "../App.css"
import { connect } from 'react-redux';
import { addReminder } from '../actions';
import { Button } from 'react-bootstrap';
import { deleteReminder } from '../actions'
import moment from 'moment'
import { modifyReminder } from '../actions'
class App extends Component {
// as usual we add our constructor to make states
constructor(props){
super(props);
// make states !
// NOTE: the dueDate key was added recently
this.state= {
text: '',
dueDate: '',
alertDate: ''
}
}
// Warning: this is not the imported addReminder from ../actions, its the helper function
addReminder(){
// I hided this console.info after using it , to show state of our input component
//console.info('this.state ', this.state);
//Note: after making the connect(), we check if our application is connected to the store or not
// normally we'll find that props are containing our action creator (addReminder()) but without data
// console.info('this ', this);
// Now after checking the console, if our action creator is in the props of our App!
// so we can call it directly! (remember we have console.info() to show the action in actions folder)
// Also we will check wheter the reducer is working or not, ( we also have console.info in the reducers folder)
//NOTE: dueDate was added recently
console.info('this.state.dueDate', this.state.dueDate)
/** HERE WE ARE CALLING THE IMPORTED addReminder() from actions folder**/
this.props.addReminder(this.state.text, this.state.dueDate);
}
// Warning: this is not the imported deleteReminder from ../actions, its the helper function
deleteReminder(id){
console.info('deleting in application', id) // outputs the id of the reminder that we click to delete
console.info('this.props', this.props) // this will prouve that our app is still connected with redux
/** HERE WE ARE CALLING THE IMPORTED deleteReminder() from actions folder**/
this.props.deleteReminder(id)
}
// Need to be handled
/*
modifyReminder(id){
console.info('modifying in application', id)
console.info('this.props', this.props)
this.props.modifyReminder(id)
const {reminders} = this.props
return (
reminders.map( reminder => {
console.info('text is ' , reminder.text)
return(
<TextModified textModifier = {reminder.text} />
)
})
)
}
*/
// After making connection between redux and the app,
// After making reactivity between UI, User and states
// and then we got our data from states.
// Now the user doesn't know that yet, so we have to show the list of reminders entered by the user
renderReminders() {
// here we declare a const variable, it will contain reminders list from mapStateToProps()
const {reminders} = this.props;
// outputs reminders [id:, text:''] as list
console.info('Reminders as list', reminders)
// return jsx
return (
// <ul> html tag for lists
<ul className = "list-group col-sm-4">
{
// we map our list, getting a value by a key
reminders.map( ( reminder ) => {
// list item
return (
<li key = { reminder.id } className='list-group-item'>
{/*the list item will have text and a date*/}
<div className = "list-item">
{/*show the reminder's text*/}
<div>{reminder.text}</div>
{/*show the reminder's date, <em> jsx tag is used to emphasize*/}
{/*install (moment --save) through yarn to format the date and time*/}
<div>
<em>
{moment(new Date(reminder.dueDate)).fromNow()}
{/* this.setState({alertDate: reminder.dueDate})*/}
</em>
</div>
</div>
{/* We add a button here to delete a reminder from the list
1 to create a deletion of an item we must have a logic,
2 go to constants.js and make a new const */}
<div
className = "list-item delete-button"
onClick= {() => this.deleteReminder(reminder.id)}>
{/*THIS IS THE REMINDER DELETION*/}
<div className = "btn delete-item" >✕</div>
</div>
{/*<div className = "Modify-item"
onClick = {() => this.modifyReminder(reminder.id)}>
Modify
</div>
*/}
</li>
)
})
}
</ul>
)
}
render(){
console.info('this.props ', this.props); // this will show props every time render() is called
//const lastName = 'Firas';
//console.info(`Hello mr.${lastName}`)
return(
<div className = "App">
{/* this is ou title */}
<div className = "Title">
My Reminder
</div>
<div className = "form-inline">
<div className = "form-group">
{/*this is the reminder tag*/}
<input className = "form-contol"
placeholder = "I have to..."
onChange = {event => this.setState({text: event.target.value})}/>
{/*this is the date tag*/}
<input className = "form-contol" type = "datetime-local"
onChange = { event => this.setState({dueDate: event.target.value})}/>
</div>
{/* this is the button */}
<Button type = "button"
className = "btn btn-success"
onClick= {() => this.addReminder()}>
Add reminder
</Button>
</div>
{/*THIS IS THE REMINDERS LIST, IT WIL BE SHOWN WHEN WE ADD REMINDERs
THIS function will be called everytime render() is called */}
{this.renderReminders()}
<div className = "btn bnt-danger" onClick = {() => this.props.clearReminders()}>
Clear Reminders
</div>
</div>
)
}
}
/** TODO: After making the view, and setting up redux search for connect(mapDispatchToProps function, mapStateToProps function) **/
// this function is hooked by connect(), it dispatch the action, it returns a bindActionCreators
// function, that turns an object
// I hided this function since, I can pass an object instead of the entire function
// so by using addReminder imported from actions folder, mapDispatchToProps is called
// automaticaly and use this object in its bindActionCreators()
/*
function mapDispatchToProps(dispatch){
return bindActionCreators({addReminder}, dispatch);
}
*/
// We can define states to props, so we can recognize the redux state within this component
// This function will be passed as first arg in connect(), and it will be hoocked by it,
// just like mapDispatchToProps function
function mapStateToProps(state){
// I hided this and replace it under render() method
//console.info('state ', state);
return {
reminders: state
}
}
// now we connect it to our component, by connect() from redux
// the first argument should be mapStateToProps(), but we don't have it yet, so we pass it as null
// the second argument is mapDispatchToProps()
// and then we'll have our App component hooked up
// NOTE: deleteReminder was added here after defining it in the actions, after the addReminder()
export default connect(mapStateToProps , {addReminder, deleteReminder, modifyReminder, clearReminders}) (App);
// Welcome to actions/index.js file !
// First we need to get the action from ../constants.js
import { ADD_REMINDER} from '../constants';
// NOTE: this is related to deletion of list item, its not related to redux setup
// we imported our type of action (delete) from ../constants.js file
import { DELETE_REMINDER } from '../constants';
import { MODIFY_REMINDER } from '../constants';
import { CLEAR_REMINDERS } from '../constants'
/**
* This is our action creator, it's called addReminder,
* its assigned by an ANONYMOUS ARROW function that will have - in our case -
* 1 parameter, its a text that we'll pass to our addReminder action creator
*/
export const addReminder = ( text , dueDate ) => { // it should be written as (text) if it has more than one arg
// here we define the Action ( Plain JS object)
const action = {
type: ADD_REMINDER, // this type name was imported from the constants.js file
// we can use ES6 syntax feature if the key and value are same (text.equals(text))
// so just write text.
// or text: text ( both are correct)
text: text,
dueDate: dueDate,
}
// Log the action through the action creator into the console, this will show, later,
// whether our action is connected to the application or not
console.info('Action in addRemider :',action );
// return the action
return action;
}
// NOTE: this is related to redux setup:
// Now we go to App.jsx file, and after running the code, an error occurs telling that
// "Expected the reducer to be a function", so let's go to create reducers/index.js file
// NOTE: the next step is considered after the step in which we show the reminders list.
/**
* TODO: make a logic to delete a list item
* -> we need to identify the item to delete it, luckily we created an id for each item so we can specify them
* so our argument in this function will be an id
*/
export const deleteReminder = id => {
const action = {
type: DELETE_REMINDER,
id:id // id here is the arg in our function deleteReminder()
}
console.info('Deleting in actions', action)
return action
}
export const modifyReminder = id => {
const action = {
type: MODIFY_REMINDER,
id
}
console.info('Modifying in actions', action)
return action
}
export const clearReminders = () => {
return {
type: CLEAR_REMINDERS
}
}
// Welcome to reducers/index.js file
// First, as we saw in actions/index.js, we need to import the type from constants file
import { ADD_REMINDER/*, ADD_FAVORITE*/ , DELETE_REMINDER , MODIFY_REMINDER , CLEAR_REMINDERS} from '../constants';
import { bake_cookie, read_cookie } from 'sfcookies' // cookies are used to save some data locally
/**
* Step 2 define a helper reminder() function which takes 1 arg ( action )
*/
const reminder = action => {
let { text,dueDate } = action;
// we return an object as a reminder, with a text and a random ID ( for example )
return {
id: Math.random(), // check Math JS class on google for better information
text: text,
dueDate: dueDate,
}
}
/** Step 3 removeById function
* we'll have an arrow function that will have 2 args:
* 1 - state = [] -> the list of our reminders
* 2 - id -> the id of the concerned item to delete
* the function is going to filter the list
* then only returns the IDs that are not equal to the clicked list item id
*/
const removeById = (state = [], id) => {
const reminders = state.filter( reminder => reminder.id !== id)
// this will show the filtered list
console.info ('new reduced reminders', reminders)
return reminders
}
const modifyById = (state = [], id) => {
const reminders = state.find( (reminder) => reminder.id === id )
console.info ('new reduced reminders', reminders)
return reminders
}
/** Step 1 Reducer creation: it will be an ANONYMOUS ARROW function,
* it has 2 parameters:
* ( state[] - preinitialized to an empty array - , - and a second arg which is - action )
* -> (state[],action)
*/
const reminders = (state = [], action) => { // it can be written as state = [] without if parentheses if there is only arg
// We initialize a variable here within our reminders reducer to null
let reminders = null;
state = read_cookie('reminders')
// Generally we can expect more than one type of action entered here in the future,
// besides addReminder() (the action creator)
// so let's use a switch statement
switch(action.type) {
// we consider our first case as our ADD_REMINDER defined in constants.js file
case ADD_REMINDER:
// -> in this case we set our reminders to an array
// -> Here we are using a NEAT ES6 trick
// Our first element will be a spread object ( like varargs in Java ) which was our state array
// Our second element will be a reminder() that will take an action parameter, check Step 2
reminders = [...state, reminder(action)];
// Log the reminder as state through the reducer into the console, this will show if our reducer is connected
// to the application or not
//console.info('reminders as state', reminders);
// save cookie to our browser
bake_cookie('reminders', reminders)
// we return the reminders tht we've gotten
return reminders;
// we consider our second case as our DELETE_REMINDER defined in constants.js file
case DELETE_REMINDER:
// in this, we move to declare our removeId function first
reminders = removeById(state, action.id)
bake_cookie('reminders', reminders)
return reminders
case MODIFY_REMINDER:
reminders = modifyById(state, action.id)
return reminders
case CLEAR_REMINDERS:
reminders = []
bake_cookie('reminders', reminders)
return reminders;
// otherwise, if we got other types than "ADD_REMINDER"
default:
return state;
}
}
export default reminders;
import React from 'react';
import ReactDOM from 'react-dom'; //ReactDOm
import App from './components/App'; // this is ur App
import { Provider } from 'react-redux';// Provider will make the applicaton under the store
import { createStore } from 'redux'; // a store is a Provider prop
import reducer from './reducers';
import './index.css'; // notice how we import css file
// NOTE: When we import the Provider and place our App into it, the console will
// throw an error asking to define a store, a store prop belongs to Provider, so we need to createStore()
// to create the store, and later we'll pass a param to this function, that takes states in an
// an action, and return new state
//const store = createStore(); // this line was added without reducers at the beginning
const store = createStore(reducer);
ReactDOM.render(
/*
A store that stores all the data and provides methods to manipulate this data.
The store is created with the createStore() function
*/
/*
A Provider component that makes it possible for any components to take data
from the store
*/
<Provider store = {store}>
<App />
</Provider>,
document.getElementById('root')
);
Похоже, вы фильтруете в обратном направлении.
@BlakeSteel Я ценю ваш совет, но извините, я новичок, я обещаю вам, что я постараюсь быть лучше в своих будущих вопросах



![Безумие обратных вызовов в javascript [JS]](https://i.imgur.com/WsjO6zJb.png)


Redux немного сложно обрабатывать, когда дело доходит до уменьшения текущего состояния поля.
Чтобы удалить элемент в списке: (omitBy происходит от Lodash)
case DELETE_ITEM: {
return omitBy(state, item => item.id === action.id);
// OR in vanilla
const itemIndex = list.findIndex(item => item.id === action.id);
const newState = [...state];
newState.splice(itemIndex, 1)
return newState;
}
Чтобы отредактировать элемент в списке:
case MODIFY_ITEM: {
const itemIndex = list.findIndex(item => item.id === action.id);
return {
...state,
[itemIndex]: {
...state[itemIndex],
...action.newItem // newItem is an object containing the properties you want to modify
}
}
}
Немного грустно использовать функции, создающие циклы, такие как findIndex или omit, когда ваши списки полны идентификаторов. Вы можете улучшить этот код, используя keyBy. (вы также можете сделать это в ванильном ES6) или даже Immutable.js, чтобы превратить ваши массивы в карты с ключом по идентификатору, а затем получить доступ к своим элементам следующим образом: mapOfItems[itemId].
Когда состояние - это карта, случай редактирования будет выглядеть следующим образом:
case MODIFY_ITEM: {
return {
...state,
[action.id]: {
...state[action.id],
...action.newItem,
}
}
}
И удаление будет таким же простым, как omit(state, action.id)
Ваш редуктор изменяет состояние для действия DELETE_ITEM при использовании "ванильной" версии.
@ Eld0w, я ценю ваш ответ, но, пожалуйста, внимательно посмотрите на мой код и попробуйте дать что-нибудь, работающее с ним, я стараюсь сделать это без использования какой-либо другой библиотеки. Мне просто нужен React, redux и все. И обычно выход есть
Когда ваше состояние нормализовано, это намного проще, вы можете сделать это вручную, как я уже упоминал, с помощью KeyBy, или даже уменьшить, или использовать библиотеку, такую как normalizr. В этом посте многое объясняется о нормализации состояния: redux.js.org/recipes/structuring-reducers/… - мои первые два образца кода должны работать вообще, и они не полагаются на какие-либо библиотеки, но я рекомендую потратить время на работу с нормализованным состоянием редукции.
@Nimelrian: я отредактировал свой ответ без изменения состояния
Привет, добро пожаловать на переполнение стека! Посмотрите эту страницу на как задать хороший вопрос, в частности, сосредоточьтесь на том, сколько кода вам действительно нужно, чтобы показать нам эту проблему. Я счел полезным попробовать создать очень-очень-очень маленький проект, чтобы продемонстрировать проблему. Это не только более полезно для людей, пытающихся ответить на ваш вопрос, но и вы можете решить его попутно! Удачи!