componentWillUnmount works after switching to another page - reactjs

I have two pages and two components LibraryPageFilters.tsx (url: /courses) and UserVideoCreatePage.tsx (url: /ugc/courses/${course.id}).
In component LibraryPageFilters.tsx
useEffect(() => {
console.log(course.id)
if (course.id) {
console.log(544)
dispatch(push(`/ugc/courses/${course.id}`));
}
}, [course]);
i have a check that if course.id present in the store, then we make a redirect.
In component UserVideoCreatePage.tsx
useEffect(() => {
return () => {
console.log(333344444)
dispatch(courseDelete());
};
}, []);
i am deleting a course from the store when componentUnmount.
why does unmount happen after a redirect? as a result, I am redirected back. Because the course is not removed from the store at the moment of unmount, and the check (if (course.id)) shows that the course is in the store and a redirect occurs back (dispatch(push(/ugc/courses/${course.id})))
UserVideoCreatePage.tsx
import React, { useEffect, useRef, useState } from 'react';
import { shallowEqual, useDispatch, useSelector } from 'react-redux';
import { useParams } from 'react-router';
import { Link } from 'react-router-dom';
import { Container } from 'Core/components/Container/Container';
import { Svg } from 'Core/components/Svg';
import { Button } from 'Core/Molecules/Button';
import { Select } from 'Core/Molecules/Select';
import {
agreementCourse, categoriesSelector,
courseDelete,
courseEditorCourseSelector,
courseUpdateApi, getCategories,
getCourse,
updateCourseApi,
} from 'Learnings/store/courseEdit';
import { CourseComments } from 'Learnings/screens/CoursePlayPage/CourseBottom/CourseComments/CourseComments';
import { AddItem } from './AddItem';
import s from 'Admin/Pages/Course/Description/index.scss';
import './UserVideoCreatePage.scss';
export const UserVideoCreatePage: React.FC = () => {
const dispatch = useDispatch();
const { id: idCourse } = useParams();
const course = useSelector(courseEditorCourseSelector, shallowEqual);
const categories = useSelector(categoriesSelector, shallowEqual);
const [value, valueSet] = useState({ name: '', description: '', categories: [], lectures: [], materials: [] });
const [tab, tabSet] = useState('program');
const inputFileRef = useRef<HTMLInputElement>(null);
const img = course.gallery_items && course.gallery_items[0];
console.log(categories);
const handleBtnClick = () => {
if (inputFileRef && inputFileRef.current) {
inputFileRef.current.click();
}
};
useEffect(() => {
dispatch(getCourse(idCourse));
dispatch(getCategories());
}, [idCourse]);
useEffect(() => {
valueSet({
name: course.name,
description: course.description,
categories: course.categories && course.categories[0] && course.categories[0].id,
lectures: course.lectures,
materials: course.materials,
});
}, [course]);
useEffect(() => {
return () => {
console.log(333344444)
dispatch(courseDelete());
};
}, []);
return (
<Container className="createCourse">
<Link to="/" className="gallery__back">
<Svg name="arrow_back" width={26} height={20} className="gallery__svg"/>
<span>Назад</span>
</Link>
<div className="createCourse__twoColumn">
<div className="createCourse__twoColumn-left">
<div className="inputBlock">
<label className="inputBlock__label" htmlFor="video">
Название видео-курса
</label>
<input
id="video"
type="text"
placeholder="Введите название вашего видео"
className="inputBlock__input"
value={value.name || ''}
onChange={e =>
valueSet({
...value,
name: e.target.value,
})
}
onBlur={e => {
if (e.target.value && course.name !== e.target.value) {
dispatch(updateCourseApi(idCourse, { name: e.target.value }));
}
}}
/>
</div>
<div className="inputBlock">
<label className="inputBlock__label" htmlFor="opisanie">
Описание видео-курса
</label>
<textarea
id="opisanie"
placeholder="Введите краткое описание вашего видео"
className="inputBlock__input"
value={value.description || ''}
onChange={e =>
valueSet({
...value,
description: e.target.value,
})
}
onBlur={e => {
if (e.target.value && course.description !== e.target.value) {
dispatch(updateCourseApi(idCourse, { description: e.target.value }));
}
}}
/>
</div>
<Select
title="Категории видео-курса"
placeholder="Категории видео-курса"
value={value.categories}
options={categories.map(category => ({ value: category.id, label: category.name }))}
onChange={val => {
valueSet({
...value,
categories: val,
});
dispatch(
updateCourseApi(idCourse, {
category_ids: val,
courses_curators: {
'': {
user_id: val,
},
},
}),
);
}}
search
/>
</div>
<div className="createCourse__twoColumn-right">
<div className="loadVideo">
<div className="loadVideo__field">
<div className="loadVideo__field--block">
{!img && (
<>
<Svg className="loadVideo__field--block-icon" name="icn-load" width={104} height={69}/>
<p className="loadVideo__field--block-text">Загрузите обложку к видео</p>
</>
)}
{img && <img src={img && img.image_url} alt=""/>}
</div>
</div>
<div className="loadVideo__under">
<div className="loadVideo__under--left">
<div className="loadVideo__under--text">
<span className="loadVideo__under--text-grey">*Рекомендуемый формат</span>
<span className="loadVideo__under--text-bold"> 356х100</span>
</div>
<div className="loadVideo__under--text">
<span className="loadVideo__under--text-grey">*Вес не должен превышать</span>
<span className="loadVideo__under--text-bold"> 10 Мб</span>
</div>
</div>
<div className="loadVideo__under--right">
<input
onChange={val => {
if (val.target.files[0]) {
if (img) {
dispatch(
updateCourseApi(idCourse, {
gallery_items: {
'': {
image: val.target.files[0],
id: img.id,
},
},
}),
);
} else {
dispatch(
updateCourseApi(idCourse, {
gallery_items: {
'': {
image: val.target.files[0],
},
},
}),
);
}
}
}}
type="file"
ref={inputFileRef}
className="Library__btn"
/>
<Button
onClick={() => {
handleBtnClick();
}}
>
Библиотека обложек
</Button>
</div>
</div>
</div>
</div>
</div>
<div className={`block-switcher block-switcher--courseCreate`}>
<div
className={`block-switcher__item ${tab === 'program' && 'block-switcher__item_active'}`}
onClick={() => tabSet('program')}
>
Программы
</div>
<div
className={`block-switcher__item ${tab === 'comments' && 'block-switcher__item_active'}`}
onClick={() => tabSet('comments')}
>
Комментарии эксперта
</div>
</div>
{tab === 'program' && (
<>
<AddItem
accept="video/mp4,video/x-m4v,video/*"
fieldName="name"
addType="lecture_type"
title="Видео-курсы"
addBtn="Добавить видео"
type="lectures"
file="video"
lecturesArg={course.lectures}
value={value}
onChangeInput={lecturesNew => {
valueSet({
...value,
lectures: lecturesNew,
});
}}
onVideoUpdate={(params: any) => {
dispatch(updateCourseApi(idCourse, params));
}}
posMove={(lectures: any) => {
dispatch(courseUpdateApi({ id: idCourse, lectures: lectures }, true));
}}
/>
<AddItem
accept=""
fieldName="title"
addType="material_type"
title="Материалы к видео-курсам"
addBtn="Добавить файл"
type="materials"
file="document"
lecturesArg={course.materials}
value={value}
onChangeInput={lecturesNew => {
valueSet({
...value,
materials: lecturesNew,
});
}}
onVideoUpdate={(params: any) => {
dispatch(updateCourseApi(idCourse, params));
}}
posMove={(lectures: any) => {
dispatch(courseUpdateApi({ id: idCourse, materials: lectures }, true));
}}
/>
</>
)}
{tab === 'comments' && <CourseComments title="Обсуждение"/>}
<Button
className={`${s.button} agreement__btn`}
size="big"
onClick={() =>
dispatch(
agreementCourse(idCourse, {
visibility_all_users: true,
}),
)
}
>
Отправить на согласование
</Button>
</Container>
);
};
LibraryPageFilters.tsx
import React, { useEffect, useState } from 'react';
import { useDispatch, useSelector, shallowEqual } from 'react-redux';
import { push } from 'connected-react-router';
import { getSettingsGlobalSelector } from 'Core/store/settings';
import { Svg } from 'Core/components/Svg';
import { NavBar } from 'Core/Organisms/NavBar';
import { Button } from 'Core/Molecules/Button';
import { CategoriesFilter } from 'Core/Organisms/Filters/components/CategoriesFilter';
import { courseDelete, courseEditorCourseSelector, createNewCourse } from 'Learnings/store/courseEdit';
import { FILTERS, LINKS } from '../../libraryPageConstants';
import { setLibraryPageQuery } from '../../actions/libraryPageActions';
import { getLibraryPageQuerySelector } from '../../libraryPageSelectors';
import s from './index.scss';
import { languageTranslateSelector } from 'Core/store/language';
import { LanguageType } from 'Core/models/LanguageSchema';
import { Status, Tabs } from 'Core/components/Tabs/Tabs';
const statuses: Array<Status> = [
{
key: 'Filter/all',
link: '/courses' || '/courses',
type: 'all' || '',
},
{
key: 'Filter/online',
link: '/courses/online',
type: 'online',
},
{
key: 'Filter/offline',
link: '/courses/offline',
type: 'offline',
},
{
key: 'Filter/complete',
link: '/courses/complete',
type: 'complete',
},
];
export const LibraryPageFilters = () => {
const dispatch = useDispatch();
const [searchTerm, setSearchTerm] = useState('');
const [isBtnDisabled, setIsBtnDisabled] = useState(false);
const course = useSelector(courseEditorCourseSelector, shallowEqual);
const global = useSelector(getSettingsGlobalSelector);
const query = useSelector(getLibraryPageQuerySelector, shallowEqual);
const courseCreateButtonText = useSelector(
languageTranslateSelector('CoursePage/courseCreateButton'),
) as LanguageType;
const { category_id: categoryID } = query;
console.log(course)
useEffect(() => {
console.log(course.id)
if (course.id) {
console.log(544)
dispatch(push(`/ugc/courses/${course.id}`));
}
}, [course]);
useEffect(() => {
return () => {
setIsBtnDisabled(false);
dispatch(courseDelete());
};
}, []);
const onFilter = (values: any) => {
return false;
};
const handleActiveCategory = (id: number) => {
const categoryParam = {
...query,
offset: 0,
category_id: id,
};
if (id === categoryID) {
delete categoryParam.category_id;
}
dispatch(setLibraryPageQuery(categoryParam));
};
const handleSearch = () => {
dispatch(setLibraryPageQuery({ query: searchTerm }));
};
return (
<React.Fragment>
<div className={s.filters}>
{global.coursesPage?.filters.length ? (
<NavBar
className={s.navBar}
links={global.coursesPage.filtersLinks.map(linkType => LINKS[linkType])}
filters={global.coursesPage.filters.map(filterType => FILTERS[filterType])}
onFilter={onFilter}
postfix={
global.coursesPage.courseCreateButton && global.coursesPage.courseCreateButton.enable ? (
<Button
className="coursePageCreateButton"
onClick={() => {
dispatch(createNewCourse());
setIsBtnDisabled(true);
}}
disabled={isBtnDisabled}
>
{courseCreateButtonText['CoursePage/courseCreateButton']}
</Button>
) : null
}
/>
) : (
<div className="track-page__header" data-tut="track-header">
<Tabs statuses={statuses} />
<div className={s.filtersSearch}>
<Svg className={s.filtersSearchIcon} name="search_alternative" width={18} height={18} />
<input
type="text"
placeholder="Поиск"
className={s.filtersSearchInput}
value={searchTerm}
onChange={event => setSearchTerm(event.target.value)}
/>
<button type="button" className={s.filtersButton} onClick={handleSearch}>
Найти
</button>
</div>
</div>
)}
</div>
<CategoriesFilter onChange={handleActiveCategory} selectedID={categoryID} />
</React.Fragment>
);
};

