React hooks form onChange misbehave after error validation occured - reactjs

In my simple crud application, when I try to add a new author with an invalid name format and try to submit form display an error and after that when I have press backspace twice to erase last letter in textbox.
Here is my AuthorForm.tsx
import React, {useEffect, useState} from 'react';
import {Row, Col, Form, Button} from 'react-bootstrap';
import {IAuthor} from "../../assets/types/LibraryTypes";
import {XCircle} from "react-feather";
import {useForm} from "react-hook-form";
interface IFormInputs {
authorName: string
}
type CreateFormProps = {
onClose: () => void,
onAuthorAdded: (author:IAuthor)=>void,
onAuthorToUpdate: IAuthor | null,
updateAuthorIndex : number | null,
onAuthorUpdated : (updatedAuthor:IAuthor, index:number)=>void,
}
const AuthorForm:React.FC<CreateFormProps> = (props) =>{
const [authorName, setAuthorName] = useState<string|null>(null);
const { register, errors, handleSubmit } = useForm<IFormInputs>({mode:'onTouched'});
useEffect(()=>{
if(!props.onAuthorToUpdate){
setAuthorName(null);
return;
}
setAuthorName(props.onAuthorToUpdate.name);
},[props.onAuthorToUpdate]);
const handleOnNameChange = (event:React.ChangeEvent<HTMLInputElement>)=>{
event.preventDefault();
setAuthorName(event.target.value);
}
const handleOnCreate = () =>{
if(!authorName){
return;
}
if(props.onAuthorToUpdate && props.updateAuthorIndex !== null){
props.onAuthorUpdated({...props.onAuthorToUpdate,name:authorName},props.updateAuthorIndex);
setAuthorName(null);
return;
}
const newAuthor: IAuthor = {name:authorName};
props.onAuthorAdded(newAuthor);
setAuthorName(null);
};
return(
<Col className='p-0' sm={10}>
<Row className=' pb-1 mb-3 mx-1'>
<Col xs={10}>
<span className='add-book-title pt-2'>
{!props.onAuthorToUpdate && 'Create Author'}
{props.onAuthorToUpdate && 'Update Author'}
</span>
</Col>
<Col className='closeBtn text-right p-0' xs={2}>
<XCircle color='#363636' className='mt-2 mr-3' onClick={props.onClose}/>
</Col>
</Row>
<Form className='mx-4' onSubmit={handleSubmit(handleOnCreate)}>
<Form.Group>
<Form.Row>
<Form.Label column="sm" xs={6} className='label'>
Name of the Author
</Form.Label>
<Col xs={6} className='warning text-right mt-2 pr-2'>
{errors.authorName?.type === "required" && (
<p>This field is required</p>
)}
{errors.authorName?.type === "maxLength" && (
<p>Author Name name cannot exceed 50 characters</p>
)}
{errors.authorName?.type === "pattern" && (
<p>Invalid Author Name</p>
)}
</Col>
<Col sm={12}>
<Form.Control size={"sm"}
name="authorName"
ref={register({
required: true,
maxLength: 50,
pattern: /^[a-zA-Z\s]+$/
})}
onChange={
(event:React.ChangeEvent<HTMLInputElement>)=>
handleOnNameChange(event)
}
value={authorName?authorName:''}
/>
</Col>
</Form.Row>
</Form.Group>
<Col className='text-right mb-3 p-0' xs={12}>
<Button type={"submit"} variant={"primary"} size={"sm"} className={"px-3 pt-1"}>
{!props.onAuthorToUpdate && 'Create'}
{props.onAuthorToUpdate && 'Update'}
</Button>
</Col>
</Form>
</Col>
)
};
export default AuthorForm;
And this is AuthorList.tsx
import React, {useEffect, useState} from 'react';
import {Container} from 'react-bootstrap';
import AuthorAddedList from "./AuthorAddedList";
import AuthorForm from "./AuthorForm";
import AuthorWelcome from "./AuthorWelcome";
import CreateAuthor from "./CreateAuthor";
import {IAuthor} from "../../assets/types/LibraryTypes";
const AuthorList:React.FC = () =>{
const initAuthors: IAuthor[] = [];
const [authors, setAuthors] = useState<IAuthor[]>(initAuthors);
const [isFormVisible, setIsFormVisible] = useState<boolean>(false);
const [authorToUpdate, setAuthorToUpdate] = useState<IAuthor | null>(null);
const [updateAuthorIndex, setUpdateAuthorIndex] = useState<number| null>(null)
useEffect(()=>{
if(!authorToUpdate){
return;
}
setIsFormVisible(true);
},[authorToUpdate]);
const handleOnCreateClick = () => {
setIsFormVisible(true);
setAuthorToUpdate(null);
};
const handleOnFormClosed = () => {
setIsFormVisible(false);
}
const handleAuthorAdded = (newAuthor: IAuthor) => {
const allAuthors: IAuthor[] = authors.slice();
allAuthors.push(newAuthor)
setAuthors(allAuthors);
};
const handleAuthorDeleted = (index: number) => {
const allAuthors: IAuthor[] = authors.slice();
allAuthors.splice(index, 1);
setAuthors(allAuthors);
}
const handleOnUpdateRequest = (index: number) => {
setAuthorToUpdate(authors[index]);
setUpdateAuthorIndex(index);
setIsFormVisible(true);
}
const handleOnAuthorUpdated = (updatedAuthor: IAuthor, index:number) =>{
const allAuthors : IAuthor [] = authors.slice();
allAuthors.splice(index,1, updatedAuthor);
setAuthors(allAuthors)
}
return (
<Container fluid={true} className={"authors"}>
<AuthorWelcome/>
<AuthorAddedList authors={authors} onDeleted={handleAuthorDeleted} onUpdateRequested={handleOnUpdateRequest} />
<CreateAuthor onClickCreate={handleOnCreateClick}/>
{isFormVisible &&
<AuthorForm onClose={handleOnFormClosed} onAuthorAdded={handleAuthorAdded} onAuthorToUpdate={authorToUpdate} onAuthorUpdated={handleOnAuthorUpdated} updateAuthorIndex={updateAuthorIndex}/>}
</Container>
)
}
export default AuthorList;
Here is the sandbox link to my full code Click Here
to demonstrate the error,
go to the sandbox link
click add author button in webapp
enter an invalid name like 'john97'
then submit the form
then try to clear the name using backspace.
now you can see to erase last letter 'j' you have to press backspace twice
please help me to solve this issue
thank you

