J'apprends Multer
avec Redux
et React
.
Mon routeur express
est comme
render() { console.log(this.props.uploadImage); } const mapStateToProps = state => ( { uploadImage: state.addressReducer.uploadImage } ); export default connect(mapStateToProps)(ModalElement);
Mon code Multer
est comme ci-dessous
const addressReducer = (state = initialState, action) => { switch (action.type) { case 'getAddresses': { return { ...state, controlModal: action.payload.valueModal, address: action.payload.addressData }; } case 'uploadImage': { return { ...state, uploadImage: action.payload }; } default: return state; } };
Mon action est comme ci-dessous
export const uploadImage = (formData, id, config) => dispatch => { return Axios.post('/api/address/upload', formData, config) .then(response => { dispatch({ type: 'uploadImage', payload: response.data }); }) .catch(error => { dispatch({ type: 'uploadImage', payload: error // I would like to pass error through here. }); return false; }); };
Mon réducteur est comme ci-dessous
const uploadImage = (req, res, next) => { const storage = multer.diskStorage({ destination: function(req, file, cb) { cb(null, './uploads/'); }, filename: function(req, file, cb) { cb(null, Date.now() + '-' + file.originalname); } }); const fileFilter = (req, file, cb) => { if (file.mimetype === 'image/jpeg' || file.mimetype === 'image/png') { cb(null, true); } else { cb(new Error('Try to upload .jpeg or .png file.'), false); } }; const upload = multer({ storage: storage, limits: { fileSize: 1024 * 1024 * 5 }, fileFilter: fileFilter }).single('addressImage'); upload(req, res, function(error) { if (error) { // An error occurred when uploading res.status(500).json({ message: error // I would like to send error from Here. }); console.log(error); } else { if (req.file.filename === res.req.res.req.file.filename) { res.status(200).json({ message: 'File uploaded', file: req.file.filename }); } return; } }); }
Je voudrais j'aime obtenir une erreur dans mon composant est comme ci-dessous
router.post('/upload', addressController.uploadImage);
Ma sortie de console est comme ci-dessous
Comment puis-je obtenir une erreur Essayer de télécharger un fichier .jpeg ou .png.
dans mon composant React pendant que j'essaie de télécharger un fichier sans l'extension .jpeg et .png?
3 Réponses :
vous n'êtes pas obligé d'envoyer 500 codes d'état à la place, vous devez envoyer 400
res.status(400).json({ message: error // I would like to send error from Here. });
Merci @Vikash Singh. J'ai utilisé ce code res.status (200) .json ({error});
et j'ai obtenu cette erreur i.stack.imgur.com/s9Ax8.png.
Il indique qu'une erreur de stockage peut être due à une manière incorrecte d'accorder le chemin du répertoire ou les autorisations de répertoire. Essayez de donner le chemin absolu complet et d'attribuer les autorisations appropriées.
Merci @Vikash Singh. Le chemin du répertoire est correct. Si j'utilise .png ou .jpeg, tout fonctionne correctement. Je souhaite transmettre le message de validation à React. Si j'utilise if (error) {console.log (error); }
le message est imprimé dans le Terminal. Merci.
res.status (400) .json ({erreur});
cela devrait résoudre
Bonjour, Pouvez-vous développer l'objet request
dans votre capture d'écran précédente et me montrer ce qu'il contient?
L ' Erreur
ne se résout pas en un json valide lorsqu'elle est transmise via res.json ()
et donc, elle est supprimée.
pour accéder au message "Essayez de télécharger un fichier .jpeg ou .png."
, vous devez mettre à jour le code Multer
comme ceci:
.catch(error => { dispatch({ type: "uploadImage", /** error.data is the response. We want the `message` property from it */ payload: error.data.message // I would like to pass error through here. }); return false; });
Voici comment j'ai pu y parvenir pour un microservice d'avatar que j'ai créé pour fonctionner avec mon application principale.
AVERTISSEMENT : Cette explication parcourt tout le flux, elle peut donc être longue et redondante si vous la comprenez déjà.
Tout d'abord, vous devez créer une configuration axios
. Par défaut, axios
n'affichera pas le err
renvoyé par le serveur, à la place, il affichera simplement un objet Error
générique. Vous devrez mettre en place un intercepteur
utils/axiosConfig.js[
class RenderMessages extends Component { shouldComponentUpdate = nextProps => this.props.serverError !== '' || nextProps.serverError !== '' || this.props.serverMessage !== '' || nextProps.serverMessage !== ''; componentDidUpdate = () => { const { serverError, serverMessage } = this.props; if (serverError || serverMessage) { const notification = serverError ? serverErrorMessage(serverError) : serverSuccessMessage(serverMessage); this.renderNotification(...notification); } }; renderNotification = ({ noteType, description }) => { notification[noteType]({ message: noteType === 'error' ? 'Error' : 'Update', description, icon: descriptionLayout(noteType), }); setTimeout(() => this.props.resetServerMessages(), 3000); }; render = () => null; } export default connect( state => ({ serverError: state.server.error, // retrieving the error from redux state serverMessage: state.server.message, }), { resetServerMessages }, )(RenderMessages);
Un utilisateur soumet un formulaire avec formData
et cela déclenche un créateur action
:
uploadAvatar thunk action creator (qui est une promesse en attente d'une réponse
ou erreur de notre serveur)
:
import * as types from 'types'; const serverInitialState = { error: '', message: '', }; const ServerReducer = (state = serverInitialState, { payload, type }) => { switch (type) { case types.RESET_SERVER_MESSAGES: return { ...state, error: '' }; case types.SERVER_ERROR: return { ...state, error: payload }; // the server err is stored to redux state as "state.server.error" case types.SERVER_MESSAGE: return { ...state, message: payload }; default: return state; } }; export default ServerReducer;
La requête POST
est sélectionnée par notre route express
:
.catch(err => // our server "err" is passed to here from the interceptor dispatch({ type: types.SERVER_ERROR, payload: err.toString() }), // then that "err" is passed to a reducer );
La requête atteint cette route: '/ api / avatar / create' , passe par une fonction middleware (voir ci-dessous) avant de passer par une autre fonction middleware
saveImage
, avant de finalement passer par un contrôleur create
.
Le serveur renvoie une réponse au client. La réponse de notre serveur passe par le axios
configuration interceptor
, qui détermine comment gérer la réponse
ou l ' erreur
qui a été renvoyé de notre serveur. Il transmettra ensuite la réponse
ou error
au .then ()
ou .catch ()
du Créateur de action
. Le créateur de l ' action
la transmet au réducteur
, qui met à jour l'état de redux
, qui met ensuite à jour le composant connect
ed .
Partout où vous définissez vos middlewares express
(ex: bodyParser
, cors
ou passeport
etc.), vous voudrez créer une fonction middleware multer
(chaque fois qu'un fichier est téléchargé, il passe par cette fonction premier):
avatarAPI.interceptors.response.use( response => response, error => { const err = get(error, ['response', 'data', 'err']); // this checks if "error.response.data.err" is present; which it is, and is now "That file extension is not accepted!" return err ? Promise.reject(err) : Promise.reject(error.message); // that err string gets returned to our uploadAvatar action creator's "catch" block }, );
services /saveImage.js (après avoir traversé la fonction middleware ci-dessus, le résultat est passé à cette fonction middleware saveImage
)
const fs = require("fs"); const sharp = require("sharp"); const { createRandomString } = require('../../utils/helpers'); module.exports = (req, res, next) => { // if the file failed to pass the middleware function above, we'll return the "req.err" as "err" or return a string if "req.file" is undefined. In short, this returns an "error.response.data.err" to the client. if (req.err || !req.file) { return res.status(400).json({ err: req.err || "Unable to process file." }); } const randomString = createRandomString(); const filename = `${Date.now()}-${randomString}-${req.file.originalname}`; const filepath = `uploads/${filename}`; const setFile = () => { req.file.path = filepath; return next(); }; /\.(gif|bmp)$/i.test(req.file.originalname) ? fs.writeFile(filepath, req.file.buffer, (err) => { if (err) return res.status(400).json({ "Unable to process file." }); setFile(); }) : sharp(req.file.buffer) .resize(256, 256) .max() .withoutEnlargement() .toFile(filepath) .then(() => setFile()); };
Si ce qui précède passe, il passe ensuite req
(qui contient req.file
et toutes ses propriétés) au contrôleur create
, qui dans mon cas, stocke un chemin vers le fichier (/uploads/name-of-file.ext), et un str pour récupérer l'image ( http: // localhost: 4000 / uploads / name-of- file.ext ) dans ma base de données. Dans mon cas, cette chaîne est ensuite renvoyée au client pour être stockée dans l'état redux, puis mise à jour en tant qu'avatar de l'utilisateur (lors du passage d'une chaîne dans un
, il envoie une requête GET
au microservice).
Disons à un utilisateur a essayé de télécharger une image .tiff
. Il passe par notre fonction middleware express
multer, qui déclenche l'erreur "Cette extension de fichier n'est pas acceptée!"
, cette erreur est renvoyée via req.err code> à
saveImage
, qui renvoie le req.err
comme: return res.status (400) .json ({err: req.err});
Du côté client, cet err
traverse notre axios interceptor
:
app.use(cors({ origin: "http://localhost:3000" })); app.use(bodyParser.json()); app.use( multer({ limits: { fileSize: 10240000, files: 1, fields: 1 }, fileFilter: (req, file, next) => { if (!/\.(jpe?g|png|gif|bmp)$/i.test(file.originalname)) { req.err = "That file extension is not accepted!"; // this part is important, I'm attaching the err to req (which gets passed to the next middleware function => saveImage) next(null, false); } next(null, true); } }).single("file") ); ...etc
Le bloc catch
du créateur d'action uploadAvatar
est déclenché:
app.post('/api/avatar/create', saveImage, create);
Le réducteur
prend le serveur err
et le stocke dans l'état:
import { avatarAPI } from '../utils/axiosConfig'; // import the custom axios configuration that was created above import * as types from 'types'; const uploadAvatar = formData => dispatch => avatarAPI .post(`avatar/create`, formData) // this makes a POST request to our server -- this also uses the baseURL from the custom axios configuration, which is the same as "http://localhost:4000/api/avatar/create" .then(({ data }) => { dispatch({ type: types.SET_CURRENT_AVATAR, payload: data.avatarurl }); }) .catch(err => // this will return our server "err" string if present, otherwise it'll return a generic Error object. IMPORTANT: Just in case we get a generic Error object, we'll want to convert it to a string (otherwise, if it passes the generic Error object to our reducer, stores it to redux state, passes it to our connected component, which then tries to display it... it'll cause our app to crash, as React can't display objects) dispatch({ type: types.SERVER_ERROR, payload: err.toString() }), );
Un composant connect
ed récupère cet état .server.error
et l'affiche (ne vous inquiétez pas trop de la logique ici, juste que c'est un composant connecté affichant le state.server.error
comme serverError code>):
import get from 'lodash/get'; import axios from 'axios'; export const avatarAPI = axios.create({ baseURL: 'http://localhost:4000/api/', // this makes it easier so that any request will be prepended with this baseURL }); avatarAPI.interceptors.response.use( response => response, // returns the server response error => { const err = get(error, ['response', 'data', 'err']); // this checks if "error.response.data.err" is present (this is the error returned from the server); VERY IMPORTANT: this "err" property is specified in our express middlewares/controllers, so please pay attention to the naming convention. return err ? Promise.reject(err) : Promise.reject(error.message); // if the above is present, return the server error, else return a generic Error object }, );
Le résultat final est Cette extension de fichier n'est pas acceptée! co de> err est affiché à l'utilisateur:
son erreur de serveur interne 500 alors, pouvez-vous s'il vous plaît poster le journal des erreurs que vous obtenez côté serveur?
Merci @VikashSingh. Si j'utilise ce code
upload (req, res, function (error) {if (error) {console.log (error); // Je dois transmettre cette erreur à React} else {if (req.file. filename === res.req.res.req.file.filename) {res.status (200) .json ({message: 'File uploadé', file: req.file.filename});} return;}}) ;
. Je reçois cette sortie i.stack.imgur.com/mJitG.pngIl existe un très beau tutoriel qui résoudra vos doutes concernant multer, veuillez le parcourir une fois: scotch.io/tutorials/express-file-uploads-with-multer J'espère que cela vous aidera. Si vous rencontrez plus de défis, faites-le moi savoir.
selon la capture d'écran, il échoue à la validation du fichier. téléchargez-vous un fichier image .jpeg ou .png?
Merci @VikashSingh. Je voudrais obtenir ce message de validation de fichier dans le composant React.
Merci @VikashSingh. J'ai vu votre tutoriel précédemment mais cela ne se concentre pas sur mon problème.
continuons cette discussion dans le chat .