Although react suggests to use Functional Component, try Class Component, I faced similar issues, this was resolved easily in Class Component :
componentDidMount();
componentDidUpdate(prevProps, prevState, snapshot);
These two will solve your problem. Ask me if anything you need.

Related

Using Formik on IndexTable with editable fields

I am trying to come up with a list that has an editable Quantity field. Here's what I have now.
As you can see, I'm unable to map the Quantity field. The values there comes from Shopify's ResourcePicker. It does not have a quantity so I manually update the object to set a default quantity of 1. This quantity should be editable as seen in the screenshot above.
Here's the code that I currently have.
import { Button, Icon, IndexTable, TextField } from '#shopify/polaris'
import { Form, Formik } from 'formik'
import { MobilePlusMajor } from '#shopify/polaris-icons'
import { ResourcePicker } from '#shopify/app-bridge-react'
import { useEffect, useState } from 'react'
function SelectProducts({ form, dispatch }) {
const [isProductPickerOpen, setIsProductPickerOpen] = useState(false)
const [showProducts, setShowProducts] = useState(false)
const [chosenProducts, setChosenProducts] = useState([])
const productPickerOnSelectHandler = (selectPayload) => {
setIsProductPickerOpen(false)
// Add quantity to selection
const updatedSelection = selectPayload.selection.map((product) => {
const variants = product.variants.map((variant) => ({
...variant,
quantity: 1,
}))
return {
...product,
variants,
}
})
setChosenProducts(updatedSelection)
}
useEffect(() => {
if (!chosenProducts) return
dispatch({
type: 'SET_PRODUCT_SELECTION_DATA_ON_CREATE_SUBSCRIPTION',
payload: chosenProducts,
})
}, [chosenProducts, dispatch])
const productPickerOnCancelHandler = () => {
setIsProductPickerOpen(false)
}
useEffect(() => {
if (
form.productSelectionData &&
Object.keys(form.productSelectionData).length > 0
) {
setShowProducts(true)
} else {
setShowProducts(false)
}
}, [form])
return (
<>
{showProducts ? (
<Formik initialValues={form.productSelectionData}>
{({ setFieldValue, values, errors, isSubmitting }) => (
<Form>
<IndexTable
resourceName={{
singular: 'product',
plural: 'products',
}}
onSelectionChange={() => {
console.log('selection changed.')
}}
headings={[
{ title: 'Product' },
{ title: 'Quantity' },
{ title: 'Price' },
{ title: 'Subtotal' },
]}
emptyState={null}
itemCount={1}
>
{values.map((product, pIndex) =>
product.variants?.map((variant, vIndex) => (
<IndexTable.Row
id={variant.id}
key={variant.id}
position={vIndex}
>
<IndexTable.Cell fontWeight="bold">
<div onClick={(e) => e.stopPropagation()}>
{variant.displayName}
</div>
</IndexTable.Cell>
<IndexTable.Cell>
<TextField
type="number"
value={variant.quantity}
onChange={(value) => {
console.log('got in here')
setFieldValue(
`products[${pIndex}].variants[${vIndex}].quantity`,
value
)
}}
/>
</IndexTable.Cell>
<IndexTable.Cell>
<div onClick={(e) => e.stopPropagation()}>
{variant.price}
</div>
</IndexTable.Cell>
<IndexTable.Cell>
<div onClick={(e) => e.stopPropagation()}>
{Number(variant.quantity * variant.price)}
</div>
</IndexTable.Cell>
</IndexTable.Row>
))
)}
</IndexTable>
</Form>
)}
</Formik>
) : null}
<Button
icon={<Icon source={MobilePlusMajor} />}
onClick={() => {
setIsProductPickerOpen(true)
}}
>
Add products and variants
</Button>
<ResourcePicker
resourceType="Product"
open={isProductPickerOpen}
showVariants
onSelection={productPickerOnSelectHandler}
onCancel={productPickerOnCancelHandler}
/>
<p> </p>
</>
)
}
export default SelectProducts
I am not sure what to put in the setFieldValue for variant.quantity. How do I set it correctly? Or am I understanding this the wrong way?