I think using Controller component of react-hook-form is better at handling controlled components, you also don't have to set onChange event and make your code much cleaner.
Using this in AuthorForm.tsx seems to make your weird bug fixed.
type FormData = {
authorName: string;
}
//some codes...
const { register, handleSubmit, control, errors, setValue, reset } = useForm<FormData>();
//some codes...
const handleOnCreate = (data: FormData) => {
if (!data?.authorName) {
return;
}
if (props.onAuthorToUpdate && props.updateAuthorIndex !== null) {
props.onAuthorUpdated(
{ ...props.onAuthorToUpdate, name: data.authorName },
props.updateAuthorIndex
);
reset({ authorName: "" }); // or setValue("authorName", "");
return;
}
const newAuthor: IAuthor = { name: data.authorName };
props.onAuthorAdded(newAuthor);
reset({ authorName: "" }); // or setValue("authorName", "");
};
//some codes...
<Controller
control={control}
name={"authorName"}
as={<Form.Control size={"sm"} />}
defaultValue=""
rules={{
required: true,
maxLength: 50,
pattern: /^[A-Za-z ]+$/i
}}
/>
Here is the sandbox.

the problem is in the AuthorForm.tsx file, in this line:
<Form className='mx-4' onSubmit={handleSubmit(handleOnCreate)}>
the onSubmit shouldn't accept an invoked function handleSubmit(handleOnCreate),
it should be changed to onSubmit={() => handleSubmit(handleOnCreate)}

