在onChange / onClick之后重新生成页面的部分

时间:2017-08-28 22:20:53

标签: javascript reactjs

我即将完成论文,但刚遇到一个我之前没遇到过的重大错误。我基本上有一个组件可以呈现冲刺(项目管理)和与之相关的用户故事。现在,您可以添加sprint的方法是在文本框中键入内容或从下拉列表中的待办事项中选择用户素材。我也可以选择编辑和删除sprint中的故事。

这里的主要问题是每次我在文本框中为用户故事键入内容或从下拉列表中进行选择,甚至尝试编辑或删除用户故事时页面都会刷新。我无法理解为什么它这样做,因为我暂时没有真正改变这个组件中的任何内容,而且它随机开始这样做。 (renderSprints方法)

我的组件:

import React, { Component } from 'react';
import { browserHistory } from 'react-router';
import { Dropdown, Button, NavItem, Tabs, Tab, Row, Input, Modal, Icon } from 'react-materialize';
import logo from '../images/scrumtastic_logo_white.png';
import axios from 'axios';
import { BASE_URL } from '../constants';
import moment from 'moment';
import Toast from './Toast';
import '../App.css';

class SprintView extends Component {
    constructor(props) {
        super(props);
        this.state = {
            'userId': '',
            'email': '',
            'userEmail': '',
            'token': '',
            'projectId': '',
            'users': [],
            'sprints': [],
            'features': [],
            'stories': [],
            'backlogStories': [],
            'sprintStartDate': '',
            'sprintEndDate': '',
            'storyDesc': '',
            'newStoryDesc': '',
            'clickedStory': '',
            'storyEditingMode': false,
            'userStoryCheck': false,
            'selectValue': '',
            'sprintId': '',
            'error': [],
            'origStories': []
        }
    }

    componentWillMount() {
       let email = localStorage.getItem('email');
       let token = localStorage.getItem('token');
       let userId = localStorage.getItem('userId');
       let projectId = localStorage.getItem('projectId');
       let projectName = localStorage.getItem('projectName');
       let stories = localStorage.getItem('stories');
       let origStories = localStorage.getItem('backlogStories');
       this.setState({'token': token, 'userId': userId, 'projectId': projectId, 'projectName': projectName, 'email': email, 'backlogStories': stories, 'origStories': origStories});

        axios.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest';
        axios.defaults.headers.common['Authorization'] = 'Bearer ' + token
        axios.get(`${BASE_URL}/projects/${projectId}/users`)
            .then((data) => {
                this.setState({'users': data.data[0].users})
            })
            .catch((error) => {
                this.setState({error});
            })  
    }

    componentDidMount() {
        const token = 'Bearer ' + this.state.token
        const projectId = this.state.projectId;

        axios.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest';
        axios.defaults.headers.common['Authorization'] = token
        axios.get(`${BASE_URL}/projects/${projectId}/sprints`)
            .then((data) => {
                let projectSprints = [];
                data.data[0].sprints.forEach((sprint) => {
                    projectSprints.push(sprint);
                })
                this.setState({'sprints': projectSprints});
                this.getStories(projectSprints);
            })
            .catch((error) => {
                this.setState({error});
            }) 
    }

    saveStoriesToStorage() {
        localStorage.setItem('stories', this.state.stories);
        localStorage.setItem('sprintId', this.state.sprintId);
    }

    renderUsers() {

        return (
            <ul className="collection">
            {
            this.state.users.map(user => {
                    return (
                        <li key={user.id} className="collection-item"><div style={{float: 'left', position: 'relative', top: '-5px'}}><Icon small>account_box</Icon></div>{user.email}</li>
                    )  
            })
            }
            </ul>
        )
    }