Please how do I add maxFiles properly to this code? I have tried a billion different things, but it just does not work with the dropzone

import { FC } from 'react';
import { useDropzone } from 'react-dropzone';
import { FileIcon } from 'assets/icons';
import Typography from '../Typography';
import { TMultipleDropzoneProps } from './types';
import styles from './MultipleDropzone.module.scss';
const MultipleDropzone: FC<TMultipleDropzoneProps> = ({ title, onDrop }) => {
const { getRootProps, getInputProps, open, isDragAccept, isFocused, isDragReject } = useDropzone({
accept: { 'image/*': ['.jpeg', '.png'], 'video/mp4': ['.mp4', '.MP4'] },
onDrop,
noClick: true,
noKeyboard: true,
maxFiles: 3,
});
const accept = isDragAccept ? 1 : 0;
const focused = isFocused ? 1 : 0;
const rejected = isDragReject ? 1 : 0;
// This is used for warning in console for camel-case attributes to the DOM element and to make it boolean
return (
<div className={styles.wrapper}>
<div
onClick={open}
className={styles.container}
{...getRootProps({ accept, focused, rejected })}
>
<input {...getInputProps({})} />
{rejected === 1 && (
<div className={styles.error}>
Some files were rejected because they did not meet the requirements.
</div>
)}
<div className={styles.container__content}>
<Typography>{title}</Typography>
</div>
<button onClick={open} className={styles.icon}>
<FileIcon />
</button>
</div>
</div>
);
};
export default MultipleDropzone;
the type file:
export type TMultipleDropzoneProps = {
title: string;
onDrop: (e: any, a: any) => void;
isFileUploaded: boolean;
maxFiles?: number;
};
the modal I am using it in:
import { useContext, useState } from 'react';
import { ModalContext } from 'context/Modal';
import { FileExtended } from 'types/global/file';
import { useAppDispatch, useAppSelector } from 'hooks';
import { NewPostTextArea, MultipleDropzone, Typography, Button } from 'components';
import { createActivityPost } from 'store/slices/activitiesSlice/activitiesThunks';
import { CloseCircleIcon } from 'assets/icons';
import { TImages } from './types';
import styles from './NewPost.module.scss';
const NewPost = () => {
const { closeModal } = useContext(ModalContext);
const [images, setImages] = useState<TImages[]>([]);
const [description, setDescription] = useState<string>('');
const dispatch = useAppDispatch();
const { userData } = useAppSelector((state) => state.auth);
const createPost = () => {
const post = {
user_id: userData?.id as number,
description: description,
content_url: 'https://cdn.pixabay.com/photo/2015/04/23/22/00/tree-736885__340.jpg',
content_type: 'string',
};
closeModal();
if (description.trim()) {
dispatch(createActivityPost(post));
}
};
const isFileUploaded = images.length > 0;
const onDrop = (acceptedFiles: FileExtended[], maxFiles: number) => {
acceptedFiles.forEach((file) => {
const reader = new FileReader();
reader.onload = (e) => {
setImages((prev) => [
...prev,
{
id: Date.now(),
type: file.type,
src: e.target && e.target.result,
file: file,
description: file.name,
},
]);
};
reader.readAsDataURL(file);
});
let modifiedAcceptedFiles = acceptedFiles;
if (acceptedFiles.length > maxFiles) {
// If the number of files exceeds the maxFiles limit,
// slice the extra files off the array and show an error message
modifiedAcceptedFiles = acceptedFiles.slice(0, maxFiles);
}
};
const removeImage = (id: number) => {
setImages(images.filter((image) => image.id !== id));
};
const imageFiles = images.map((image) => {
return (
<div key={image.id} className={styles.container__main__case__box}>
<CloseCircleIcon
onClick={() => removeImage(image.id)}
className={styles.container__main__case__box_close}
/>
{image.type.includes('video') ? (
<video src={image.src as string} autoPlay loop />
) : (
<img src={image.src as string} alt={image.description} />
)}
</div>
);
});
return (
<div className={styles.container}>
<Typography className={styles.container__head}>New Post</Typography>
<div className={styles.container__description}>
<NewPostTextArea value={description} setValue={setDescription} />
</div>
<div className={styles.container__main}>
<Typography className={styles.container__main__head}>{images.length} items</Typography>
<div className={styles.container__main__case}>{imageFiles}</div>
</div>
<MultipleDropzone
onDrop={onDrop}
title='Please attach your files here (Max 3)'
isFileUploaded={isFileUploaded}
/>
<div className={styles.container__footer}>
<Button className={styles.container__footer_close} onClick={closeModal}>
Close
</Button>
<Button type='submit' className={styles.container__footer_submit} onClick={createPost}>
Create
</Button>
</div>
</div>
);
};
export default NewPost;
I tried adding maxFiles to every single component, I also tried adding it to the onDrop component. New to React ( 1 week) and I am slowly losing my sanity. I will never forgive Zuckerberg for this apparition he has brought upon coderkin. Even chatGPT could not help my case.