Related

Saving additional data to a custom react-flow node

I want to save additional data for the custom node, I used this article for creating a custom node using additional data.
Custom node:
import { useState } from 'react';
import { Handle, Position } from 'react-flow-renderer';
import { Modal } from "react-bootstrap";
import { Form } from 'react-bootstrap';
import { triggerLimitationType } from "../../../enums/triggerLimitationType";
import _ from 'lodash';
import i18next from "../../../i18n";
function CustomNode({ data }) {
const [show, setShow] = useState(false);
const handleClose = () => setShow(false);
const handleShow = () => setShow(true);
const defaultValues = { type: 0 };
const [formData, setFormData] = useState(defaultValues);
const handleChange = event => {
const name = event.target.name;
const value = event.target.value;
setFormData(values => ({ ...values, [name]: value }))
data.attribute = formData;
}
return (
<row>
<div onDoubleClick={handleShow}>
<Handle type="target" position={Position.Top} />
<div className="node-icon" >
<i className="feather icon-skip-back"></i>
</div>
<Handle type="source" position={Position.Bottom} id="b" />
</div>
{show && (
<Modal show={show} onHide={handleClose} className="right fade">
<Modal.Header closeButton>
<Modal.Title>{i18next.t("Trigger")} : {i18next.t("VisitorReturn")} </Modal.Title>
</Modal.Header>
<Modal.Body>
<Form.Group >
<Form.Label>Type</Form.Label>
<Form.Control required as="select" placeholder="Enter Name" value={formData.type} name="type" onChange={handleChange} >
{_.map(triggerLimitationType, (value, key) => (
<option value={value} key={value}>{key}</option>
))}
</Form.Control>
<Form.Control.Feedback type="invalid">Please choose a type.</Form.Control.Feedback>
</Form.Group>
</Modal.Body>
</Modal>
)}
</row>
);
}
export default CustomNode;
Created node:
const onDrop = (event) => {
event.preventDefault();
const reactFlowBounds = reactFlowWrapper.current.getBoundingClientRect();
const type = event.dataTransfer.getData("type");
const position = reactFlowInstance.project({
x: event.clientX - reactFlowBounds.left,
y: event.clientY - reactFlowBounds.top
});
console.log("event", event.dataTransfer);
const newNode = {
id: getId(),
position,
type: type,
data: {
label: `${type} node`, attribute: {
type: 0
} }
};
// setElements((es) => es.concat(newNode));
setElements([...elements, newNode]);
// onSave();
dispatch(updateElements(newNode));
console.log("ONDROP", elements);
};
But when I set the value for an attribute, I get the error: "attribute" is read-only.

appeare third field based on condition