    getStories(projectSprints) {
        const token = 'Bearer ' + this.state.token;
        let stateStories = [];

        projectSprints.forEach(sprint => {
            axios.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest';
            axios.defaults.headers.common['Authorization'] = token
            axios.get(BASE_URL + '/sprints/' + sprint.id + '/stories')
                .then((data) => {

                    let sprintStories = [];
                    data.data[0].stories.forEach((story) => {
                        sprintStories.push(story);
                    })
                    sprintStories.forEach(story => {
                        stateStories.push(story);
                    })
                    this.setState({'stories': stateStories});

                        this.getBacklogStories();
                })
                .catch((error) => {
                    this.setState({error});
                }) 
        })
    }

    getBacklogStories() {
        const token = 'Bearer ' + this.state.token
        const projectId = this.state.projectId;

        axios.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest';
        axios.defaults.headers.common['Authorization'] = token

        axios.get(`${BASE_URL}/projects/${projectId}/stories`)
            .then((data) => {
                let projectStories = [];
                data.data[0].stories.forEach((story) => {
                    projectStories.push(story);
                })
                this.setState({'backlogStories': projectStories});
            })
            .catch((error) => {
                this.setState({error});
            }) 
    }

    logOut() {
        const token = 'Bearer ' + this.state.token;

        axios.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest';
        axios.defaults.headers.common['Authorization'] = token
        axios.post(`${BASE_URL}/logout`, {
            'email': this.state.email,
        })
            .then((data) => {
                if(data.status === 200) {
                    localStorage.removeItem('token')
                    localStorage.removeItem('email')
                    let t = new Toast("Succesfully logged out!", 2500)
                    t.Render(); 
                    setTimeout(() => {browserHistory.push('/signin')}, 2500)
                }
            })
            .catch((error) => {
                this.setState({error});
            }) 
    }

    createStory(sprintId) {
        let stories = this.state.stories;

        const token = 'Bearer ' + this.state.token;

        axios.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest';
        axios.defaults.headers.common['Authorization'] = token
        axios.post(`${BASE_URL}/sprintstories`, {
            'description': this.state.storyDesc,
            'sprint_id': sprintId,
            'project_id': this.state.projectId
        })
            .then((data) => {
                data.data.pivot = { 'sprint_id': sprintId, 'story_id': data.data.id};
                stories.push(data.data);
                this.setState({'stories': stories});
                this.forceUpdate();
            })
            .catch((error) => {
                this.setState({error});
            }) 
    }

    handleCheck() {
        this.setState({'userStoryCheck': !this.state.userStoryCheck});
    }

    createStorySelect(sprintId) {
        const backlogStories = this.state.stories;
        if (backlogStories) {
            let items = [];
            for(let i = 0; i < backlogStories.length; i++) {
                items.push(<NavItem key={i} value={backlogStories[i].description} onClick={this.handleChange.bind(this, backlogStories[i].id, sprintId)}>{backlogStories[i].description}</NavItem>);
            }

            return items;
        }
    }

    addStoryToSprint(storyId, sprintId) {
        const token = 'Bearer ' + this.state.token;
        let stories = this.state.stories;

        axios.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest';
        axios.defaults.headers.common['Authorization'] = token
        axios.post(`${BASE_URL}/storestorypivot`, {
            'sprint_id': sprintId,
            'story_id': storyId
        })
            .then((data) => {
                data.data.pivot = { 'sprint_id': sprintId, 'story_id': data.data.id};
                stories.push(data.data);
                this.setState({'stories': stories});
                this.forceUpdate();
            })
            .catch((error) => {
                this.setState({error});
            })        
    }

    renderErrors() {
        let errors = [];
        if(this.state.error.response && this.state.error.response.data) {
            const errorResp = this.state.error.response.data.error;
            if (typeof errorResp === "string") {
                errors.push(<p className="errorMessage" key={"error_" + 1}>{errorResp}</p>)
            } else {
                for (let key in errorResp) {
                    errors.push(<p className="errorMessage" key={"error_" + key}>{errorResp[key]}</p>)
                }
            }
        }
        return <div className="center-align">{errors}</div>
    }

    handleChange(storyId, sprintId) {
        this.addStoryToSprint(storyId, sprintId);
    }