React Functional Component Rendering Old Data

I'm trying to setup a piece of code into its own component, however, by doing so the data doesn't show up after doing it:
Code StackBlitz Example
As you see from the picture, both of the inputs from the component setup are blank. I'm assuming that it might have something to do with closures, but I'm not sure, and even more confused as how to resolve the issue.
App.tsx
import React, { useEffect, useState } from 'react';
import axios from 'axios';
import { SimpleTable } from './components/SimpleTable/SimpleTable';
import ITableCol from './components/SimpleTable/ITableCol';
interface IItem {
userId: number;
id: number;
title: string;
body: string;
}
interface IIdItem {
[key: string]: number;
}
export const App = () => {
const getItems = async () => {
const res = await axios.get('https://jsonplaceholder.typicode.com/posts');
const twoItems = [res.data[0], res.data[1]];
setItems(twoItems);
};
const getTableCols = (): ITableCol[] => {
return [
{
title: 'title',
renderFn: renderTitle,
},
{
title: 'input',
renderFn: renderInput,
},
];
};
const processData = () => {
let _idValues = { ...idValues };
for (const item of items) {
if (!_idValues[item.title]) {
_idValues[item.title] = item.id * 5;
}
}
return _idValues;
};
const renderTitle = (item: IItem) => {
return <div className="">{item.title}</div>;
};
const handleChange = (e: ChangeEvent<any>) => {};
const renderInput = (item: IItem) => {
const valueItem = idValues[item.title];
return (
<div className="">
<div>Id: {item.id}</div>
<div>
<input type="number" value={valueItem} onChange={handleChange} />
</div>
</div>
);
};
useEffect(() => {
getItems();
}, []);
const [items, setItems] = useState<IItem[]>([]);
const [idValues, setIdValues] = useState<IIdItem>({});
const [tableCols, setTableCols] = useState<ITableCol[]>([]);
useEffect(() => {
setTableCols(getTableCols());
setIdValues(processData());
}, [items]);
return (
<div className="p-2">
<h1>State Issue Example</h1>
<div className="mb-5">
<div>Simple Table</div>
<SimpleTable data={items} cols={tableCols} />
</div>
<div>
<div>Non Componentized Table</div>
<div className="d-flex">
<div className="w-50">
<b>title</b>
</div>
<div className="w-50">
<b>input</b>
</div>
</div>
</div>
<div>
{items.map((item) => {
return (
<div className="d-flex">
<div className="w-50">{renderTitle(item)}</div>
<div className="w-50">{renderInput(item)}</div>
</div>
);
})}
</div>
</div>
);
};
SimpleCode.tsx
import React, { useState } from 'react';
import ITableCol from '../SimpleTable/ITableCol';
type Props = {
cols: ITableCol[];
data: any[];
};
export const SimpleTable: React.FC<Props> = (props) => {
return (
<div>
<div className="d-flex">
{props.cols.map((col) => {
return (
<div className="w-50">
<b>{col.title}</b>
</div>
);
})}
</div>
{props.data.map((data, index) => {
return (
<div className="d-flex" key={'data-' + index}>
{props.cols.map((col, index) => {
return (
<div key={data.id} className="w-50">
{col.renderFn(data, index)}
</div>
);
})}
</div>
);
})}
<div>Data Size: {props.data.length}</div>
</div>
);
};
ITableCol.tsx
export default interface ITableCol {
renderFn: (item: any, index: number) => void;
title: string;
}
Problem is getTableCols gets render even before API response. Using two useEffect would have solved your issue.Screenshot of the Solution
useEffect(() => {
setIdValues(processData());
}, [items]);
useEffect(() => {
setTableCols(getTableCols());
}, [idValues]);
Given below is the full code of App.tsx
import React, { useEffect, useState } from 'react';
import axios from 'axios';
import { SimpleTable } from './components/SimpleTable/SimpleTable';
import ITableCol from './components/SimpleTable/ITableCol';
interface IItem {
userId: number;
id: number;
title: string;
body: string;
}
interface IIdItem {
[key: string]: number;
}
export const App = () => {
const getItems = async () => {
const res = await axios.get('https://jsonplaceholder.typicode.com/posts');
const twoItems = [res.data[0], res.data[1]];
setItems(twoItems);
};
const getTableCols = (): ITableCol[] => {
return [
{
title: 'title',
renderFn: renderTitle,
},
{
title: 'input',
renderFn: renderInput,
},
];
};
const processData = () => {
let _idValues = { ...idValues };
for (const item of items) {
if (!_idValues[item.title]) {
_idValues[item.title] = item.id * 5;
}
}
return _idValues;
};
const renderTitle = (item: IItem) => {
return <div className=""> {item.title}</div>;
};
const handleChange = (e: ChangeEvent<any>) => {};
const renderInput = (item: IItem) => {
const valueItem = idValues[item.title];
return (
<div className="">
<div>Id: {item.id}</div>
<div>valueItem: {valueItem}</div>
<div>
<input type="number" value={valueItem} onChange={handleChange} />
</div>
</div>
);
};
useEffect(() => {
getItems();
}, []);
const [items, setItems] = useState<IItem[]>([]);
const [idValues, setIdValues] = useState<IIdItem>({});
const [tableCols, setTableCols] = useState<ITableCol[]>([]);
useEffect(() => {
setIdValues(processData());
}, [items]);
useEffect(() => {
setTableCols(getTableCols());
}, [idValues]);
return (
<div className="p-2">
<h1>State Issue Example</h1>
<div className="mb-5">
<div>Simple Table</div>
<SimpleTable data={items} cols={tableCols} />
</div>
<div>
<div>Non Componentized Table</div>
<div className="d-flex">
<div className="w-50">
<b>title</b>
</div>
<div className="w-50">
<b>input</b>
</div>
</div>
</div>
<div>
{items.map((item, i) => {
return (
<div key={i} className="d-flex">
<div className="w-50">{renderTitle(item)}</div>
<div className="w-50">{renderInput(item)}</div>
</div>
);
})}
</div>
</div>
);
};

