{"id":1365,"date":"2026-06-23T14:37:54","date_gmt":"2026-06-23T13:37:54","guid":{"rendered":"https:\/\/geekcommunicant.com\/blog\/?p=1365"},"modified":"2026-06-23T14:38:14","modified_gmt":"2026-06-23T13:38:14","slug":"composant-react-choix-image","status":"publish","type":"post","link":"https:\/\/geekcommunicant.com\/blog\/2026\/06\/23\/composant-react-choix-image\/","title":{"rendered":"Un composant React de choix d&rsquo;image"},"content":{"rendered":"\n<p class=\"wp-block-paragraph\">Durant le d\u00e9veloppement de ma premi\u00e8re application fullstack avec React (JavaScript \u00ab\u00a0natif\u00a0\u00bb d&rsquo;abord puis TypeScript) et FastApi , j&rsquo;ai \u00e9t\u00e9 amen\u00e9 \u00e0 cr\u00e9er un composant d&rsquo;upload de fichier image.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Principe de fonctionnement<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Ce composant est pr\u00e9vu pour \u00eatre utilis\u00e9 comme \u00ab\u00a0champ\u00a0\u00bb d&rsquo;un formulaire, et utilise des donn\u00e9es formatt\u00e9es comme suit :<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: jscript; title: ; notranslate\" title=\"\">\n{\n   mode: &#039;current&#039; | &#039;delete&#039; | &#039;new&#039;, \/\/ utilis\u00e9 c\u00f4t\u00e9 back\n   currentUrl: &#039;user-profile.png&#039;,\n   file: objet File()\n}\n<\/pre><\/div>\n\n\n<p class=\"wp-block-paragraph\">Au niveau visuel, d&rsquo;un c\u00f4t\u00e9 on affiche l&rsquo;image actuelle ou celle par d\u00e9faut ou la nouvelle image, de l&rsquo;autre, des boutons radio permettant de soit garder l&rsquo;image actuelle, soit l&rsquo;effacer (et la remplacer par l&rsquo;image par d\u00e9faut), soit la remplacer.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Chacune des images mentionn\u00e9es ci-dessus est affich\u00e9e dans sa propre balise <img> et est masqu\u00e9e ou affich\u00e9e celon le besoin.<\/p>\n\n\n\n<!--more-->\n\n\n\n<h2 class=\"wp-block-heading\">Le code<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">D&rsquo;abord, on d\u00e9clare le type pour le mode :<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: jscript; title: ; notranslate\" title=\"\">\ntype AvatarMode = &#039;current&#039; | &#039;delete&#039; | &#039;new&#039;;\n<\/pre><\/div>\n\n\n<p class=\"wp-block-paragraph\">Ensuite, le type des donn\u00e9es principales de ce composant :<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: jscript; title: ; notranslate\" title=\"\">\ninterface AvatarState {\n   mode: AvatarMode;\n   currentUrl?: string;\n   file?: File;\n}\n<\/pre><\/div>\n\n\n<p class=\"wp-block-paragraph\">Notre composant prend 3 props :<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>la valeur actuelle (de type <code>AvatarState<\/code>)<\/li>\n\n\n\n<li>une fonction fournie par le composant parent pour assurer la mise \u00e0 jour des donn\u00e9es<\/li>\n\n\n\n<li>une URL de l&rsquo;image par d\u00e9faut<\/li>\n<\/ul>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: jscript; title: ; notranslate\" title=\"\">\ninterface AvatarChooserProps {\n   value: AvatarState;\n   onChange: (data: AvatarState) =&gt; void;\n   defaultAvatar?: string;\n}\n<\/pre><\/div>\n\n\n<p class=\"wp-block-paragraph\">Au niveau des hooks React, j&rsquo;en utilise 4 :<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><code>const fileInputRef = useRef(null); <\/code>, pour r\u00e9f\u00e9rencer l&rsquo;input de type \u00ab\u00a0file\u00a0\u00bb<\/li>\n\n\n\n<li>un state bool\u00e9en pour indiquer si la nouvelle image est bien charg\u00e9e en m\u00e9moire : <code>const [isNewImageLoaded, setIsNewImageLoaded] = useState(false);<\/code><\/li>\n\n\n\n<li>un memo pour charger la nouvelle image en tant qu&rsquo;objet URL :<br><code>const previewUrl = useMemo(() => {<br>   return value.file ? URL.createObjectURL(value.file) : null;<br>}, [value.file]);<\/code><\/li>\n\n\n\n<li>un effect pour s&rsquo;assurer de bien nettoyer l&rsquo;objet URL au d\u00e9montage du composant : <code>useEffect(() => {<br>   return () => {<br>      if (previewUrl) {<br>         URL.revokeObjectURL(previewUrl);<br>      }<br>   };<br>}, [previewUrl]);<\/code><\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">Ensuite, deux handlers pour les champs radio et l&rsquo;input de fichier :<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: jscript; title: ; notranslate\" title=\"\">\nconst radioHandler = (e: React.ChangeEvent) =&gt; {\n   onChange({ \u2026value, mode: e.target.value as AvatarMode }); \/\/ on envoie directement la nouvelle valeur au composant parent\n};\n<\/pre><\/div>\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: plain; title: ; notranslate\" title=\"\">\nconst fileHandler = (e: React.ChangeEvent) =&gt; {\n   const file = e.target.files?.&#x5B;0];\n   if (!file) {\n      return;\n   }\n\n   setIsNewImageLoaded(false);\n\n   onChange({\n      ...value,\n      mode: &#039;new&#039;,\n      file,\n   });\n};\n<\/pre><\/div>\n\n\n<p class=\"wp-block-paragraph\">Pour l&rsquo;image actuelle, comme elle peut \u00eatre d\u00e9j\u00e0 celle par d\u00e9faut (pas d&rsquo;image pr\u00e9cise enregistr\u00e9e en base), il faut une rapide logique pour la d\u00e9terminer :<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: jscript; title: ; notranslate\" title=\"\">\nconst currentAvatarUrl = value.currentUrl ?\n   `${import.meta.env.VITE_APP_STATIC_FILES_URL}\/${value.currentUrl`\n   : defaultAvatar;\n<\/pre><\/div>\n\n\n<p class=\"wp-block-paragraph\">Ensuite, il ne reste plus qu&rsquo;\u00e0 calculer les flags pour l&rsquo;image \u00e0 afficher :<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: jscript; title: ; notranslate\" title=\"\">\nconst showNew = value.mode === &#039;new&#039; &amp;&amp; previewUrl &amp;&amp; isNewImageLoaded;\nconst showCurrent = value.mode === &#039;current&#039; &amp;&amp; !showNew;\nconst showDefault = value.mode === &#039;delete&#039; || (value.mode === &#039;new&#039; &amp;&amp; !showNew);\n<\/pre><\/div>\n\n\n<p class=\"wp-block-paragraph\">Pour voir le code complet, RDV sur <a href=\"https:\/\/github.com\/valvalsal\/fullstack-todo-fastapi-react\/blob\/main\/frontend\/src\/components\/AvatarChooser\/index.tsx\">GitHub<\/a>.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Durant le d\u00e9veloppement de ma premi\u00e8re application fullstack avec React (JavaScript \u00ab\u00a0natif\u00a0\u00bb d&rsquo;abord puis TypeScript) et FastApi , j&rsquo;ai \u00e9t\u00e9 amen\u00e9 \u00e0 cr\u00e9er un composant d&rsquo;upload de fichier image. Principe de fonctionnement Ce composant est pr\u00e9vu pour \u00eatre utilis\u00e9 comme &hellip; <a href=\"https:\/\/geekcommunicant.com\/blog\/2026\/06\/23\/composant-react-choix-image\/\">Continuer la lecture <span class=\"meta-nav\">&rarr;<\/span><\/a><\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_jetpack_newsletter_access":"","_jetpack_dont_email_post_to_subs":true,"_jetpack_newsletter_tier_id":0,"_jetpack_memberships_contains_paywalled_content":false,"_jetpack_feature_clip_id":0,"_jetpack_memberships_contains_paid_content":false,"footnotes":"","jetpack_publicize_message":"","jetpack_publicize_feature_enabled":true,"jetpack_social_post_already_shared":true,"jetpack_social_options":{"image_generator_settings":{"template":"highway","default_image_id":0,"font":"","enabled":false},"version":2},"jetpack_post_was_ever_published":false},"categories":[19],"tags":[76,78],"class_list":["post-1365","post","type-post","status-publish","format-standard","hentry","category-javascript","tag-react","tag-typescript"],"jetpack_publicize_connections":[],"jetpack_featured_media_url":"","jetpack_shortlink":"https:\/\/wp.me\/p2IoOb-m1","jetpack_sharing_enabled":true,"jetpack_likes_enabled":true,"jetpack-related-posts":[{"id":1138,"url":"https:\/\/geekcommunicant.com\/blog\/2023\/03\/17\/quel-est-le-plus-vieux-navigateur\/","url_meta":{"origin":1365,"position":0},"title":"Quel est le plus vieux navigateur ?","author":"geekc","date":"17\/03\/2023","format":false,"excerpt":"Derni\u00e8rement, je me suis pos\u00e9 la question de quel est le plus vieux navigateur web toujours en cours de d\u00e9veloppement. J\u2019ai donc fait quelques recherches que je vais essayer de vous synth\u00e9tiser au mieux ici. Pr\u00e9sentation des candidats J\u2019ai orient\u00e9 mes recherches autour des 5 navigateurs les plus utilis\u00e9s actuellement,\u2026","rel":"","context":"Dans &quot;Divers&quot;","block_context":{"text":"Divers","link":"https:\/\/geekcommunicant.com\/blog\/category\/divers\/"},"img":{"alt_text":"","src":"","width":0,"height":0},"classes":[]},{"id":382,"url":"https:\/\/geekcommunicant.com\/blog\/2014\/08\/15\/menu-mobile-modx\/","url_meta":{"origin":1365,"position":1},"title":"Un menu pour mobile avec MODX","author":"geekc","date":"15\/08\/2014","format":false,"excerpt":"Avec la g\u00e9n\u00e9ralisation des terminaux mobiles (smartphones et tablettes) il est important de prendre en compte la taille r\u00e9duite de leur affichage lors de la conception de votre site. Un \u00e9l\u00e9ment essentiel du design d'un site est le menu principal de navigation. Pour les terminaux mobiles, une des solutions est\u2026","rel":"","context":"Dans &quot;JavaScript&quot;","block_context":{"text":"JavaScript","link":"https:\/\/geekcommunicant.com\/blog\/category\/javascript\/"},"img":{"alt_text":"Menu pour smartphone","src":"https:\/\/i0.wp.com\/geekcommunicant.com\/blog\/wp-content\/uploads\/menu-smartphone.jpg?resize=350%2C200&ssl=1","width":350,"height":200},"classes":[]},{"id":253,"url":"https:\/\/geekcommunicant.com\/blog\/2014\/01\/23\/javascripthtml5-tout-est-dans-le-placement\/","url_meta":{"origin":1365,"position":2},"title":"[JavaScript\/HTML5] Tout est dans le placement","author":"geekc","date":"23\/01\/2014","format":false,"excerpt":"\u00c0 la fin de l'article pr\u00e9c\u00e9dent j'avais utilis\u00e9 une image comme motif. Comme on ne pouvait pas le deviner l'image utilis\u00e9e \u00e9tait la suivante. Les canvas ne permettent pas de placement pr\u00e9cis des motifs. Mais il existe un moyen: le compositage. Cette m\u00e9thode fonctionne un peu comme les calques et\u2026","rel":"","context":"Dans &quot;JavaScript&quot;","block_context":{"text":"JavaScript","link":"https:\/\/geekcommunicant.com\/blog\/category\/javascript\/"},"img":{"alt_text":"","src":"https:\/\/i0.wp.com\/geekcommunicant.com\/blog\/wp-content\/uploads\/summer-girls-13.jpg?resize=350%2C200&ssl=1","width":350,"height":200},"classes":[]},{"id":1000,"url":"https:\/\/geekcommunicant.com\/blog\/2022\/12\/16\/amusons-nous-avec-canvas-retour\/","url_meta":{"origin":1365,"position":3},"title":"Amusons-nous avec les canvas &#8211; le retour","author":"geekc","date":"16\/12\/2022","format":false,"excerpt":"Il y a quelques ann\u00e9es, j\u2019avais \u00e9crit une s\u00e9rie d\u2019articles expliquant comment cr\u00e9er \u00e0 l\u2019aide du\u00a0 tag <canvas> un texte ombr\u00e9 avec une image en fond utilisant une police Google Fonts. La technique ayant un peu \u00e9volu\u00e9 depuis, et ayant fait de nouveaux essais de mon c\u00f4t\u00e9, je pense qu\u2019il\u2026","rel":"","context":"Dans &quot;JavaScript&quot;","block_context":{"text":"JavaScript","link":"https:\/\/geekcommunicant.com\/blog\/category\/javascript\/"},"img":{"alt_text":"","src":"https:\/\/i0.wp.com\/geekcommunicant.com\/blog\/wp-content\/uploads\/image.png?resize=350%2C200&ssl=1","width":350,"height":200},"classes":[]},{"id":326,"url":"https:\/\/geekcommunicant.com\/blog\/2014\/04\/06\/comment-marche-le-web\/","url_meta":{"origin":1365,"position":4},"title":"Comment marche le Web ?","author":"geekc","date":"06\/04\/2014","format":false,"excerpt":"Vous vous \u00eates toujours demand\u00e9 quelle diff\u00e9rence il pouvait y avoir entre le HTML et le PHP, entre le CSS et le JavaScript ? Cet article est fait pour vous. Apr\u00e8s l'avoir lu, vous serez incollables, ou presque, sur les technologies du Web. HTML et CSS Le HTML est la\u2026","rel":"","context":"Dans &quot;Pour les nuls&quot;","block_context":{"text":"Pour les nuls","link":"https:\/\/geekcommunicant.com\/blog\/category\/pour-les-nuls\/"},"img":{"alt_text":"HTML, CSS, et JavaScript","src":"https:\/\/i0.wp.com\/geekcommunicant.com\/blog\/wp-content\/uploads\/logo_html5-300x175.png?resize=350%2C200&ssl=1","width":350,"height":200},"classes":[]},{"id":1143,"url":"https:\/\/geekcommunicant.com\/blog\/2023\/03\/24\/gloubi-script-horloge-illustree\/","url_meta":{"origin":1365,"position":5},"title":"Gloubi script &#8211; une horloge illustr\u00e9e","author":"geekc","date":"24\/03\/2023","format":false,"excerpt":"Aujourd\u2019hui, j\u2019ai d\u00e9cid\u00e9 de m\u2019attaquer \u00e0 la version gloubi d\u2019un script que j\u2019ai \u00e9galement d\u00e9j\u00e0 cr\u00e9\u00e9 de mon c\u00f4t\u00e9\u00a0: l\u2019horloge illustr\u00e9e. La version gloubi est visible ici, tandis que la cr\u00e9ation de ma propre version, faite avec jQuery, est pr\u00e9sent\u00e9e ici. Pr\u00e9sentation de la version gloubi Pour commencer, on cr\u00e9\u00e9\u2026","rel":"","context":"Dans &quot;JavaScript&quot;","block_context":{"text":"JavaScript","link":"https:\/\/geekcommunicant.com\/blog\/category\/javascript\/"},"img":{"alt_text":"","src":"","width":0,"height":0},"classes":[]}],"_links":{"self":[{"href":"https:\/\/geekcommunicant.com\/blog\/wp-json\/wp\/v2\/posts\/1365","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/geekcommunicant.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/geekcommunicant.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/geekcommunicant.com\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/geekcommunicant.com\/blog\/wp-json\/wp\/v2\/comments?post=1365"}],"version-history":[{"count":1,"href":"https:\/\/geekcommunicant.com\/blog\/wp-json\/wp\/v2\/posts\/1365\/revisions"}],"predecessor-version":[{"id":1366,"href":"https:\/\/geekcommunicant.com\/blog\/wp-json\/wp\/v2\/posts\/1365\/revisions\/1366"}],"wp:attachment":[{"href":"https:\/\/geekcommunicant.com\/blog\/wp-json\/wp\/v2\/media?parent=1365"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/geekcommunicant.com\/blog\/wp-json\/wp\/v2\/categories?post=1365"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/geekcommunicant.com\/blog\/wp-json\/wp\/v2\/tags?post=1365"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}