简体   繁体   English

React.js,Redux,制作提醒应用

[英]React.js, Redux, Making Reminder App

This is my "reminders" app. 这是我的“提醒”应用。

My problem is: I want to make an Edit button to edit an item and update it. 我的问题是:我想使用“ 编辑”按钮来编辑项目并更新它。 Every item has a random ID, to specify its action. 每个项目都有一个随机ID,用于指定其操作。

the process is as below: 过程如下:

  1. the button 'edit' is shown like the delete button DYNAMICALLY 动态显示了“编辑”按钮,就像删除按钮一样
  2. when the 'edit' button is pressed, an input should be created somewhere on the screen, including the text value of the reminder, and new button called 'update text' 当按下“编辑”按钮时,应在屏幕上的某处创建输入,包括提醒的文本值,以及名为“更新文本”的新按钮
  3. when the value is edited, and the 'update text' button is clicked, the item on the list have to change dynamically. 当编辑值并单击“更新文本”按钮时,列表中的项目必须动态更改。

I used to get that list of reminders in my reducer, then make function and then .filter() on the list when deleting an item whenever a button 'X' is pressed, and for the edit functionnlity , I used filter(< giving an id here === id>), but this has cleared the list, except the one which is licked! 我曾经在我的reducer中获取该提醒列表,然后在每次按下按钮“ X”时删除项目时在列表中使用make函数,然后使用.filter(),对于编辑功能,我使用filter(< id here === id>),但这已经清除了列表,除了被舔的列表! I tried with .find() but this return an object not an array: So I got problem in my App.jsx because I m mapping that array. 我尝试使用.find(),但这返回的对象不是数组:所以我在App.jsx中遇到问题,因为我正在映射该数组。

My App.jsx file 我的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.log after using it , to show state of our input component 
    //console.log('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.log('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.log() to show the action in actions folder)
    // Also we will check wheter the reducer is working or not, ( we also have console.log in the reducers folder)

    //NOTE: dueDate was added recently
    console.log('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.log('deleting in application', id) // outputs the id of the reminder that we click to delete
    console.log('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.log('modifying in application', id)
    console.log('this.props', this.props)

    this.props.modifyReminder(id)

    const {reminders} = this.props

    return (
        reminders.map( reminder => {
        console.log('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.log('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" >&#x2715;</div>
                                        </div>

                                        {/*<div className="Modify-item" 
                                        onClick = {() => this.modifyReminder(reminder.id)}>
                                            Modify
                                        </div>
                                        */}

                                    </li>
                                )
                        })
                }
            </ul>
            )       
}

render(){
    console.log('this.props ', this.props); // this will show props every time render() is called

    //const lastName = 'Firas';
    //console.log(`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.log('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);

My actions/index.js 我的动作/ index.js

// 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.log('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.log('Deleting in actions', action)

    return action
}

export const modifyReminder = id => {

    const action = {
        type: MODIFY_REMINDER,
        id
    }

    console.log('Modifying in actions', action)

    return action
}

export const clearReminders = () => {
    return {
        type: CLEAR_REMINDERS
    }
}

My reducers/index.js file 我的reducers / index.js文件

// 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.log ('new reduced reminders', reminders)

    return reminders
}

const modifyById = (state = [], id) => {

    const reminders = state.find( (reminder) => reminder.id === id )

    console.log ('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.log('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;

My src/index.js file 我的src / index.js文件

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')
    );

Here's a GIF for my App 这是我的应用的GIF 在此处输入图片说明

Redux is a bit tricky to handle when it comes to reducing the current state of a field. 在减少字段的当前状态时,Redux的处理有些棘手。

To delete an item in a list : (omitBy comes from lodash ) 要删除列表中的项目:(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;
}

To edit an item in a list : 要编辑列表中的项目:

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

It's a bit sad to use functions making loops such as findIndex or omit when your lists are full of ids. 这是一个有点伤感使用功能,使循环如findIndex或省略当你的列表是充满的ID。 You can further improve this code by making use of keyBy . 您可以通过使用keyBy进一步改进此代码。 (you can also do it in vanilla ES6), or even Immutable.js to turn your arrays into Maps keyed by id and then be able to access your items like this : mapOfItems[itemId] . (您也可以在普通ES6中执行此操作),甚至可以使用Immutable.js将数组转换为以id为键的Maps,然后可以访问您的项目,例如: mapOfItems[itemId]

When the state is a map, the edit case would look like this : 当状态为地图时,编辑用例将如下所示:

case MODIFY_ITEM: {
  return {
    ...state,
    [action.id]: { 
      ...state[action.id], 
      ...action.newItem,
    }
  }
}

And the deletion would be as simple as omit(state, action.id) 删除就像omit(state, action.id)一样简单

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM