I’m using react-bootstrap
and formik
.
The below UpdateWorkloadForm
component is rendered in a Modal’s body.
import React, { useImperativeHandle, useLayoutEffect, useReducer, forwardRef } from 'react';
import PropTypes from 'prop-types';
import { Form, Row, Col } from "react-bootstrap";
import "react-datetime/css/react-datetime.css";
import { useFormik} from 'formik';
import * as yup from "yup";
import Datetime from 'react-datetime';
import moment from 'moment';
import DivSpacing from '../common/divSpacing';
import { reducer } from '../common/reducer'
import { TASKSTATUSOPTIONS } from '../data/constants';
// Initial states of UpdateWorkloadForm
const initialState = {
workflowTasks: null,
};
const UpdateWorkloadForm = forwardRef((props, ref) => {
// State Handling
const [state, dispatch] = useReducer(reducer, initialState);
/***
* Rest of the code
*/
// "workflowTask" & "workflowTaskStatus" option selection
const handleWorkflowTasks = (taskList) => {
dispatch({ type: "workflowTasks", value: taskList});
if(props.workloadDetails.task_progression.task_id){
const statusId = (TASKSTATUSOPTIONS.find(v => { return v.label == props.workloadDetails.task_progression.task_status; })).id;
formik.setFieldValue("workflowTask", props.workloadDetails.task_progression.task_id);
formik.setFieldValue("workflowTaskStatus", statusId);
}else{
formik.setFieldValue("workflowTask", taskList[0].task_id);
formik.setFieldValue("workflowTaskStatus", TASKSTATUSOPTIONS[0].id);
}
}
// Fetch the tasks of `workflowDescription` from DB
const fetchWorkflowTasks = async () => {
return new Promise(function(resolve, reject){
props.api.post({
url: '/fetchWorkflowTasks',
body: {
workflowDescription: props.workloadDetails.Description,
}
}, (res) => {
if(res.tasks){
handleWorkflowTasks(res.tasks);
resolve();
}
}, (err, resp) => {
reject("Oops! Couldn't fetch tasks from DB.");
});
});
}
useLayoutEffect(() => {
if(Object.keys(props.workloadDetails).length !== 0){
fetchWorkflowTasks();
}
},[]);
return (
<>
<Form noValidate onSubmit={formik.handleSubmit}>
<Row>
<Col>
<Col>
<Form.Group controlId="workflowTask">
<Form.Label>Task</Form.Label>
<Form.Control
as="select"
name="workflowTask"
onChange={formik.handleChange}
value={formik.values.workflowTask}
isInvalid={formik.touched.workflowTask && formik.errors.workflowTask}
>
{ state.workflowTasks &&
state.workflowTasks.map((item, index) => (
<option key={item.task_id} value={item.task_id}>
{item.task_name}
</option>
))
}
</Form.Control>
<Form.Control.Feedback type="invalid">
{formik.errors.workflowStartingPoint}
</Form.Control.Feedback>
</Form.Group>
</Col>
</Col>
</Row>
<Row>
<Col>
<Col>
<Form.Group controlId="workflowTaskStatus">
<Form.Label>Task Status</Form.Label>
<Form.Control
as="select"
name="workflowTaskStatus"
onChange={formik.handleChange}
value={formik.values.workflowTaskStatus}
>
{ TASKSTATUSOPTIONS.map((item, index) => (
<option key={item.id} value={item.id}>
{item.label}
</option>
))
}
</Form.Control>
</Form.Group>
</Col>
</Col>
</Row>
</Form>
</>
);
})
UpdateWorkloadForm.propTypes = {
api: PropTypes.object.isRequired,
workloadDetails: PropTypes.object.isRequired,
};
export default UpdateWorkloadForm;
My scenario is: When the modal opens, I make an API call to update the select
fields in the form.
Since this is DOM mutation, I used useLayoutEffect
. But still I could see the flicker when
the component is rendered.
What am I doing wrong?
Thanks
>Solution :
All that useLayoutEffect
does is that if you set state while the effect is still running, then the resulting render will be done synchronously. It is not going to wait for asynchronous things like fetching data to complete. The typical use case for layout effects is that you want to render something, then immediately measure the on-screen size of what you rendered, and then render something else so the user doesn’t see the dom used for the measurement.
For fetching data, useLayoutEffect is not going to help. Instead, you need to design your component so that it looks good if the data has not yet been fetched. For example, you could render a loading indicator, or if you just want to render nothing you could return null
from the component.
Assuming your state has a .loading
property on it, that might look something like this:
if (state.loading) {
return <div>Loading...</div>
// or, return null
} else {
return (
<>
<Form noValidate onSubmit={formik.handleSubmit}>
// ...
</Form>
</>
)
}