    goToTasks(sprintId) {
        localStorage.setItem('sprintId', sprintId);
        browserHistory.push('/list');
    }

    addUser() {
        const token = 'Bearer ' + this.state.token;
        let users = this.state.users;

        axios.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest';
        axios.defaults.headers.common['Authorization'] = token
        axios.post(`${BASE_URL}/attachuser`, {
            'email': this.state.userEmail,
            'project_id': this.state.projectId
        })
            .then((data) => {
                users.push(data.data[0]);
                this.setState({users: users})
                let userModal = document.getElementById('userModal');
                let userModalOverlay = document.getElementById('materialize-modal-overlay-1');
                userModal.style.display = 'none';
                userModalOverlay.style.display = 'none';
                let t = new Toast(this.state.userEmail + " added to project!", 2500)
                t.Render(); 
            })
            .catch((error) => {
                this.setState({error});
            })   
    }

    sprintIdToStorage(sprintId) {
        this.setState({sprintId: sprintId})
    }

    editStory(storyId, storyDescription) {

        if(this.state.storyEditingMode === true) {
            const token = 'Bearer ' + this.state.token;
            let newStoryDesc = this.state.newStoryDesc;
            let stories = this.state.stories;

            if(this.state.storyDesc) {
                newStoryDesc = this.state.storyDesc
            }

            axios.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest';
            axios.defaults.headers.common['Authorization'] = token
            axios.put(`${BASE_URL}/stories/${storyId}`, {
                'description': newStoryDesc
            })
                .then((data) => {
                    for (var i=0; i < stories.length; i++) {
                        if (stories[i].id === storyId) {
                            stories[i].description = newStoryDesc;
                            this.setState({'storyDesc': ''});
                            this.setState({'stories': stories});
                        }
                    }  
                })
                .catch((error) => {
                    this.setState({error});
                }) 
        } 
        else {
            this.setState({'newStoryDesc': storyDescription});
        }

        if(storyId) {
            this.setState({'clickedStory': storyId});
        }
        this.setState({'storyEditingMode': !this.state.storyEditingMode});
    }

    deleteStory(storyId) {
        let stories = this.state.stories;
        const token = 'Bearer ' + this.state.token;

        axios.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest';
        axios.defaults.headers.common['Authorization'] = token
        axios.delete(`${BASE_URL}/stories/${storyId}`)
            .then((data) => {
                this.searchAndDeleteStoryFromState(storyId, stories);
            })
            .catch((error) => {
                this.setState({error});
            }) 
    }

    searchAndDeleteStoryFromState(keyName, array) {
        for (var i=0; i < array.length; i++) {
            if (array[i].id === keyName) {
                delete array[i]
                this.setState({'stories': array});
            }
        }    
    }