I have a project and in this project I have several interfaces and among these interfaces there is an interface to create an index and I have three fields, the first is the name and the second is the type and I have 20 types, and I want when the type is 11 or 14 to show me a third field which is “RefId”:
import React from 'react';
import FormElement from '../../../../common/form-element';
import { IFormProps } from '../../../../../interfaces/form-props';
import { Col, Row } from 'antd';
import Index from '../../../../../api/nuclearMedicineApi/services/Index';
import { useQuery } from 'react-query';
import { useIntl } from 'react-intl';
interface IndexFormProps extends IFormProps { }
const IndexForm: React.FC<IndexFormProps> = ({
control,
disabled,
type,
data
}) => {
// console.log('data: ', data);
// Get All types
const getAllIndexTypesQuery = useQuery('getAllIndexTypes', () =>
Index.indexGetAllTypes(),
);
const getIndexGetAllLite = useQuery('getIndexGetAllLite', () =>
Index.indexGetAllLite({ Type: data?.type?.value}),
);
const sharedProps = {
control,
disabled,
};
const { formatMessage } = useIntl();
return (
<div className='permissions p-8'>
<Row>
<Col span={24}>
<FormElement
{...sharedProps}
label={'indexName'}
type='input'
name={'name'}
required
/>
</Col>
<Col span={24}>
<FormElement
{...sharedProps}
label={'indexType'}
type='select'
name={'type'}
options={
getAllIndexTypesQuery.data?.map((op) => ({
...op,
label: formatMessage({ id: op?.label }),
}))
}
required
/>
</Col>
{
// (data?.type?.value === 13 || data?.type?.value === 14 || data?.type?.label === 'SubTopography' || data?.type?.label === 'SubCity') ?
(type=== 'Create' && data?.type?.value === 13 || data?.type?.value === 14) ?
<Col span={24}>
<FormElement
{...sharedProps}
label={'refId'}
type='select'
name={'refId'}
options={
getIndexGetAllLite.data?.items?.map((op) => ({
...op,
label: formatMessage({ id: op.label }),
}))
}
required
/>
</Col>: ''
}
{type !== 'Create' && (
<>
<Col span={24} className={"m-8"}>
<FormElement
{...sharedProps}
label={'isActive'}
type='checkbox'
name='isActive'
disabled
/>
</Col>
</>
)}
</Row>
</div>
);
};
export default IndexForm;
you could hande this with an onChange event and a state like "hasExtraField" to render extra field.
const Page = () => {
const [hasExtraField, setHasExtraField] = useState(false);
const handleOnChange = (id) => {
if(id === 14 || id === 11) {
setHasExtraField(true);
}
}
return (
<>
{...yourOtherFields}
{hasExtraField && <input name="extraField" />}
</>
)
}

React input, I defined the state into initial state but still get warning changing uncontrolled to controlled input

