EditMealCore.jsx 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148
  1. import React, { useEffect, useState } from 'react';
  2. import { arrayOf, bool, func, number, shape, string } from "prop-types";
  3. import { useTranslation } from "react-i18next";
  4. import ImageUpload from "../Images/ImageUpload";
  5. import { useAuth0, withAuthenticationRequired } from "@auth0/auth0-react";
  6. import { LoadingBody } from "../Loading";
  7. import SelectMealCategory from "./SelectMealCategory";
  8. import SelectMealTags from "./SelectMealTags";
  9. import OutlinedTextField from "../util/OutlinedTextField";
  10. import { fetchAndUpdateMealsFromUser } from "./meals.util";
  11. /** component is used by AddMeal and EditMeal and provides their shared core elements: text and photo input as well as choosing a category and adding tags.
  12. * Does not handle communication to server. */
  13. const EditMealCore = (props) => {
  14. const { t } = useTranslation();
  15. const { user } = useAuth0();
  16. const {
  17. updateMeal,
  18. meal: {
  19. _id: mealId,
  20. title,
  21. recipeLink,
  22. comment,
  23. images,
  24. tags,
  25. category,
  26. },
  27. isSecondary,
  28. autoFocusFirstInput,
  29. setImagesLoading,
  30. setLoadingImagesTakesLong,
  31. loadingImagesTakesLongAfter,
  32. } = props;
  33. const [placeholder, setPlaceholder] = useState(t('Recipe, instructions, comments, etc.'));
  34. const [, updateState] = useState();
  35. const forceUpdate = React.useCallback(() => updateState({}), []);
  36. const [loadingImagesTakesLongTimeout, setLoadingImagesTakesLongTimeout] = useState(0);
  37. const onChangeUploadedImages = (newUploadedImages) => {
  38. console.log('updated uploadedImages', newUploadedImages);
  39. updateMeal('images', newUploadedImages);
  40. forceUpdate(); // I don't know why the component does not rerender on state change but this solution fixes it. Source: https://stackoverflow.com/questions/53215285/how-can-i-force-component-to-re-render-with-hooks-in-react
  41. }
  42. useEffect(() => {
  43. if (user) {
  44. fetchAndUpdateMealsFromUser(user.sub, meals => {
  45. if (meals.length < 3) setPlaceholder(t('You can use this field to add a recipe in text form, instructions, experience or other comments.'));
  46. })
  47. }
  48. // eslint-disable-next-line
  49. }, [user]);
  50. const setLoadingAndStartTimeout = (value) => {
  51. if(setImagesLoading) setImagesLoading(value);
  52. if(setLoadingImagesTakesLong) {
  53. if (value === true) {
  54. setLoadingImagesTakesLongTimeout(setTimeout(() => {
  55. setLoadingImagesTakesLong(true);
  56. }, loadingImagesTakesLongAfter));
  57. }
  58. if (value === false) {
  59. clearTimeout(loadingImagesTakesLongTimeout);
  60. setLoadingImagesTakesLongTimeout(0);
  61. setLoadingImagesTakesLong(false);
  62. }
  63. }
  64. };
  65. return (
  66. <>
  67. <OutlinedTextField name="title"
  68. value={title}
  69. label={t('Meal Title')}
  70. onChange={e => updateMeal('title', e.target.value)}
  71. isSecondary={isSecondary}
  72. autoFocus={autoFocusFirstInput}
  73. required />
  74. <OutlinedTextField name="recipeLink" value={recipeLink} label={t('Link to Recipe')} onChange={e => updateMeal('recipeLink', e.target.value)} isSecondary={isSecondary} />
  75. <OutlinedTextField multiline
  76. rowsMax={10}
  77. rows={1}
  78. name="comment"
  79. placeholder={placeholder}
  80. value={comment}
  81. onChange={e => updateMeal('comment', e.target.value)}
  82. isSecondary={isSecondary} />
  83. <SelectMealCategory currentCategory={category} updateMeal={updateMeal} />
  84. <SelectMealTags own currentTags={tags} updateTags={(newTags) => {updateMeal('tags', newTags)}} allowCreate />
  85. <ImageUpload multiple
  86. uploadedImages={images}
  87. category="mealImages"
  88. categoryId={mealId}
  89. onChangeUploadedImages={onChangeUploadedImages}
  90. imageName={title}
  91. tags={tags}
  92. setLoading={setLoadingAndStartTimeout} />
  93. </>
  94. );
  95. }
  96. EditMealCore.propTypes = {
  97. /** setState function of the parent component's meal that takes key and value of attribute and updates it */
  98. updateMeal: func.isRequired,
  99. /** meal to be edited */
  100. meal: shape({
  101. _id: string,
  102. userId: string,
  103. title: string,
  104. images: arrayOf(shape({
  105. name: string,
  106. url: string,
  107. })),
  108. recipeLink: string,
  109. comment: string,
  110. category: string,
  111. tags: arrayOf(string),
  112. }).isRequired,
  113. /** whether to use primary or secondary color scheme */
  114. isSecondary: bool,
  115. /** whether to autofocus title input (will open keyboard on smartphones) */
  116. autoFocusFirstInput: bool,
  117. /** this is a function to update the state of the calling component. It will receive false, if all images of the meal have been uploaded, and true otherwise */
  118. setImagesLoading: func,
  119. /** this is a function to update the state of the calling component. It will receive true, if all images have not loaded after the specified interval */
  120. setLoadingImagesTakesLong: func,
  121. /** interval for setLoadingImagesTakesLong */
  122. loadingImagesTakesLongAfter: number,
  123. }
  124. EditMealCore.defaultProps = {
  125. isSecondary: false,
  126. autoFocusFirstInput: false,
  127. setImagesLoading: undefined,
  128. setLoadingImagesTakesLong: undefined,
  129. loadingImagesTakesLongAfter: 6000,
  130. }
  131. export default withAuthenticationRequired(EditMealCore, {
  132. onRedirecting: () => <LoadingBody />,
  133. });