admin管理员组

文章数量:1325708

I have a ponent that is supposed to show a list of items. The items are fetched from an API when the ponent first is loaded, and then the items should be refreshed/replaced when the user clicks a button.

The problem is, my ponent always seems to be one step behind the user. For example, the first time the user clicks the button the API is called and returns new data, but the ponent still shows the data it got on initial load. The second time the user clicks, my ponent shows the data that was returned on the first click.

I'm thinking that the ponent is reloaded to quick after the button click, so the state isn't updated when the ponent is displayed, but I have no idea how to solve it?

My ponent:

import React, {Component} from 'react';
import {connect} from 'react-redux';
import {matchesFetchData} from '../actions/matches';
import Match from './match';


class MatchList extends Component {
    constructor(){
        super();
        this.generateNew = this.generateNew.bind(this);
    }  

    ponentDidMount() {
        this.props.fetchData('http://localhost:4752/api/matches');
    }

    render() {
        if (this.props.hasErrored) {
            return <p>Sorry!There was an error loading the items</p>;
        }
        if (this.props.isLoading) {
            return <p>Loading…</p>;
        }

        return (   
            <div>
                <nav className="menu">
                    <input type="number" defaultValue="0" min="0" max="13" ref="safeguards"/>      
                    <button className="generateNew" onClick={this.generateNew}>Ny</button>         
                </nav>
                <ul className="matchlist">
                    {this.props.matches.map(function (match, index) {                        
                        return <Match key={index} match={match}/>
                    }) }
                </ul>
            </div>
        )
    }

    generateNew(e){
        e.preventDefault();
        const value = this.refs.safeguards.value;
        if(value == 0){
            this.props.fetchData('http://localhost:4752/api/matches');
        }
        else{
            this.props.fetchData('http://localhost:4752/api/matches/' + value);
        }
    }
}

const mapStateToProps = (state) => {
    return {
        matches: state.matchesApp.matches,
        hasErrored: state.matchesApp.matchesHasErrored,
        isLoading: state.matchesApp.matchesIsLoading
    };
};


const mapDispatchToProps = (dispatch) => {
    return {
        fetchData: (url) => dispatch(matchesFetchData(url))
    };
};  

export default connect(mapStateToProps, mapDispatchToProps)(MatchList);

Actions:

export const MATCHES_HAS_ERRORED = 'MATCHES_HAS_ERRORED';
export const MATCHES_IS_LOADING = 'MATCHES_IS_LOADING';
export const MATCHES_FETCH_DATA_SUCCESS = 'MATCHES_FETCH_DATA_SUCCESS';

export function matchesHasErrored(bool){
    return {
        type: MATCHES_HAS_ERRORED,
        hasErrored: bool
    };
}

export function matchesIsLoading(bool){
    return {
        type: MATCHES_IS_LOADING,
        isLoading: bool
    };
}

export function matchesFetchDataSuccess(matches){
    return{
        type: MATCHES_FETCH_DATA_SUCCESS,
        matches
    };
}

export function matchesFetchData(url){
    return (dispatch) => {
        dispatch(matchesIsLoading(true));

        fetch(url)
        .then((response) => {
            if(!response.ok){
                throw Error(response.statusText);
            }

            dispatch(matchesIsLoading(false));

            return response;
        })
        .then((response) => response.json())
        .then((matches) => dispatch(matchesFetchDataSuccess(matches)))
        .catch(() => dispatch(matchesHasErrored(true)));
    }
}

Reducer:

import {MATCHES_HAS_ERRORED, MATCHES_IS_LOADING, MATCHES_FETCH_DATA_SUCCESS} from '../actions/matches';

const initialState = {
    matchesIsLoading: false,
    matchesHasErrored: false,
    matches: []
}

export function matchesApp(state = initialState, action){
    switch(action.type){
      case MATCHES_HAS_ERRORED:
            return Object.assign({}, state, {
                matchesHasErrored: action.hasErrored
            });
      case MATCHES_IS_LOADING:
            return Object.assign({}, state, {
                matchesIsLoading: action.isLoading
            });
      case MATCHES_FETCH_DATA_SUCCESS:
            return Object.assign({}, state, {
                matchesIsLoading: false,
                matchesHasErrored: false,
                matches: action.matches
            });
      default:
            return state;
    }
}