How can I trigger the function using the button?

my code consists of a select by provinces, when I click the province to filter I get the result of clinics in that province, what I want is to get that result by clicking the button, not through the select itself.///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
import React, { useState, useEffect } from 'react'
import Select, { SingleValue } from 'react-select'
import { getClinic } from '../../api/drupalAPI'
import {Clinic} from '#icofcv/common';
import "./Modal.css";
interface Props {
isOpen: boolean,
closeModal: () => void
}
export const SearchFilterClinics : React.FC<Props> = ({ children, isOpen, closeModal, }) => {
////filter
type OptionType = {
value: string;
label: string;
};
const provincesList: OptionType[] = [
{ value: 'Todos', label: 'Todos' },
{ value: 'Valencia', label: 'Valencia' },
{ value: 'Alicante', label: 'Alicante' },
{ value: 'Castellón', label: 'Castellón' },
]
const [clinicList, setClinicList] = useState<Clinic[]>([]);
const [clinicListFilteredSelect, setClinicListFilteredSelect] = useState<Clinic[]>([]);
const [filterSelectClinic, setFilterSelectClinic] = useState<SingleValue<OptionType>>(provincesList[0]);
const handleChangeSelect = async (provinceList: SingleValue<OptionType>) => {
getClinic().then((response) => {
setClinicList(response);
setClinicListFilteredSelect(response)
setFilterSelectClinic(provinceList);
filterSelect(provinceList );
}).catch ((error) => {
console.error(error);
throw error;
});
}
const filterSelect=(termSearch)=>{
const resultFilterSelect = clinicList.filter((element) => {
if(element.province?.toString().toLowerCase().includes(termSearch.value.toLowerCase() )
){
return element;
}
});
setClinicListFilteredSelect(resultFilterSelect);
}
const handleModalContainerClick = (e) => e.stopPropagation();
return (
<>
<div className={`modal ${isOpen && "is-open"}`} onClick={closeModal}>
<div className="modal-container" onClick={handleModalContainerClick}>
<button className="modal-close" onClick={closeModal}>x</button>
{children}
<div>
<h1>Encuentra tu clínica</h1>
</div>
<div>
<form>
<label>Provincia</label>
<Select
defaultValue={filterSelectClinic}
options={provincesList}
onChange={handleChangeSelect}
/>
<button onClick={handleChangeSelect}>buscar</button>
</form>
{
clinicListFilteredSelect.map((clinicFilter) => (
<div>
<div>{clinicFilter.title}</div>
<div>{clinicFilter.propsPhone}</div>
<div>{clinicFilter.mobile}</div>
<div>{clinicFilter.email}</div>
<div>{clinicFilter.province} </div>
<div>{clinicFilter.registry}</div>
</div>
))
}
</div>
</div>
</div>
</>
)
}
It looks like you are calling the handleChangeSelect function with the button, and also when the select menu changes (with onChange). So remove the onChange in the select, and leave the onClick for the button.
<label>Provincia</label>
<Select
defaultValue={filterSelectClinic}
options={provincesList}
onChange={handleChangeSelect} //THIS TRIGGERS THE FUNCTION
/>
<button onClick={handleChangeSelect}>buscar</button> //ALSO TRIGGERS FUNCTION

