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.
3 réponses
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:
La première fois que le composant est rendu. L'état initial de
itemsIsLoading
estfalse
, donc le premier rendu échouera à tous les contrôles de sécurité. La valeur par défaut deitems
est[]
donc il devrait simplement restituer<ul></ul>
pendant un très bref moment jusqu'à ce que l'actionitemsIsLoading(true)
soit distribuée. Vous pouvez définir l'état initial surtrue
pour arrêter cela, ou modifier le contrôle de chargement pour êtreif (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.
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 surfalse
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 actionsITEMS_FETCH_DATA_SUCCESS
etITEMS_HAS_ERRORED
remettent également la valeuritemsIsLoading
à false (plusieurs réducteurs peuvent agir sur le même type d'action).
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)));
};
}
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 :-)
Questions connexes
De nouvelles questions
reactjs
React est une bibliothèque JavaScript pour la création d'interfaces utilisateur. Il utilise un paradigme déclaratif basé sur les composants et vise à être à la fois efficace et flexible.