Match ponent:

import React, {Component} from 'react';
import {Link} from 'react-router-dom';
import Result from './result';

class Match extends Component {
    constructor(props){
        super(props);
        this.match = this.props.match;
    }    

    render() {
        return (
            <li>
                <Link to={'/' + this.match.home + '/' + this.match.away}>
                    <span className="matchNumber">{this.match.number}</span>
                    <span className="matchup">
                        {this.match.home} - {this.match.away}
                    </span>
                    <Result value="1" selected={this.match.homeWin} odds={this.match.percent1}/><Result value="X" selected={this.match.draw} odds={this.match.percentX}/><Result value="2" selected={this.match.awayWin} odds={this.match.percent2}/>
                </Link>
            </li>
        )
    }
}

export default Match;

I have a ponent that is supposed to show a list of items. The items are fetched from an API when the ponent first is loaded, and then the items should be refreshed/replaced when the user clicks a button.

The problem is, my ponent always seems to be one step behind the user. For example, the first time the user clicks the button the API is called and returns new data, but the ponent still shows the data it got on initial load. The second time the user clicks, my ponent shows the data that was returned on the first click.

I'm thinking that the ponent is reloaded to quick after the button click, so the state isn't updated when the ponent is displayed, but I have no idea how to solve it?

My ponent:

import React, {Component} from 'react';
import {connect} from 'react-redux';
import {matchesFetchData} from '../actions/matches';
import Match from './match';


class MatchList extends Component {
    constructor(){
        super();
        this.generateNew = this.generateNew.bind(this);
    }  

    ponentDidMount() {
        this.props.fetchData('http://localhost:4752/api/matches');
    }

    render() {
        if (this.props.hasErrored) {
            return <p>Sorry!There was an error loading the items</p>;
        }
        if (this.props.isLoading) {
            return <p>Loading…</p>;
        }

        return (   
            <div>
                <nav className="menu">
                    <input type="number" defaultValue="0" min="0" max="13" ref="safeguards"/>      
                    <button className="generateNew" onClick={this.generateNew}>Ny</button>         
                </nav>
                <ul className="matchlist">
                    {this.props.matches.map(function (match, index) {                        
                        return <Match key={index} match={match}/>
                    }) }
                </ul>
            </div>
        )
    }

    generateNew(e){
        e.preventDefault();
        const value = this.refs.safeguards.value;
        if(value == 0){
            this.props.fetchData('http://localhost:4752/api/matches');
        }
        else{
            this.props.fetchData('http://localhost:4752/api/matches/' + value);
        }
    }
}

const mapStateToProps = (state) => {
    return {
        matches: state.matchesApp.matches,
        hasErrored: state.matchesApp.matchesHasErrored,
        isLoading: state.matchesApp.matchesIsLoading
    };
};


const mapDispatchToProps = (dispatch) => {
    return {
        fetchData: (url) => dispatch(matchesFetchData(url))
    };
};  

export default connect(mapStateToProps, mapDispatchToProps)(MatchList);

Actions:

export const MATCHES_HAS_ERRORED = 'MATCHES_HAS_ERRORED';
export const MATCHES_IS_LOADING = 'MATCHES_IS_LOADING';
export const MATCHES_FETCH_DATA_SUCCESS = 'MATCHES_FETCH_DATA_SUCCESS';

export function matchesHasErrored(bool){
    return {
        type: MATCHES_HAS_ERRORED,
        hasErrored: bool
    };
}

export function matchesIsLoading(bool){
    return {
        type: MATCHES_IS_LOADING,
        isLoading: bool
    };
}

export function matchesFetchDataSuccess(matches){
    return{
        type: MATCHES_FETCH_DATA_SUCCESS,
        matches
    };
}

