Tout d'abord mon code fonctionne (tout est exporté correctement etc) mais il n'attend pas le retour asynchrone des données. J'utilise redux-thunk pour mon middleware asynchrone

J'ai une action nommée async.js

export function itemsHasErrored(bool) {
    return {
        type: 'ITEMS_HAS_ERRORED',
        hasErrored: bool
    };
}

export function itemsIsLoading(bool) {
    return {
        type: 'ITEMS_IS_LOADING',
        isLoading: bool
    };
}

export function itemsFetchDataSuccess(items) {
    return {
        type: 'ITEMS_FETCH_DATA_SUCCESS',
        items
    };
}

export function itemsFetchData(url) {
    return (dispatch) => {
        dispatch(itemsIsLoading(true));

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

                dispatch(itemsIsLoading(false));

                return response;
            })
            .then((response) => response.json())
            .then((items) => dispatch(itemsFetchDataSuccess(items)))
            .catch(() => dispatch(itemsHasErrored(true)));
    };
}

Mon réducteur

export function itemsHasErrored(state = false, action) {
    switch (action.type) {
        case 'ITEMS_HAS_ERRORED':
            return action.hasErrored;

        default:
            return state;
    }
}

export function itemsIsLoading(state = false, action) {
    switch (action.type) {
        case 'ITEMS_IS_LOADING':
            return action.isLoading;

        default:
            return state;
    }
}

export function items(state = [], action) {
    switch (action.type) {
        case 'ITEMS_FETCH_DATA_SUCCESS':
            return action.items;

        default:
            return state;
    }
}

J'ai un composant conteneur, asyncContainer.js

import React, {Component} from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux'

import {itemsFetchData} from '../../../actions/async';
import AsyncUI from './asyncUI'

class AsyncContainer extends Component {

    componentDidMount() {
        this.props.fetchData('http://5826ed963900d612000138bd.mockapi.io/items');
    }

    render() {

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

        if (this.props.isLoading) {
            return <p>Loading…</p>;
        }
//This fails to wait
        return (
               <AsyncUI />
        );
    }
}

AsyncContainer.propTypes = {
    fetchData: PropTypes.func.isRequired,
    items: PropTypes.array.isRequired,
    hasErrored: PropTypes.bool.isRequired,
    isLoading: PropTypes.bool.isRequired
};

const mapStateToProps = (state) => {
    return {
        items: state.items,
        hasErrored: state.itemsHasErrored,
        isLoading: state.itemsIsLoading
    };
};

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

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

Et enfin, j'ai un composant d'interface utilisateur simple nommé asyncUI.js écrit de manière fonctionnelle

import React, {Component} from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux'

const AsyncUI = (items) => {

    return (
        <ul>
            {items.map((item) => (
                <li key={item.id}>
                    {item.label}
                </li>
            ))}
        </ul>
    );
}

const mapStateToProps = (state) => {
    return {
        items: state.items
    };
};

export default connect(mapStateToProps)(AsyncUI);

Dans asyncContainer.js , vous pouvez voir l'appel à mon composant d'interface utilisateur simple

 return (
               <AsyncUI />
        );

Mais en appelant la propriété du redux store items dans asyncUI.js un tableau vide, donc le items.map échoue

Cependant , si je supprime le code de asyncUI.js et le place dans asyncContainer.js cela fonctionne

C'est le code qui fonctionne dans asyncContainer.js

  class AsyncContainer extends Component {
        componentDidMount() {
            this.props.fetchData('http://5826ed963900d612000138bd.mockapi.io/items');
        }

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

            if (this.props.isLoading) {
                return <p>Loading…</p>;
            }

//THIS IS WHERE I HAD <Async />
            return (
                <ul>
                    {this.props.items.map((item) => (
                        <li key={item.id}>
                            {item.label}
                        </li>
                    ))}
                </ul>
            );
        }
    }

    AsyncContainer.propTypes = {
        fetchData: PropTypes.func.isRequired,
        items: PropTypes.array.isRequired,
        hasErrored: PropTypes.bool.isRequired,
        isLoading: PropTypes.bool.isRequired
    };

    const mapStateToProps = (state) => {
        return {
            items: state.items,
            hasErrored: state.itemsHasErrored,
            isLoading: state.itemsIsLoading
        };
    };

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

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