I read the controlled and uncontrolled input from React page and follow what they recommend but somehow the warning about uncontrolled into controlled still there.
My code below is actually a page component to edit the data for the project detail. I set the values of the form to the project data after retrieve from API.
import { useState } from "react";
import { useDispatch } from "react-redux";
import {
updateProject,
deleteProject,
getProjectById,
reset,
} from "../../features/project/projectSlice";
import { useSelector } from "react-redux";
import { useNavigate, useParams } from "react-router-dom";
import { useEffect } from "react";
import { Button, Container, Form, Modal, Col, Spinner, Stack, ListGroup, Dropdown, Row } from "react-bootstrap"
import Member from "./Member";
import AddMember from "./AddMember";
function EditProject() {
// Modal
const [show, setShow] = useState(false);
const handleClose = () => setShow(false);
const handleShow = () => setShow(true);
// Data State
const { id } = useParams();
const dispatch = useDispatch();
const navigate = useNavigate();
const { isLoading, isError, projects, isSuccess, message } = useSelector(
(state) => state.project
);
const {user} = useSelector((state)=> state.auth)
const [code, setCode] = useState("");
const [title, setTitle] = useState("");
const [desc, setDesc] = useState("");
const [users, setUsers] = useState([]);
useEffect(() => {
dispatch(getProjectById(id));
if (isError) {
console.log(message);
}
return () => dispatch(reset());
}, [isError, message, dispatch]);
useEffect(() => {
if (isSuccess && projects) {
setCode(projects.code);
setTitle(projects.title);
setDesc(projects.desc);
setUsers(projects.users);
}
}, [isSuccess,projects]);
// Data Saving
const onSubmit = (e) => {
e.preventDefault();
const projectData = {
code,
title,
desc,
users: users.map((user)=> ({...user, _user: user._user._id}))
};
const projectChanges = {
projectId: id,
projectData,
};
console.log(projectData)
dispatch(updateProject(projectChanges));
navigate("/projects");
};
if (isLoading) {
return (
<Container className="mt-5" style={{ justifyContent: "center", display: "flex" }}>
<Spinner animation="border" />
</Container>
)
}
if(user._id !== projects.manager){
return <h5>You do not have permission to edit this project</h5>
}
return (
<>
<Container fluid="md" className="mt-5">
<Form onSubmit={onSubmit}>
<Stack gap={3}>
<Form.Group>
<Form.Label>Code</Form.Label>
<Form.Control type="text" placeholder="Enter project codename" value={code} onChange={(e) => setTitle(e.target.value)} />
<Form.Text className="text-muted">Codename is a short form of the project that used widely</Form.Text>
</Form.Group>
<Form.Group>
<Form.Label>Title</Form.Label>
<Form.Control type="text" rows={5} placeholder="Enter project description" value={title} onChange={(e) => setTitle(e.target.value)} />
</Form.Group>
<Form.Group>
<Form.Label>Description</Form.Label>
<Form.Control as="textarea" rows={5} placeholder="Enter project description" value={desc} onChange={(e) => setDesc(e.target.value)} />
</Form.Group>
<Form.Group>
<Row className="mb-2">
<Col className="vertical-align"><Form.Label style={{ margin: "0" }}>Team Member</Form.Label></Col>
<Col md="auto"><Button variant="primary" onClick={handleShow}>Add People</Button></Col>
</Row>
<ListGroup>
{users && users.map((user,index) => <Member key={user._user._id} user={user} setUser={setUsers} users={users} index={index} />)}
</ListGroup>
</Form.Group>
<Row className="align-items-end">
<Col className="d-flex justify-content-end gap-2">
<Button variant="danger" className="px-3">Delete</Button>
<Button type="submit" className="px-5">Submit</Button>
</Col>
</Row>
</Stack>
</Form>
</Container>
<AddMember show={show} onHide={handleClose} />
</>
);
}
export default EditProject;
I found that the code below is source of the warning, I did remove and the warning gone. I want to know what exactly my mistake that cause the warning and the solution for this.
useEffect(() => {
if (isSuccess && projects) {
setCode(projects.code);
setTitle(projects.title);
setDesc(projects.desc);
setUsers(projects.users);
}
}, [isSuccess,projects]);
Here is sample of API retrieved,
{
"_id": "abc123",
"code": "PROJ",
"title": "Project",
"desc": "Project detail.",
"users": [
{
"_user": "1",
"role": "UI/UX Designer",
"status": "Active"
},
{
"_user": "2",
"role": "Software Engineer",
"status": "Active"
}
],
}
This is most likely happening because one of the properties of projects is undefined.
Try checking that each property is not undefined before setting it:
useEffect(() => {
if (isSuccess && projects) {
if (projects.code !== undefined) {
setCode(projects.code);
}
if (projects.title !== undefined) {
setTitle(projects.title);
}
if (projects.desc !== undefined) {
setDesc(projects.desc);
}
if (projects.users !== undefined) {
setUsers(projects.users);
}
}
}, [isSuccess, projects]);
Or, you could use nullish coalescing:
useEffect(() => {
if (isSuccess && projects) {
setCode(projects.code ?? '');
setTitle(projects.title ?? '');
setDesc(projects.desc ?? '');
setUsers(projects.users ?? []);
}
}, [isSuccess, projects]);

How to create a add image input in formik