    renderSprints() {
        const sprints = this.state.sprints;
        const stories = this.state.stories;

        return (
            <div className="row">
                {this.state.sprints.length === 0 ? <p style={{marginLeft: '10px'}} className="sprint-message waves-effect waves-light teal lighten-2">Seems like you need to add some sprints</p> : null }
                <Tabs className='tab-demo z-depth-1'>
                {
                    sprints.map(sprint => {
                        return (
                            <Tab key={sprint.id} title={sprint.name}>

                                <h3>{moment(sprint.start_date).format("MMM Do YY")} - {moment(sprint.end_date).format("MMM Do YY")}</h3>
                                {
                                    stories.map((story, key) => {
                                        if(story.pivot.sprint_id === sprint.id) {
                                            return (
                                                <ul key={key} className="collection with-header">
                                                {
                                                    <li key={key+1} className="collection-item">
                                                    {
                                                        (!this.state.storyEditingMode && (this.state.clickedStory === null)) || (!this.state.storyEditingMode && (this.state.clickedStory === story.id)) || (!this.state.storyEditingMode && (this.state.clickedStory !== story.id)) || (this.state.storyEditingMode && (this.state.clickedStory !== story.id)) ?
                                                        <div>
                                                            <div style={{float: 'left', position: 'relative', top: '-5px'}}><Icon small>label_outline</Icon></div> {story.description}
                                                            <a onClick={this.deleteStory.bind(this, story.id)} style={{cursor: 'pointer'}}><i className="material-icons small" style={{color: '#a6262c', float: 'right'}}>delete_forever</i></a>
                                                            <a onClick={this.editStory.bind(this, story.id, story.description)} style={{cursor: 'pointer'}}><i className="material-icons small" style={{color: '#2633a6', float: 'right'}}>mode_edit</i></a>
                                                        </div>
                                                        :
                                                        <div className="row">
                                                            <div className="input-field col s8">
                                                                <input 
                                                                    type="text"
                                                                    placeholder={story.description}
                                                                    onChange={event => this.setState({storyDesc:event.target.value})}
                                                                    onKeyPress={event => {
                                                                    if(event.key === "Enter") {
                                                                            this.editStory(story.id, null);
                                                                        }
                                                                    }}
                                                                />
                                                            </div>
                                                            <a onClick={() => {this.editStory(story.id, null)}} style={{cursor: 'pointer'}}><i className="material-icons small" style={{color: '#2633a6', float: 'right'}}>mode_edit</i></a>
                                                        </div>
                                                    }
                                                    </li>
                                                }
                                                </ul>
                                            )
                                        }

                                    })
                                }

                                { !this.state.userStoryCheck ?
                                    <div>
                                    <input 
                                        className="validate"
                                        type="text"
                                        placeholder="Add Your User Story"
                                        onChange={event => this.setState({storyDesc:event.target.value})}
                                        onKeyPress={event => {
                                        if(event.key === "Enter") {
                                                this.createStory(sprint.id);
                                            }
                                        }}
                                    />
                                    <p>
                                        <input type="checkbox" className="filled-in" id={sprint.id} onClick={this.handleCheck.bind(this)} />
                                            <label htmlFor={sprint.id}>Check to select a User Story</label>
                                    </p>
                                </div>
                                :
                                <div>
                                    <Dropdown trigger={
                                        <Button defaultValue={this.state.selectValue} onChange={this.handleChange}>Select User Story</Button>
                                        }>
                                        {this.createStorySelect(sprint.id)}
                                    </Dropdown>
                                    <p>
                                    <input type="checkbox" className="filled-in" id={sprint.id} onClick={this.handleCheck.bind(this)} />
                                        <label htmlFor={sprint.id}>Check to manually add a User Story</label>
                                    </p>
                                </div>
                                }
                                <a 
                                    className="waves-effect waves-light btn-large"
                                    onClick={() => this.goToTasks(sprint.id)}
                                >
                                Go To Sprint Board
                                </a>
                            </Tab>
                        )
                    })
                }
                </Tabs>
            </div>
        )
    }