Je pense que le problème est que le composant est rendu avant que les données des éléments ne soient prêtes. C'est un comportement de réaction normal. Alors, comment puis-je "retarder" le rendu. Comme vous pouvez le voir, j'essaie d'utiliser un style d'architecture Conteneur / Composant. Je peux toujours utiliser la solution qui fonctionne comme mentionné ci-dessus, mais j'aimerais m'en tenir à ce conteneur / composant. Vais-je devoir approfondir mes promesses, etc.? Ne devrais-je pas utiliser la manière fonctionnelle d'écrire pour asyncUI.js? Je suis un peu confus.

0
Rory 21 avril 2017 à 12:10

3 réponses

Meilleure réponse

Essayer:

const AsyncUI = ({items}) => {
              /* ^ see ^ */
    return (
        <ul>
            {items.map((item) => (
                <li key={item.id}>
                    {item.label}
                </li>
            ))}
        </ul>
    ); }

Cela extrait la valeur items des accessoires que vous avez réagis dans la fonction mapStateToProps, qui est un objet, pas un tableau (donc pas de fonction map).

REMARQUE: Cela devrait résoudre votre problème, mais il est encore techniquement d'essayer de rendre les éléments avant qu'ils ne soient prêts dans 2 cas:

  1. La première fois que le composant est rendu. L'état initial de itemsIsLoading est false, donc le premier rendu échouera à tous les contrôles de sécurité. La valeur par défaut de items est [] donc il devrait simplement restituer <ul></ul> pendant un très bref moment jusqu'à ce que l'action itemsIsLoading(true) soit distribuée. Vous pouvez définir l'état initial sur true pour arrêter cela, ou modifier le contrôle de chargement pour être

    if (this.props.isLoading || this.props.items.length != 0) {
        return <p>Loading…</p>;
    }
    

    On peut argumenter sur la nécessité de l’une ou l’autre de ces solutions.

  2. Une fois que fetch a renvoyé l'ordre dans lequel les actions sont réparties, il en résultera un autre bref rendu de <ul></ul> car l'état de chargement est défini sur false avant que les éléments ne soient définis. Consultez la réponse de dgrijuela pour trouver un moyen de résoudre ce problème. Une autre manière serait de ne pas distribuer d'actions séparées et de faire en sorte que les actions ITEMS_FETCH_DATA_SUCCESS et ITEMS_HAS_ERRORED remettent également la valeur itemsIsLoading à false (plusieurs réducteurs peuvent agir sur le même type d'action).

2
Community 23 mai 2017 à 12:34

Vous appelez dispatch(itemsIsLoading(false)) avant dispatch(itemsFetchDataSuccess(items))

Essayez comme ceci:

// async.js
...

export function itemsFetchData(url) {
  return (dispatch) => {
    dispatch(itemsIsLoading(true));

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

            return response;
        })
        .then((response) => response.json())
        .then((items) => {
          dispatch(itemsFetchDataSuccess(items));
          dispatch(itemsIsLoading(false));
        })
        .catch(() => dispatch(itemsHasErrored(true)));
    };
}
0
dgrijuela 21 avril 2017 à 10:00

Voir Michael Peyper pour une bonne réponse

Il s'avère que le problème venait du style fonctionnel de codage de mon composant asyncUI . Je l'ai reconverti en composant avec état «standard» et le bingo a fonctionné.

AsyncContainer.js

class AsyncContainer extends Component {

    componentDidMount() {
        this.props.fetchData('http://5826ed963900d612000138bd.mockapi.io/items');
    }

    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 (
                  <AsyncUI />     
        )

    }
}

asyncUI.js

import React, {Component} from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux'

    class AsyncUI extends Component {

        render() {
                return (
                <ul>
                {this.props.items.map((item) => (
                    <li key={item.id}>
                        {item.label}
                    </li>
                ))}
            </ul>   
            )
        }
    }

    const mapStateToProps = (state) => {
        return {
            items: state.items,
        };
    };

export default connect(mapStateToProps)(AsyncUI);

J'espère que cela aide tout le monde :-)

0
Rory 21 avril 2017 à 11:38