[英]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: 过程如下:
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中遇到问题,因为我正在映射该数组。
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" >✕</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);
// 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
}
}
// 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;
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')
);
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.