    render() {
        return (
            <div>
                <nav className="teal lighten-3">
                    <div className="nav-wrapper">
                    <a className="brand-logo" href="/"><img className="nav-logo" src={logo} alt="logo"/></a>
                        <ul id="nav-mobile" className="left hide-on-med-and-down" style={{paddingLeft: '180px'}}>
                            <li><a href="/">Projects</a></li>
                            <li><a href="/projects">Backlog</a></li>
                            <li><a onClick={this.saveStoriesToStorage.bind(this)} href="/list">Tasks</a></li>
                        </ul>
                        <ul id="nav-mobile" className="right hide-on-med-and-down" style={{marginRight: '10px'}}>
                            <i className="material-icons" style={{height: 'inherit', lineHeight: 'inherit', float: 'left', margin: '0 30px 0 0', width: '2px'}}>perm_identity</i>
                            <Dropdown trigger={
                                <Button style={{display: 'inline'}}>{this.state.email}</Button>
                                }>
                                <NavItem onClick={this.logOut.bind(this)}><span><span id="nav-icon"><Icon large>input</Icon></span><span style={{position: 'relative', fontSize: '2.5rem', top: '15px', left: '10px', float: 'left'}}>Log Out</span></span></NavItem>
                                <NavItem divider />
                            </Dropdown>
                        </ul>
                    </div>
                </nav>
                <div className="row">
                    <div className="col s2" />
                    <div className="col s8">
                        <h2 style={{color: '#26a69a'}}>{this.state.projectName}: Sprints</h2>
                        <Modal id="sprintModal" trigger={<Button style={{float: 'right', position: 'relative', top: '-70px'}}><Icon small>
                                playlist_add
                            </Icon><span style={{position: 'relative', top: '-4px', marginLeft: '5px'}}>Add Sprint</span></Button>}>
                        <div className="row">
                            <div className="col s2" />
                            <div className="col s7">
                                <h3 style={{paddingLeft: '89px'}}>Add Sprint</h3>
                            </div>
                            <div className="col s3" />
                        </div>
                        <div className="row">
                            <div className="col s3" />
                            <div className="col s6">
                            <Row>
                                <Input style={{width: '175px'}} name='on' type='date' placeholder="Start Date" onChange={event => this.setState({sprintStartDate:event.target.value})} />
                                <Input style={{width: '175px'}} name='on' type='date' placeholder="End Date" onChange={event => this.setState({sprintEndDate:event.target.value})} />
                                <Button
                                    onClick={() => this.addSprint()}
                                >
                                    Add Sprint
                                </Button>
                            </Row>
                            </div>
                            <div className="col s3" />
                        </div>
                        <div className="row">
                            <div className="col s3" />
                            <div className="col s6">
                                {this.renderErrors()}
                            </div>
                            <div className="col s3" />
                        </div>
                        </Modal>
                        <Modal id="userModal" trigger={<Button style={{float: 'right', position: 'relative', top: '-70px', marginRight: '15px'}}>
                            <Icon small>
                                account_box
                            </Icon><span style={{position: 'relative', top: '-4px', marginLeft: '5px'}}>Add User</span></Button>}>
                        <div className="row">
                            <div className="col s2" />
                            <div className="col s7">
                                <h3 style={{paddingLeft: '25px'}}>Add User</h3>
                            </div>
                            <div className="col s3" />
                        </div>
                        <div className="row">
                            <div className="col s2" />
                            <div className="col s1" style={{paddingTop: '25px', paddingLeft: '40px'}}>
                                <Icon small>
                                email    
                                </Icon>
                            </div>
                            <div className="input-field inline col s6">
                                <input 
                                    className="validate"
                                    id="user"
                                    type="email"
                                    defaultValue=""
                                    onChange={event => this.setState({userEmail:event.target.value})}
                                    onKeyPress={event => {
                                    if(event.key === "Enter") {
                                            this.addUser();
                                        }
                                    }}
                                />
                                <label htmlFor="user">Enter User Email</label>
                            </div>
                            <div className="col s3" />
                        </div>
                        <div className="row">
                            <div className="col s3" />
                            <div className="col s6">
                                {this.renderErrors()}
                            </div>
                            <div className="col s3" />
                        </div>
                        </Modal>
                        {this.renderSprints()}
                        <div className="row">
                            { this.renderErrors() }
                        </div>
                        <h2 style={{color: '#26a69a'}}>Contributors</h2>
                        <div className="row">
                            {this.renderUsers()}
                        </div>
                    </div>
                    <div className="col s2" />
                </div>
            </div>
        )
    }
}

export default SprintView;

任何形式的帮助都会受到很多赞赏,因为这个项目将在几天后到期,谢谢!

2 个答案:

答案 0 :(得分:0)

调用this.setState()将始终强制组件重新呈现。

另一种方法是this.forceUpdate()

祝你好运!

答案 1 :(得分:0)

首先 - 考虑将您的组件拆分为更小的组件以便于阅读

我相信您的问题是您在整个组件中使用的Button操作。默认按钮操作是提交导致页面刷新的表单。如果您不希望发生这种情况,则需要在所有功能中考虑到这一点:

handleClick(e) {
   e.preventDefault();

   console.log("No refresh!");
}