I'm fetching data from my backend using Axios in ReactJS. The response should be an array of data with I believe my backend returns 5 elements and I have proved it using postman.
Here is my code :
export const getRoomMessages = async (chatRoomId, limit = 20, page = 1) => {
try {
const response = await backendApi.get(
`/chat_rooms/${chatRoomId}/chat_room_messages?limit=${limit}&page=${page}`,
);
console.log('reponse', response.data.data);
console.log('data0', response.data.data[0]);
console.log('data1', response.data.data[1]);
console.log('data2', response.data.data[2]);
console.log('data3', response.data.data[3]);
console.log('data4', response.data.data[4]);
return [null, response.data];
} catch (e) {
return [e, null];
}
};
And here is the screenshot of the console log:
Before I expand the response data it was detected as Array with have 5 elements. But after I expand the response data it became 4 elements ? I'm missing the one element. So I tried to console log all the array element from index 0 to 4, and surprisingly it was no error and I got the missing element. But there is one more strange thing, the console log order is also random if we look at the data[0] in the response tab (which I border with blue) is the data with id "241" but when I console the log separately (which I border with red) it becomes "254" which is the missing element.
I'm still not sure what's causing this problem and I'm not sure it's a bug in Axios.
Full Source Code (How I updated my State) :
componentDidMount() {
this.initialFetch();
this.connectToWebSocket();
}
componentWillUnmount() {
this.disconnectFromWebsocket();
}
initialFetch = async () => {
this.fetchRoomDetail();
this.fetchMessages();
};
fetchMessages = async () => {
this.setState({ loading: true, error: null });
const [err, res] = await getRoomMessages(
this.roomId,
this.state.pagination.size,
1,
);
if (res) {
this.setState({
messages: res.data.reverse(),
pagination: {
...this.state.pagination,
current: 2,
},
noMoreLoad: res.paging.total_page <= 1 ? true : false,
loading: false,
});
this.scrollToBottom();
await this.readMessage(res.data.pop().id);
} else {
this.setState({ error: errorToString(err), loading: false });
}
};
render() {
return (
<AdminLayout>
{this.state.error ? (
<Error500Page
errMessage={this.state.error}
tryAgain={this.initialFetch}
/>
) : this.state.loading ? (
<ActivityIndicator
number={3}
diameter={10}
borderWidth={1}
duration={200}
activeColor={theme.colors.ui.primary}
/>
) : (
<div className="card bg-theme-secondary mxH75">
<div className="customCardHeader">
<i
className="bx bx-arrow-back mr-2p sizeLarge pointer"
onClick={() => this.props.navigate('/chats/')}
></i>
<Avatar
src={`https://ui-avatars.com/api/?background=${theme.colors.ui.primary.substring(
1,
)}&name=${
this.state.room?.chat_room_title
}&size=56&color=${theme.colors.text.inverse.substring(1)}`}
alt={this.state.room?.chat_room_title + "'s Profile Picture"}
size="medium"
type="circle flexible"
/>
<h5 className="noMarginBottom">
{this.state.room?.chat_room_title}
</h5>
</div>
<div className="customContent" id="vertical-example">
{this.state.noMoreLoad ? null : (
<div className="rce-container-smsg m-1">
{this.state.loadingMore ? (
<div
className="spinner-border spinner-border-sm text-success"
role="status"
>
<span className="visually-hidden">Loading...</span>
</div>
) : (
<Button
onClick={() => this.fetchMoreMessages()}
color={theme.colors.text.inverse}
backgroundColor={theme.colors.ui.primary}
text="Muat Pesan Sebelumnya"
/>
)}
</div>
)}
{this.state.messages?.map((x) =>
x.user ? (
<MessageBox
key={x.id.toString()}
onDownload={
x.image_url
? () => this.saveAs(x.image_url)
: x.file_url
? () => this.saveAs(x.file_url)
: null
}
removeButton={
x.user.id === this.currentUserId && !x.is_deleted
? true
: false
}
onRemoveMessageClick={() => this.unsentMessage(x.id)}
forwarded={!x.is_deleted ? true : false}
title={this.state.room?.type_id === 1 ? x.user.name : null}
position={
x.user.id === this.currentUserId ? 'right' : 'left'
}
type={x.image_url ? 'photo' : x.file_url ? 'file' : 'text'}
text={
x.is_deleted
? Parser(
'<i class="bx bx-comment-x" style="color:red;"></i><i style="color:red;"> Pesan ini telah dihapus</i>',
)
: x.is_forwarded
? Parser(
`<i class="bx bx-subdirectory-right" style="color:blue;"></i><i style="color:blue;"> Forwarded</i><br/>${x.message}`,
)
: x.file_url
? x.file_url.split('/').pop()
: x.message
}
data={
x.image_url
? {
uri: x.image_url,
alt: x.image_url.split('/').pop(),
width: 300,
height: 300,
status: {
autoDownload: false,
error: false,
download: true,
click: true,
loading: false,
},
}
: x.file_url
? {
name: x.file_url.split('/').pop(),
extension: x.file_url.split('.').pop(),
uri: x.file_url,
status: {
autoDownload: false,
error: false,
download: false,
click: false,
loading: false,
},
}
: null
}
date={new Date(x.created_at_timestamp * 1000)}
/>
) : (
<SystemMessage text={x.message} key={x.id.toString()} />
),
)}
<div ref={this.messagesEndRef} />
</div>
<div className="sendMessageContainer">
<Input
value={this.state.focusedMessage}
onChange={(e) =>
this.setState({ focusedMessage: e.target.value })
}
// referance={this.inputReferance}
placeholder="Masukkan Pesan"
multiline={true}
className="form-control"
leftButtons={
<>
{/* <input type={'file'} />
<input type={'file'} /> */}
</>
}
rightButtons={
this.state.sendingMessage ? (
<div className="spinner-border text-success" role="status">
<span className="visually-hidden">Loading...</span>
</div>
) : (
<Button
onClick={() => this.sendMessage()}
color={theme.colors.text.inverse}
backgroundColor={theme.colors.ui.primary}
text="Kirim"
/>
)
}
/>
</div>
{/* <div>{JSON.stringify(this.state.messages)}</div> */}
</div>
)}
</AdminLayout>
);
}
return [null, response.data];
Shouldn't it be
return [null, response.data.data];
?
Finally I found my mistake, when I want to get the last element's id, I used pop() method. I have updated my code with res.data[res.data.length-1] and it works now !
Related
In a specific case, I had to render some UI in a specific skeleton/format. For that case I decided to make an array of objects, in which each object has some specific UI components, which are supposed to be rendered on the UI. I was able to see the expected result, but as we know react components re-render on each state update, and all the functions and variables are reinstantiated.
If the components inside the array are dependent on states of the component in which they are used, it will keep on rerendering the array as well. Will it cause massive performance issues?
I decided to use hooks like useMemo and wrap the array of components in it and by putting the states necessary for components to work in the dependency array of useMemo, but there are way too many states. What is the correct way to render Components in such cases ?
Please take a look at the code mentioned below to get some context.
Code:
Return function
<div className='create-action'>
{
loading
?
loadingComponent
? loadingComponent
:<div className='action-loader'> <CirclesLoader/> </div>
: <>
{
preview
? PREVIEW.map((item, index) => (
<React.Fragment key={item.id}>
{item.content}
</React.Fragment>
))
: CREATION.map((item, index) =>
item.hidden
? null
: <React.Fragment key={item.id}>
{item.content}
</React.Fragment>
)
}
{
showWarning
? <NewPromptDialog
is_active={showWarning}
heading="Cancel Action?"
message="Your progress will be lost. Do you still wish to continue?"
secondary_button_text="No"
primary_button_text="Cancel Action"
onClose={() => setShowWarning(false)}
onAccept={closeCreateAction}
button_right_aligned={true}
defaultPromptCtaClasses="create-action-button-delete-prompt"
/>
: null
}
</>
}
</div>
CREATION array used in Rendering
const CREATION = [
{
id: 1,
content:
<div className='header'>
<span className="header-text">
{
page === TEMPLATE
? <span className='header-back-cta' onClick={
JSON.stringify(orgActionTemplateData) === JSON.stringify(actionTemplateData)
? closeCreateAction
: () => setShowWarning(true)
}><Back/></span>
: null
}
Create Action
</span>
<span className="icon" onClick={
JSON.stringify(orgActionTemplateData) === JSON.stringify(actionTemplateData)
? closeCreateAction
: () => setShowWarning(true)
}>
<Close />
</span>
</div>
},
{
id: 2,
content:
<div className='middle'>
{
DATA_BODY.length
? DATA_BODY.map((data, index) => {
return (
!data.hidden
? data.type === 'warning' // To display WarningCard in Chat
? data.content ? data.content : null
: <>
<div key={index} className="middle-block ">
{data.title ? <div className="middle-title">{data.title}</div> : null}
{data.subtitle ? <div className="middle-subtitle">{data.subtitle}</div> : null}
{data.content ? data.content : null}
{data.focusContent && data.focus ? data.focusContent : null}
{
data.validationError && data.errorContent
? <p className={showValidationErrors ? `middle-error-content` : 'middle-warning-content'}><img src={showValidationErrors ? information_circle : warning_information_circle} height="13px" width="13px" alt="info-circle" />{data.errorContent}</p>
: null
}
</div>
<div className='middle-bottom-divider' />
</>
: null
)
})
: null
}
</div>
},
{
id: 3,
content: <div className={'footer'}>
<div data-for="create-cta" data-tip="Please fill the required fields" className={classNames({'disabled-cursor': (apiCall || disableCreateCTA)})} onClick={disableCreateCTA ? () => {
validateForm('errors');
} : () => {}}>
<Button
type="primary"
label={"Create"}
disabled={apiCall || disableCreateCTA} // Add disable logic here
onClick={!apiCall ? onClickHandler : () => {}}
iconAfterLabel={forward_system_white}
btnClasses={loading ? "is-loading" : null}
/>
{disableCreateCTA ? <ReactTooltip effect="solid" place="left" id="create-cta" /> : null}
</div>
</div>
}
]
DATA_BODY array used in CREATION array object
const DATA_BODY = [
{
id: DATA_BODY_ID++,
type: 'warning',
content: <WarningCard
message={"Only the details related to the action will be shared with the assignee. No confidential information or data from the chat will be shared."}
icon={info_icon}
type={"info"}
/>,
hidden: (page && ![CRISP_CHAT, IJP].includes(page))
},
{
id: DATA_BODY_ID++,
title: "Summary*",
focus: summaryFocus,
subtitle: "Give a summary that is crisp and clear",
content:<input id="summary" className={`input ${(showValidationErrors || showValidationWarning) && !actionTemplateData.summary ? showValidationErrors ? 'input-error' : 'input-warning' : ''}`} name='summary' value={actionTemplateData.summary} required autoComplete='off' onChange={onChangeHandler} onFocus={() => setSummaryFocusValue(true)} onBlur={() => setSummaryFocusValue(false)} maxLength={80} placeholder="E.g. Provide more opportunities to learn and grow" />,
focusContent: <label className={classNames('error-status',{'warning': actionTemplateData.summary?.length >= 70 && actionTemplateData.summary?.length < 80}, {'error': actionTemplateData.summary?.length === 80 })}>{actionTemplateData.summary?.length ? actionTemplateData.summary.length : 0} / 80</label>,
validationError: (showValidationErrors || showValidationWarning) && !actionTemplateData.summary,
errorContent: 'Please enter the summary'
},
{
id: DATA_BODY_ID++,
title: "Description*",
subtitle: "Add more details about the action",
content: <TextArea
placeholder='E.g. Make your workforce future ready'
classes={`input ${(showValidationErrors || showValidationWarning) && !actionTemplateData.description ? showValidationErrors ? 'input-error' : 'input-warning' : ''}`}
name='description'
id="description"
value={actionTemplateData.description}
required
autoComplete='off'
onChange={onChangeHandler}
maxLength={1024}
auto_grow
re_size="none"
/>,
validationError: (showValidationErrors || showValidationWarning) && !actionTemplateData.description,
errorContent: 'Please enter the description'
},
{
id: DATA_BODY_ID++,
title: "Assignee*",
subtitle: "This person will be notified and be responsible for completing the action",
content: <div className='create-action-assignee-dropdown' name='assignee-dropdown'>
{
[IJP, CRISP_CHAT].includes(page) && assigneeListForCrispChat?.length
? <AssigneeDropdownComponent
id={"assignee-dropdown"}
assigneeCategories={assigneeListForCrispChat}
click_mode={true}
trigger_close={assigneeCrispChatDropdownTrigger}
selected_item_text={selectedAssignee && selectedAssignee[0]?.value ? selectedAssignee[0].label : "Select Assignee" }
no_selection_text={'Select Assignee'}
classes={`crisp-chat-assignee-dropdown`}
dropdown_button_class={(showValidationErrors || showValidationWarning) && actionTemplateData.assignee_id === null ? showValidationErrors ? 'input-error' : 'input-warning' : ''}
onClick={(assignee) => {
setSelectedAssignee(assignee);
setAssigneeCrispChatDropdownTrigger(trigger => !trigger);
}}
onOutsideClick={() => {}}
/>
: assigneeDropdownData && Object.keys(assigneeDropdownData).length
? <span><MultiCheckboxDropdown data={assigneeDropdownData} /></span>
: null
}
</div>,
// hidden: ((["IJP", "Crisp Chat"].includes(page) && !assigneeListForCrispChat.length) || !(assigneeList && assigneeList.list_items && assigneeList.list_items.length > 0)) ? true : false,
validationError: (showValidationErrors || showValidationWarning) && actionTemplateData.assignee_id === null,
errorContent: 'Please select the assignee'
},
{
id: DATA_BODY_ID++,
title: "Start Date*",
subtitle: "Set the start date for the action",
content:
<div className="create-action-date-picker" name='start-date'>
<CalenderDropdown
id={"startDate-dropdown"}
date={actionTemplateData.startDate ? new Date(moment.unix(actionTemplateData.startDate).format('DD MMM YYYY')) : null}
onChange={(date) => setDatePeriod("startDate", date)}
no_selection_text={'Select Start Date'}
selected_item_class='calendar-dropdown-selected-text'
selected_item_text={actionTemplateData.startDate ? moment.unix(actionTemplateData.startDate).format('DD MMM YYYY') : 'Select Start date'}
// classes={'is-left cal-dropdown dropdown-fit-content'}
dropdown_button_class={(showValidationErrors || showValidationWarning) && actionTemplateData.startDate === null ? showValidationErrors ? 'input-error' : 'input-warning' : ''}
minDate={moment().hour() < 21 ? new Date(new Date().setDate(new Date().getDate())) : new Date(new Date().setDate(new Date().getDate() + 1))}
click_mode={true}
trigger_close={startDateTrigger}
customIcon={calendar_icon}
className={'date-action-creation'}
/>
{(showValidationErrors || showValidationWarning) && actionTemplateData.startDate === null ? <p className={`${showValidationErrors ? 'middle-error-content' : 'middle-warning-content'} mg-top-5`}>
<img src={showValidationErrors ? information_circle : warning_information_circle } height="13px" width="13px" alt="info_circle" />
Please select the start date
</p> : null}
</div>
},
{
id: DATA_BODY_ID++,
title: "Due Date*",
subtitle: "Set the due date for the action",
content: <div className="create-action-date-picker" name='end-date'>
<CalenderDropdown
id={"endDate-dropdown"}
date={actionTemplateData.endDate ? new Date(moment.unix(actionTemplateData.endDate).format('DD MMM YYYY')) : null}
onChange={(date) => setDatePeriod("endDate", date)}
no_selection_text={'Select Due Date'}
selected_item_class='calendar-dropdown-selected-text'
selected_item_text={actionTemplateData.endDate ? moment.unix(actionTemplateData.endDate).format('DD MMM YYYY') : "Select Due Date"}
// classes={'is-left cal-dropdown dropdown-fit-content'}
dropdown_button_class={(showValidationErrors || showValidationWarning) && actionTemplateData.endDate === null ? showValidationErrors ? 'input-error' : 'input-warning' : ''}
minDate={actionTemplateData.startDate ? new Date(moment.unix(actionTemplateData.startDate)) : moment().hour() < 21 ? new Date(new Date().setDate(new Date().getDate())) : new Date(new Date().setDate(new Date().getDate() + 1))}
click_mode={true}
trigger_close={endDateTrigger}
customIcon={calendar_icon}
className={'date-action-creation'}
/>
{(showValidationErrors || showValidationWarning) && actionTemplateData.endDate === null ? <p className={`${showValidationErrors ? 'middle-error-content' : 'middle-warning-content'} mg-top-5`}>
<img src={showValidationErrors ? information_circle : warning_information_circle } height="13px" width="13px" alt="info_circle" />
Please select the due date
</p> : null}
</div>
},
{
id: DATA_BODY_ID++,
title: "Checklist items*",
subtitle: "Break down your action into bite sized checklist items (At least 1 required)",
content: <AddMore
id={'checklist-items'}
data={checklistItems}
placeholder="E.g. Recognize 3 team members in the all hands"
addCTALabel="Add Checklist Items"
minField={1}
customDisableAddCTA={!checklistItems?.filter(list => list !== "")?.length || checklistItems?.filter(list => list !== "")?.length === 10}
validationError={showValidationErrors && checklistItems.filter(item => item === '')?.length}
validationWarning={showValidationWarning && checklistItems.filter(item => item === '')?.length}
tooltipData={checklistItems.length === 10 ? 'You can add a maximum of 10 Checklist items.' : 'Fill the empty fields <br/> before creating new ones'}
errorContent={'Please add at least 1 checklist item'}
onChange={(checklist = ['']) => {
if (checklist) {
setChecklistItems(checklist);
}
}}
/>
},
{
id: DATA_BODY_ID++,
title: "Tag Drivers & Elements",
subtitle: "Associate Drivers and Elements with the action",
content: <div className='create-action-assignee-dropdown'><MultiCheckboxDropdown data={driverElementsDropdown} classses="is-up" /></div>
},
{
id: DATA_BODY_ID++,
title: "Additional resources",
subtitle: "Add links to any reference videos or articles",
content: <div className="padding-bottom-50 fullwidth" ><AddMore
data={referenceLinks}
placeholder="E.g. www.example.com/blog"
addCTALabel="Add Link"
customDisableAddCTA={referenceLinks.filter(link => !isValidURL(link)).length > 0}
validationError={showValidationErrors && referenceLinks.filter(link => link !== "").length}
validationWarning={showValidationWarning && referenceLinks.filter(link => link !== "").length}
tooltipData={'Fill the empty fields <br/> before creating new ones'}
errorContent={'Please enter a valid resource link'}
type="ref_links"
errorIndexItem={referenceLinks.findIndex(link => !isValidURL(link))}
onChange={(r) => {
if (r) {
setReferenceLinks(r)
}
}}
/> </div>
}
]
I'm unable to show conditional output with the ternary operator. I want to pass a value to a function and show only related info from the state. My code:
import React, {useState} from 'react';
function Tasks({taskId, index}){
{task.parentId == taskId : } //Unable to code this.
return( //show only tasks where parentId == taskId
<div>
<div> {task.title} </div>
<div> {task.body} </div>
</div>
)
}
function App(){
const[tasks, setTasks] = useState([
{
taskId: 1,
title: 'Task1',
body: 'This is the body of the task1',
isComplete: false,
parentId: 0
},
{
taskId: 2,
title: 'Task2',
body: 'This is the body of the task2',
isComplete: false,
parentId: 1
},
{
taskId: 3,
title: 'Task3',
body: 'This is the body of the task3',
isComplete: false,
parentId: 1
},
{
taskId: 4,
title: 'Task4',
body: 'This is the body of the task4',
isComplete: false,
parentId: 3
}
])
return(
<div style={{marginLeft: 20}}>
<h1>ToDo</h1>
{tasks.map((task, index)=>
<Tasks
taskId=1
/>
)}
</div>
)
}
export default App;
So, I want to only show the tasks that have the parentId as 1. How should I go about this?
If you're trying to render only those tasks with the specified id, you may not have to use the ternary operator.
function renderTasks(id) {
return tasks
.filter(({ taskId }) => taskId == id)
.map(({ title, body }) => (
<div>
<div> {title} </div>
<div> {body} </div>
</div>
));
}
For the least modification to the code, you can return an empty fragment or null:
function Tasks({ task }) {
return task.parentId == task.taskId
? (
<div>
<div> {task.title} </div>
<div> {task.body} </div>
</div>
)
: null;
}
(make sure to use parentId, not pasrentId, and task.taskId, not taskId - you aren't passing task as a prop currently, so change the code to do so: <Tasks task={task} />)
But I think it'd make more sense to use .filter in the caller:
return (
<div style={{ marginLeft: 20 }}>
<h1>ToDo</h1>
{tasks
.filter(task => task.parentId === task.taskId)
.map(task => <Task task={task} />)
}
</div>
)
(since Tasks renders a single task, consider calling it Task instead of Tasks)
I'am trying to fetch data from an API, then set it on my State and display that state in a table. The issue is that the render method is called first and causes my state to be undefined which causes this issue:
The console.log()
https://i.gyazo.com/642f8d6fe3481d2db9763091618e19de.png
state = {
loading: true,
data: [],
customColumns: [],
}
componentDidMount = () => {
axios.get('http://localhost:8080/lagbevakning/revision/subscriptions?id=' + (this.props.match.params.id)).then(response => {
this.setState({
data: response.data,
loading: false
})
})
axios.get('http://localhost:8080/lagbevakning/company?id=' + sessionStorage.getItem("id")).then(response2 => {
this.setState({
customColumns: response2.data
})
})
}
displayCustomColumn = (columnInput) => {
if(columnInput === null) {
return
} else {
return <Table.HeaderCell>{columnInput}</Table.HeaderCell>
}
}
displayList = () => {
return (
<div>
<Table celled>
<Table.Header>
<Table.Row>
{this.displayCustomColumn(this.state.customColumns.customHeaderName1)}
{this.displayCustomColumn(this.state.customColumns.customHeaderName2)}
</Table.Row>
</Table.Header>
{this.state.data.map((item, i) => (
<Table.Body key={i}>
<Table.Row>
<Table.Cell>{item}</Table.Cell>
<Table.Cell>{item}</Table.Cell>
</Table.Row>
</Table.Body>
))}
</Table>
</div>
)}
render() {
return (
<div>
{this.state.loading
? <div><h1>LOADING...</h1></div>
:
<h2> Company Name: {this.state.customColumns.companyName} <br/>
Revision Name: {this.state.data.name} <br/>
Revision ID: {this.state.data.id} </h2>
}
{this.displayList()}
</div>
)
}
}
Any suggestions on how to solve this issue is much appreciated, thank you.
render() {
return (
<div>
{this.state.loading
? <div><h1>LOADING...</h1></div>
:
<h2> Company Name: {this.state.customColumns.companyName} <br/>
Revision Name: {this.state.data.name} <br/>
Revision ID: {this.state.data.id} </h2>
}
{this.displayList()}
</div>
)
}
I think you are expecting data to be array so you can't access name and id from data state. Check structure of your response and set it accordingly.
Can you try adding the following :
render() {
return (
<div>
{this.state.loading
? <div><h1>LOADING...</h1></div>
:
<h2> Company Name: {this.state.customColumns.companyName} <br/>
Revision Name: {this.state.data.name} <br/>
Revision ID: {this.state.data.id} </h2>
}
{this.state.data.length > 0 && this.displayList()}
</div>
)
}
You can probably try this.
render() {
if (this.state.loading) return <div><h1>LOADING...</h1></div>;
return (
<div>
{this.state.data && this.state.data.length &&
<div>
<h2> Company Name: {this.state.customColumns.companyName} <br />
Revision Name: {this.state.data.name} <br />
Revision ID: {this.state.data.id} </h2>
}
{this.displayList()}
</div>
</div>
)
}
}
You can return immediately if loading is true.
Below that you can check for data which is array of objects.
I have a fade-in animation I want to use for a particular parent component. The problem is, the animation only occurs the first time the component is rendered. When I change the state by clicking a button, the animation does not reset and get triggered again.
This is in a react-redux application. I haven't tried adding an action and reducer and then utilizing componentDidMount(). Any help would be greatly appreciated.
Here's the component that renders the data:
export class Doctors extends React.Component {
componentDidMount() {
this.props.dispatch(fetchProfileInfo(this.props.user.id));
}
render() {
if (this.props.profile.doctors) {
const cards = this.props.profile.doctors.map((d, i) => {
return {
previous: i === 0 ? null : i - 1,
next: i === this.props.profile.doctors.length - 1 ? null : i + 1,
doctor: d
};
});
const d = cards[this.props.currentDoctor].doctor;
console.log('this.props.profile.doctors', this.props.profile.doctors);
return (
<div className="container">
<NavigationBar />
<main role="main">
<h1 className="doctors-h1">Doctors</h1>
<div className="doctor-display-section">
<Doctor doctor={d} />
</div>
<div className="doctor-button-holder">
<button
className={this.props.currentDoctor !== 0 ? 'display-doctor-button-1' : 'hidden-1'}
onClick={() => this.props.dispatch(updateCurrentDoctor(cards[this.props.currentDoctor].previous))}
>
<p className="fas fa-long-arrow-alt-left"></p>
<p className="display-profile-section-button-p">
{cards[this.props.currentDoctor].previous === null ? '' : cards[this.props.currentDoctor - 1].doctor.name.firstName +
' ' + cards[this.props.currentDoctor - 1].doctor.name.lastName}
</p>
</button>
<button
className={this.props.currentDoctor !== cards.length - 1 ? 'display-doctor-button-2' : 'hidden-1'}
onClick={() => this.props.dispatch(updateCurrentDoctor(cards[this.props.currentDoctor].next))}
>
<p className="fas fa-long-arrow-alt-right"></p>
<p className="display-profile-section-button-p">
{cards[this.props.currentDoctor].next === null ? '' : cards[this.props.currentDoctor + 1].doctor.name.firstName +
' ' + cards[this.props.currentDoctor + 1].doctor.name.lastName}
</p>
</button>
</div>
</main>
<Footer />
</div>
);
}
return (
<div>
loading...
</div>
);
}
}
const mapStateToProps = state => ({
user: state.auth.currentUser,
profile: state.app.profile,
currentDoctor: state.app.currentDoctor
});
export default requiresLogin()(connect(mapStateToProps)(Doctors));
I have form with dynamic amount of inputs (admin email) however checking for uniqueness fails:
validationSchema={Yup.object().shape({
adminEmails: Yup.array()
.of(
Yup.string()
.notOneOf(Yup.ref('adminEmails'), 'E-mail is already used')
What is best approach here?
FYI, as a form helper I use Formik.
Try this:
Yup.addMethod(Yup.array, 'unique', function(message, mapper = a => a) {
return this.test('unique', message, function(list) {
return list.length === new Set(list.map(mapper)).size;
});
});
Then use it like this:
const headersSchema = Yup.object().shape({
adminEmails: Yup.array().of(
Yup.string()
)
.unique('email must be unique')
})
If you want to have the errors in each field and not in the array
Yup.addMethod(Yup.mixed, 'uniqueIn', function (array = [], message) {
return this.test('uniqueIn', message, function (value) {
return array.filter(item => item === value).length < 2;
});
});
This is a simple inline solution to validate that an array of strings only contains unique elements:
Yup.array().of(Yup.string())
.test(
'unique',
'Only unique values allowed.',
(value) => value ? value.length === new Set(value)?.size : true
)
Simply Do This It works for me
First Define this function in your react component
Yup.addMethod(Yup.array, "unique", function (message, mapper = (a) => a) {
return this.test("unique", message, function (list) {
return list.length === new Set(list.map(mapper)).size
})
})
Just Put this schema inside your Formik tag
<Formik
initialValues={{
hotelName: "",
hotelEmail: [""],
}}
validationSchema={Yup.object().shape({
hotelName: Yup.string().required("Please enter hotel name"),
hotelEmail: Yup.array()
.of(
Yup.object().shape({
email: Yup.string()
.email("Invalid email")
.required("Please enter email"),
}),
)
.unique("duplicate email", (a) => a.email),
})}
onSubmit={(values, { validate }) => {
getFormPostData(values)
}}
render={({ values, errors, touched }) => (
<Form>
<FieldArray
name="hotelEmail"
render={(arrayHelpers) => (
<>
{values.hotelEmail.map((hotel, index) => (
<div class="row" key={index}>
<div className="col-md-8 mt-3">
<div className="user-title-info user-details">
<div className="form-group d-flex align-items-center mb-md-4 mb-3">
<label className="mb-0" htmlFor="hotelName">
{lang("Hotelmanagement.hotelsystemadmin")}
<sup className="text-danger">*</sup>
</label>
<div className="w-100">
<Field
name={`hotelEmail.${index}.email`}
className="form-control"
id="hotelEmail"
placeholder={lang(
"Hotelmanagement.hotelsystemadmin",
)}
/>
<span className="text-danger d-block">
{errors &&
errors.hotelEmail &&
errors.hotelEmail[index] &&
errors.hotelEmail[index].email && (
<span className="text-danger d-block">
{errors.hotelEmail[index].email}
</span>
)}
{errors &&
errors.hotelEmail &&(
<span className="text-danger d-block">
{errors.hotelEmail}
</span>
)}
</span>
</div>
</div>
</div>
</div>
<div className="col-md-2 mt-3">
{index > 0 && (
<i
className="bx bx-minus addnewBtn "
onClick={() => arrayHelpers.remove(index)}
/>
)}
{index === values.hotelEmail.length - 1 && (
<i
className="bx bx-plus addnewBtn ml-5"
onClick={() => arrayHelpers.push("")}
/>
)}
</div>
</div>
))}
</>
)}
/>
not to show error do this following
{errors &&
errors.hotelEmail &&(
<span className="text-danger d-block">
{errors.hotelEmail}
</span>
)}
)}
/>
To improve the performance of Alex's answer, use something like this to pre-calculate the lookup (using lodash).
yup.addMethod(yup.mixed, 'uniqueIn', function (array = [], message) {
return this.test('uniqueIn', message, function (value) {
const cacheKey = 'groups';
if (!this.options.context[cacheKey]) {
this.options.context[cacheKey] = _.groupBy(array, x => x);
}
const groups = this.options.context[cacheKey];
return _.size(groups[value]) < 2;
});
});
then call validate with an object for context so we can store our calculated group for the duration of the validate call
schema.validate(data, {context: {}}).then(...);
Probably too late to respond, but anyways, you should use this.createError({path, message});
See example:
yup.addMethod(yup.array, 'growing', function(message) {
return this.test('growing', message, function(values) {
const len = values.length;
for (let i = 0; i < len; i++) {
if (i === 0) continue;
if (values[i - 1].intervalTime > values[i].intervalTime) return this.createError({
path: `intervals[${i}].intervalTime`,
message: 'Should be greater than previous interval',
});
}
return true;
});
});