I want to create a customized input field in formik for an image upload. I want to pass binary file data to backend, but it is taking the file location. Can you help me?
Here is the boilerplate code:
import React from 'react';
import { useField } from 'formik';
import { Form, Col, Row } from 'react-bootstrap';
const FileInput = ({ label, ...props }) => {
const [field, meta] = useField(props);
return (
<>
<Form.Group as={Row} controlId="">
<Form.Label htmlFor={props.id || props.name} column sm={3}>
{label}
</Form.Label>
{props.initalImage ? (
<img height="35px" alt={props.initalImage} src={props.initalImage} />
) : null}
<Col sm={4}>
<Form.Control
{...field}
{...props}
accept={props.accept}
isValid={meta.touched && !meta.error}
isInvalid={Boolean(meta.touched && meta.error)}
/>
{meta.error ? (
<>
<Form.Control.Feedback type="invalid">
{meta.error}
</Form.Control.Feedback>
</>
) : null}
<span className="form-text text-muted mt-3">
Please choose an icon which is min. 256x256px.
</span>
</Col>
</Form.Group>
</>
);
};
export { FileInput };
and here is where I am calling it:
<FileInput
label="Light Icon:"
type="file"
name="lightIcon"
accept="image/x-png"
onBlur
/>
I tried this but it is not working:
<FileInput
label="Light Icon:"
type="file"
name="lightIcon"
accept="image/x-png"
onBlur
onChange={(e) => {
setFieldValue('lightIcon', e.target.files[0]);
}}
/>
The trick with <input> elements with type="file" is that we shouldn't set their value manually, it actually uses it's own mechanism along with browser's security implementations and set it itself on the client's machine.
So we don't have access to the real path of the file on the disk, instead we are able to use a FileReader object to asynchronously read contents of the file as blob of buffer and pass it to our parent component.
There are cases where you can change file input's value
programmatically, like to null to reset your input.
The following example implements an UploadFiled component with type="file" get wrapped with a custom Field component, whenever child component getting update, a file is being uploaded, and parent component will knows and starts reading the contents of the specified Blob, once finished, the result attribute contains a data url representing the file's data will set as it's value. It currently accepts an image base on the validation that is set for it using Yup.
The traditional application/json type won’t help in uploading the image to your server, the FormData will. You need to write your handleSubmit() using the form data and pass the values handled by Formik.
Check my CodeSandbox for the working demo.
const handleSubmit= () => {
// Create an object of formData
const formData = new FormData();
// Update the formData object
formData.append("myFile", file, file.name);
// Details of the uploaded file
console.log(file);
// Request made to the backend api
// Send formData object
axios.post("api/uploadfile", formData);
};
// UploadForm.jsx
import React, { useState, useEffect } from "react";
import { Field, useField } from "formik";
import { Grid, FormHelperText } from "#material-ui/core";
import UploadField from "../../FormFields/UploadField";
import Thumb from "../Helper/Thumb";
const ImageForm = (props) => {
const {
formField: { image }
} = props;
const [field, meta, helper] = useField(image.name);
const { touched, error } = meta;
const { setValue } = helper;
const isError = touched && error && true;
const { value } = field;
const [fileName, setFileName] = useState(value.name);
const [file, setFile] = useState(value.file);
const [src, setSrc] = useState(value.src);
const _onChange = (e) => {
let reader = new FileReader();
let file = e.target.files[0];
if (file) {
reader.onloadend = () => setFileName(file.name);
if (file.name !== fileName) {
reader.readAsDataURL(file);
setSrc(reader);
setFile(file);
}
}
};
useEffect(() => {
if (file && fileName && src) {
setValue({ file: file, src: src, name: fileName });
console.log(fileName);
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [src, fileName, file]);
return (
<React.Fragment>
<Grid container spacing={3} justify="center" alignItems="center">
<Grid item xs={12}>
<label>
{image.label}
</label>
<br />
<div
style={{
display: "flex",
justifyContent: "flex-start",
fontSize: "1.2em"
}}
>
<Field
variant="outlined"
field={field}
component={UploadField}
onChange={_onChange}
isError={isError}
/>
{isError && <FormHelperText color={"red"}>{error}</FormHelperText>}
</div>
</Grid>
<Grid item>{file && src && <Thumb file={file} src={src}></Thumb>}</Grid>
</Grid>
</React.Fragment>
);
};
export default ImageForm;
import React from "react";
import { Field } from "formik";
const UploadField = ({
field,
form: { touched, errors },
name,
label,
isError,
...props
}) => {
return (
<>
<Field
variant="outlined"
name="uploader"
title={label}
type={"file"}
{...props}
/>
</>
);
};
export default UploadField;

React Dynamic Filtering to Filter the Card Component

I am new to react, I am working on a project. I need little help regarding the filtering of the data. There are two filters one-: State and Location. The location is dependent on State, which is working fine, but after that, I want to filter my card component i.e VideoCategory card basis on above-selected value. There are three levels of filtering and for the third level, the filtering is not working.
Please, anyone, help me out, as I am trying to fix this issue from last two days.
import React, { useState, useEffect } from "react";
import Menu from "../common/Menu";
import { Form, Container, Row, Col } from "react-bootstrap";
import {
getVideosBasedOnCategories,
getAllStates,
} from "./helper/userApiCalls";
import VideoCard from "../common/VideoCard";
const CategoryVideos = ({ match }) => {
const [videoCategory, setVideoCategory] = useState([]);
const [selectState, setSelectState] = useState([]);
const [selectLocation, setSelectLocation] = useState([]);
let location="";
const preload = (categoryId) => {
getVideosBasedOnCategories(categoryId).then((data) => {
if (data.error || !data) {
console.log(data.error);
return <h1>No Data to Show Now</h1>;
} else {
setVideoCategory(...videoCategory, data);
}
});
};
//Intial Loading
useEffect(() => {
preload(match.params.categoryId);
getAllStateForSelect();
}, []);
//getting data for first Select Component from API
const getAllStateForSelect = () => {
getAllStates().then((data) => {
console.log(data);
if (data.error) {
return console.log(data.error);
} else {
setSelectState(...selectState, data);
}
});
};
const handleChange = (event) => {
console.log(event.target.value);
setSelectLocation(event.target.value);
};
const onLocationChange=(event)=>{
location = event
console.log(location)
}
//Storing Location into Option for Second Select
const onSplit = (x) => {
var arr = [];
for (let i = 0; i < x.citynames.length; i++) {
arr.push(
<option key={i} value={x.citynames[i]}>
{x.citynames[i]}
</option>
);
}
return arr;
};
return (
<div>
<Menu />
<Container style={{ marginTop: "200px" }}>
<Form>
<Form.Group controlId="exampleForm.SelectCustom">
<Form.Label>Select State</Form.Label>
<Form.Control
as="select"
onChange={handleChange.bind(selectState[0])}
custom
>
{selectState.map((data, index) => (
<option name="setmap" key={index} value={data._id}>
{data.statename}
</option>
))}
</Form.Control>
</Form.Group>
</Form>
<Form>
<Form.Group controlId="exampleForm.SelectCustom">
<Form.Label>Select Location</Form.Label>
<Form.Control
as="select"
onChange={(e) => onLocationChange(e.target.value)}
custom
>
{selectState
.filter((selectState) => selectState._id.includes(selectLocation))
.map((e) => onSplit(e))}
</Form.Control>
</Form.Group>
</Form>
<Row>
{videoCategory.filter(videocard=>videocard.location.includes(location.toString()))
.map((videocard) => {
return (
<Col lg={4} xs={12} md={12} className="py-3 px-3">
<VideoCard videoCategory={videocard}
/>
</Col>
);
})}
</Row>
</Container>
</div>
);
};
export default CategoryVideos;
VideoCategory File
import React, { Fragment,useEffect,useState } from "react";
import { Card, Button, Container, Row, Col } from "react-bootstrap";
import {getStateById} from '../user/helper/userApiCalls'
const VideoCard = ({ videoCategory }) => {
const [state,setState] = useState("");
const [city,setCity] = useState("");
//const { name, link, description } = videoCategory;
const getStateByFromVideoId = (stateId)=>{
getStateById(stateId).then((data)=>{
console.log(data)
if(data.error){
return console.log(data.error)
}
else{
setState(data.statename)
}
})
}
useEffect(() => {
getStateByFromVideoId(videoCategory.state);
}, []);
return (
<Container fluid>
<iframe
src={videoCategory.link}
width="300px"
height="300px"
id="myId"
className="myClassname"
display="initial"
position="relative"
allowfullscreen="allowfullscreen"
></iframe>
<Card style={{ width: "300px", }}>
<Card.Body>
<Card.Title>{videoCategory.name}</Card.Title>
<Card.Text>{videoCategory.description}</Card.Text>
<Card.Text>{state}</Card.Text>
<Card.Text>{videoCategory.location}</Card.Text>
<Button variant="primary">Go somewhere</Button>
</Card.Body>
</Card>
</Container>
);
};
export default VideoCard;
**UPDATE**
The API structure for State, which will reflect in card, the location is hardcoded, it doesn't have any id.
{
"_id": "5eb59d177693b6f99404f4c6",
"link": "https://www.youtube.com/embed/NEIwl93Yr8o",
"description": "LifeStyle",
"name": "Testing",
"state": "5eb564ec7693b6f99404f4c5",
"category": "5ead7555fb5c440f458e625b",
"location": "Kondapur",
"createdAt": "2020-05-08T17:55:35.731Z",
"updatedAt": "2020-05-08T18:28:43.635Z",
"__v": 0
},
** The Filter of State and location(citynames) is :**
{
"citynames": [
"ABC",
"EFG"
],
"_id": "5eb2ad8b554215be68773cf1",
"statename": "Madras",
"createdAt": "2020-05-06T12:28:59.149Z",
"updatedAt": "2020-05-06T12:28:59.149Z",
"__v": 0
},
Here's i change some code, but there's also some notes i need to confirm first
import React, { useState, useEffect } from "react";
import Menu from "../common/Menu";
import { Form, Container, Row, Col } from "react-bootstrap";
import {
getVideosBasedOnCategories,
getAllStates,
} from "./helper/userApiCalls";
import VideoCard from "../common/VideoCard";
const CategoryVideos = ({ match }) => {
const [videoCategory, setVideoCategory] = useState([]);
const [apiStates, setApiStates] = useState([])
const [selectedState, setSelectedState] = useState(null);
// Change default value to '' i think
const [selectedLocation, setSelectedLocation] = useState(null);
const preload = (categoryId) => {
getVideosBasedOnCategories(categoryId).then((data) => {
if (data.error || !data) {
console.log(data.error);
return <h1>No Data to Show Now</h1>;
} else {
setVideoCategory(...videoCategory, data);
}
});
};
//Intial Loading
useEffect(() => {
preload(match.params.categoryId);
getAllStateForSelect();
}, []);
//getting data for first Select Component from API
const getAllStateForSelect = () => {
getAllStates().then((data) => {
console.log(data);
if (data.error) {
return console.log(data.error);
} else {
setApiStates(...apiStates, data);
// remove this line
// setSelectedLocation(data[0])
}
});
};
const onStateChange = event => {
setSelectedState(event.target.value)
}
const onLocationChange= event => {
setSelectedLocation(event.target.value);
}
//Storing Location into Option for Second Select
const onSplit = (x) => {
var arr = [];
for (let i = 0; i < x.citynames.length; i++) {
arr.push(
<option key={i} value={x.citynames[i]}>
{x.citynames[i]}
</option>
);
}
return arr;
};
return (
<div>
<Menu />
<Container style={{ marginTop: "200px" }}>
<Form>
<Form.Group controlId="exampleForm.SelectCustom">
<Form.Label>Select State</Form.Label>
<Form.Control
as="select"
onChange={onStateChange}
custom
>
{apiStates.map((data, index) => (
<option name="setmap" key={index} value={data._id}>
{data.statename}
</option>
))}
</Form.Control>
</Form.Group>
</Form>
<Form>
<Form.Group controlId="exampleForm.SelectCustom">
<Form.Label>Select Location</Form.Label>
<Form.Control
as="select"
onChange={onLocationChange}
custom
>
{apiStates
.filter(apiState => apiState._id === selectedState)
.map(value => onSplit(value))}
</Form.Control>
</Form.Group>
</Form>
<Row>
// I'm curious about the location, is it array of string ?
{videoCategory.filter(videocard => videocard.location === selectedLocation))
.map((videocard) => {
return (
<Col lg={4} xs={12} md={12} className="py-3 px-3">
<VideoCard videoCategory={videocard} />
</Col>
);
})}
</Row>
</Container>
</div>
);
};
export default CategoryVideos;
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>

Resources