I'm using redux and firebase for storage and I don't know what I'm doing wrong

Actions/index.js The problem is in the PostArticleAPI() function
import db, { auth, provider, storage } from "../firebase";
import { SET_USER } from "./actionType";
export const setUser = (payload) => ({
type: SET_USER,
user: payload
});
export function signInAPI() {
return (dispatch) => {
auth.signInWithPopup(provider).then((payload) => {
console.log(payload);
dispatch(setUser(payload.user));
}).catch((error) => alert(error.message));
}
}
export function getUserAuth() {
return (dispatch) => {
auth.onAuthStateChanged(async (user) => {
if (user) {
dispatch(setUser(user));
}
})
}
}
export function signOutAPI() {
return (dispatch) => {
auth.signOut().then(() => {
dispatch(setUser(null));
}).catch((error) => {
console.error(error.message);
});
}
}
export function postArticleAPI(payload) {
return (dispatch) => {
if (payload.image != '') {
const upload = storage.ref(`images/${payload.image.name}`).put(payload.image);
upload.on('state_changed', (snapshot) => {
const progress = (snapshot.bytesTransferred / snapshot.totalBytes) * 100;
console.log(`Progress: ${progress}%`);
if (snapshot.state === 'RUNNING') {
console.log(`Progress: ${progress}%`);
}
}, (error) => console.log(error.code),
async () => {
const downloadURL = await upload.snapshot.ref.getDownloadURL();
db.collection('articles').add({
actor: {
description: payload.user.email,
title: payload.user.displayName,
date: payload.timestamp,
image: payload.user.photoURL
},
video: payload.video,
shareImg: downloadURL,
comments: 0,
description: payload.description,
})
}
);
}
}
}
PostModal.js
import React, { useState } from 'react'
import styled from 'styled-components';
import ReactPlayer from 'react-player';
import { connect } from 'react-redux';
import { postArticleAPI } from '../actions';
import firebase from 'firebase';
const PostModal = (props) => {
const [editorText, setEditorText] = useState('');
const [shareImage, setShareImage] = useState('');
const [videoLink, setVideoLink] = useState('');
const [assetArea, setAssetArea] = useState('');
const handleChange = (e) => {
const image = e.target.files[0];
if (image === '' || image === undefined) {
alert(`not an image, the file is a ${typeof (image)}`);
return;
}
setShareImage(image);
}
const switchAssetArea = (area) => {
setShareImage("");
setVideoLink("");
setAssetArea(area);
};
const postArticle = (e) => {
console.log("heyy abay");
e.preventDefault();
if (e.target !== e.currentTarget) {
console.log("heyy abay2");
return;
}
const payload = {
image: shareImage,
video: videoLink,
user: props.user,
description: editorText,
timestamp: firebase.firestore.Timestamp.now(),
}
props.postArticle(payload);
reset(e)
}
const reset = (e) => {
setEditorText("");
setShareImage("");
setVideoLink("");
setAssetArea("");
props.handleClick(e);
};
return (
<>
{props.showModal === "open" && (
<Container>
<Content>
<Header>
<h2>Create a post</h2>
<button onClick={(e) => reset(e)}>
<img src="/images/cancel.svg" alt="cancel" />
</button>
</Header>
<SharedContent>
<UserInfo>
{props.user && props.user.photoURL ? (
<img src={props.user.photoURL} alt="" />)
: (<img src="/images/user.svg" alt="" />)}
{/* <img src="/images/user.svg" alt="" /> */}
<span>{props.user.displayName}</span>
</UserInfo>
<Editor>
<textarea value={editorText}
onChange={(e) => setEditorText(e.target.value)}
placeholder='What do you want to talk about?'
autoFocus={true}
/>
{assetArea === "image" ? (
<UploadImage>
<input type="file"
accept='image/gif, image/jpeg, image/png'
name='image'
id='file'
style={{ display: 'none' }}
onChange={handleChange}
/>
<p>
<label htmlFor="file">Select an image to share</label>
</p>
{shareImage && <img
src={URL.createObjectURL(shareImage)} />}
</UploadImage>) : (
assetArea === "media" && (
<>
<input
type="text"
placeholder="Please input a video link"
value={videoLink}
onChange={e => setVideoLink(e.target.value)}
/>
{videoLink && (<ReactPlayer width={"100%"}
url={videoLink} />)}
</>))
}
</Editor>
</SharedContent>
<ShareCreations>
<AttachAssets>
<AssetButton onClick={() => switchAssetArea("image")} >
<img src="/images/gallery.svg" alt="" />
</AssetButton>
<AssetButton onClick={() => switchAssetArea("media")}>
<img src="/images/video.svg" alt="" />
</AssetButton>
</AttachAssets>
<ShareComment>
<AssetButton>
<img src="/images/chat1.svg" alt="" />
Anyone
</AssetButton>
</ShareComment>
<PostButton disabled={!editorText ? true : false}
onClick={(e) => postArticle(e)}>
Post
</PostButton>
</ShareCreations>
</Content>
</Container>)
}
</>
)
};
//I'm hiding CSS
const mapStateToProps = (state) => {
return {
user: state.userState.user,
};
}
const mapDispatchToProps = (dispatch) => ({
postArticle: (payload) => dispatch(postArticleAPI(payload)),
});
export default connect(mapStateToProps, mapDispatchToProps)(PostModal);
Error:
TypeError: firebase__WEBPACK_IMPORTED_MODULE_0_.storage.ref is not a function
This is the error I'm getting and in this line actions/index.js
const upload = storage.ref(`images/${payload.image.name}`).put(payload.image);
It is saying storage.ref is not a function. I am getting this error that is why I'm writing this extra lines
You are probably not exporting properly the storage from ../firebase.
If you share the code from that file we can help you more. Maybe it's also a missmatch with the new Firebase API version (v9) and old one (v8).

Resources