export function matchesFetchData(url){
    return (dispatch) => {
        dispatch(matchesIsLoading(true));

        fetch(url)
        .then((response) => {
            if(!response.ok){
                throw Error(response.statusText);
            }

            dispatch(matchesIsLoading(false));

            return response;
        })
        .then((response) => response.json())
        .then((matches) => dispatch(matchesFetchDataSuccess(matches)))
        .catch(() => dispatch(matchesHasErrored(true)));
    }
}

Reducer:

import {MATCHES_HAS_ERRORED, MATCHES_IS_LOADING, MATCHES_FETCH_DATA_SUCCESS} from '../actions/matches';

const initialState = {
    matchesIsLoading: false,
    matchesHasErrored: false,
    matches: []
}

export function matchesApp(state = initialState, action){
    switch(action.type){
      case MATCHES_HAS_ERRORED:
            return Object.assign({}, state, {
                matchesHasErrored: action.hasErrored
            });
      case MATCHES_IS_LOADING:
            return Object.assign({}, state, {
                matchesIsLoading: action.isLoading
            });
      case MATCHES_FETCH_DATA_SUCCESS:
            return Object.assign({}, state, {
                matchesIsLoading: false,
                matchesHasErrored: false,
                matches: action.matches
            });
      default:
            return state;
    }
}

Match ponent:

import React, {Component} from 'react';
import {Link} from 'react-router-dom';
import Result from './result';

class Match extends Component {
    constructor(props){
        super(props);
        this.match = this.props.match;
    }    

    render() {
        return (
            <li>
                <Link to={'/' + this.match.home + '/' + this.match.away}>
                    <span className="matchNumber">{this.match.number}</span>
                    <span className="matchup">
                        {this.match.home} - {this.match.away}
                    </span>
                    <Result value="1" selected={this.match.homeWin} odds={this.match.percent1}/><Result value="X" selected={this.match.draw} odds={this.match.percentX}/><Result value="2" selected={this.match.awayWin} odds={this.match.percent2}/>
                </Link>
            </li>
        )
    }
}

export default Match;
Share Improve this question edited May 5, 2017 at 6:32 spersson asked May 3, 2017 at 14:22 sperssonspersson 5501 gold badge8 silver badges19 bronze badges 5
  • I looked your code over. Looks correct. Have you tried inspecting what happens the first time the button is clicked? – Gilbert Nwaiwu Commented May 3, 2017 at 15:19
  • Also you can put log things in your reducer and actions to see if they are being called. – Gilbert Nwaiwu Commented May 3, 2017 at 15:20
  • @GilbertNwaiwu When I click the button, the reducer logs the new, correct data. The action also logs the new, correct data. But if I put a log in the render function of my ponent it logs twice, first the old data, then the new. But it's the old data that is displayed. – spersson Commented May 4, 2017 at 7:07
  • The second time the render call gets called did you try logging the value of the match data before returning your JSX? – Gilbert Nwaiwu Commented May 4, 2017 at 8:16
  • I've put a console.log(this.props.matches) right before render(), aswell as inside the render function right before {this.props.matches.map()}. Both have the same result: two entries in the console, the first one with the old data and the second one with the new data. – spersson Commented May 4, 2017 at 8:23
Add a ment  | 

2 Answers 2

Reset to default 8

Depending upon what the ponent looks like, it is possible that there is a problem with the key property that you are passing down, where if the display value of the es from state, the ponent will not be pletely removed, and thus will display stale data

Can you replace this:

<ul className="matchlist">
  {this.props.matches.map(function (match, index) {                        
    return <Match key={index} match={match}/>
  }) }
</ul>

with this:

let matchesData = this.props.matches.map(function (match, index) {                        
  return <Match key={index} match={match}/>
}) ;

...
return(
  <div>
    <nav className="menu">
      <input type="number" defaultValue="0" min="0" max="13" ref="safeguards"/>      
      <button className="generateNew" onClick={this.generateNew}>Ny</button>         
    </nav>
    <ul className="matchlist">
      {matchesData}
    </ul>
  </div>
)

本文标签: javascriptReactjs component showing old dataStack Overflow