Set data from backend to text editor , then change those Data - javascript

import draftToHtml from "draftjs-to-html";
import { Editor } from "react-draft-wysiwyg";
import { EditorState, convertFromRaw, convertToRaw } from "draft-js";
const Sampleform=()=>{
const { ContextData } = useContext(SomeContext);
const [data, setData] = useState(EditorState.createEmpty());
const[loading,setLoading]=useState(false);
const [details, setDetails] = useState({
body:data,
date: null,
dataNew: true,
});
useEffect(() => {
setLoading(true);
(async () => {
if (details) {
setDetails(
details
)
}else{
try{
setDetails({...details,
body: //body deatils whick comes from backend
});
setDetails(details);
}catch(error){
console.log(error);
}
}
})
}, [])
useEffect(() => {
setLoading(true);
if (details && details.dataNew) {
try {
getNewDetails();
} catch (error) {
console.log(error);
}
}
}, [ContextData]);
const getNewDetails =()=>{
//func working
}
return(
<Formik
enableReinitialize={true}
initialValues={letterBody}
validationSchema={validationSchema}
onSubmit={(letterBody, { setSubmitting }) => {
details.body = draftToHtml(letter.getCurrentContent());
}}
>
{({
values,
handleBlur,
handleChange,
setFieldValue,
handleSubmit,
isSubmitting,
errors,
touched,
}) => (
<Form>
{/* data input here its works fine */}
<div>
<Editor
editorState={data}
wrapperClassName="demo-wrapper"
editorClassName="demo-editor"
onEditorStateChange={(data) => setData(data)}
value={data}
onblur={handleBlur}
toolbar={{
options: ["inline", "list", "link", "remove"],
inline: {
options: ["bold", "italic", "underline"],
bold: { className: "bordered-option-classname" },
italic: { className: "bordered-option-classname" },
underline: { className: "bordered-option-classname" },
},
}}
spellCheck={true}
/>
</div>
</Form>
)}
</Formik>
);
};
export default Sampleform;
**want to sent data to my text editor which comes from backend API, then change those data from a text editor, in here date component work well but text editor didn't make any sense, How to fix this Iam using "react-draft-WYSIWYG" text editor with formik **
I have provided a code sample above what is the best way to do the above task, with react useEffect and "react-draft-WYSIWYG" with formik , text editor value does not change my state

Related

Antd Design EditableRow not changing buttons from "edit" to "save" and "cancel"

Im using Antd library and i can't seem to find where i have the bug.
This is my EditableTableCell component
import React, {Component} from 'react';
import { Form } from '#ant-design/compatible';
import '#ant-design/compatible/assets/index.css';
import { Input, InputNumber, Select, DatePicker } from "antd";
import moment from "moment";
import {EditableContext} from "./EditableTableRow";
const FormItem = Form.Item;
const Option = Select.Option;
class EditableTableCell extends Component {
getInput = (record, dataIndex, title, getFieldDecorator) => {
switch (this.props.inputType) {
case "number":
return (
<FormItem style={{ margin: 0 }}>
{getFieldDecorator(dataIndex, {
rules: [
{
required: true,
message: `Please Input ${title}!`
}
],
initialValue: record[dataIndex]
})(
<InputNumber formatter={value => value} parser={value => value} />
)}
</FormItem>
);
case "date":
return (
<FormItem style={{ margin: 0 }}>
{getFieldDecorator(dataIndex, {
initialValue: moment(record[dataIndex], this.dateFormat)
})(<DatePicker format={this.dateFormat} />)}
</FormItem>
);
case "select":
return (
<FormItem style={{ margin: 0 }}>
{getFieldDecorator(dataIndex, {
initialValue: record[dataIndex]
})(
<Select style={{ width: 150 }}>
{[...Array(11).keys()]
.filter(x => x > 0)
.map(c => `Product ${c}`)
.map((p, index) => (
<Option value={p} key={index}>
{p}
</Option>
))}
</Select>
)}
</FormItem>
);
default:
return (
<FormItem style={{ margin: 0 }}>
{getFieldDecorator(dataIndex, {
rules: [
{
required: true,
message: `Please Input ${title}!`
}
],
initialValue: record[dataIndex]
})(<Input />)}
</FormItem>
);
}
}
render() {
const { editing, dataIndex, title, inputType, record, index,...restProps} = this.props;
return (
<EditableContext.Consumer>
{form => {
const { getFieldDecorator } = form;
return (
<td {...restProps}>
{editing ?
this.getInput(record, dataIndex, title, getFieldDecorator)
: restProps.children}
</td>
);
}}
</EditableContext.Consumer>
);
}
}
export default EditableTableCell;
This is my EditableTableCell component
import React, {Component} from 'react';
import { Form} from '#ant-design/compatible';
export const EditableContext = React.createContext();
class EditableTableRow extends Component {
render() {
return (
<EditableContext.Provider value={this.props.form}>
<tr {...this.props} />
</EditableContext.Provider>
);
}
}
export default EditableTableRow=Form.create()(EditableTableRow);
This is my ProductsPage component im having bug in
import React, {Component} from 'react';
import {Button, Layout, notification, Popconfirm, Space, Table,Typography} from "antd";
import {Link} from "react-router-dom";
import {Content} from "antd/es/layout/layout";
import EditableTableRow, {EditableContext} from "../components/EditableTableRow";
import EditableTableCell from "../components/EditableTableCell";
import API from "../server-apis/api";
import {employeesDataColumns} from "../tableColumnsData/employeesDataColumns";
import {CheckCircleFilled, InfoCircleFilled} from "#ant-design/icons";
class ProductsPage extends Component {
constructor(props) {
super(props);
this.state = {
data: [],
error: null,
isLoaded: false,
editingKey: "",
errorMessage: "",
}
}
columns = [
...employeesDataColumns,
{
title: "Actions",
dataIndex: "actions",
width: "10%",
render: (text, record) => {
const editable = this.isEditing(record);
return editable ? (
<span>
<EditableContext.Consumer>
{form => (<a onClick={() => this.saveData(form, record.username)} style={{ marginRight: 8 }}>Save</a>)}
</EditableContext.Consumer>
<a onClick={this.cancel}>Cancel</a>
</span>
) : (
<Space size="middle">
<a onClick={() => this.edit(record.username)}>Edit</a>
<Popconfirm title="Are you sure you want to delete this product?"
onConfirm={() => this.remove(record.username)}>
<a style={{color:"red"}}>Delete</a>
</Popconfirm>
</Space>
);
},
}
];
isEditing = (record) => {
return record.username === this.state.editingKey;
};
edit(username) {
this.setState({editingKey:username});
}
cancel = () => {
this.setState({ editingKey: ""});
};
componentDidMount() {
this.setState({ loading: true });
const token="Bearer "+ JSON.parse(localStorage.getItem("token"));
API.get(`users/all`,{ headers: { Authorization: token}})
.then(res => {
// console.log(res.data._embedded.productList);
const employees = res.data._embedded.employeeInfoDtoList;
this.setState({loading: false,data:employees });
})
}
async remove(username) {
const token="Bearer "+ JSON.parse(localStorage.getItem("token"));
API.delete(`/users/${username}`,{ headers: { Authorization: token}})
.then(() => {
let updatedProducts = [...this.state.data].filter(i => i.username !== username);
this.setState({data: updatedProducts});
this.successfullyAdded("Employee is deleted. It wont have any access to the website anymore.")
}).catch(()=>this.errorHappend("Failed to delete"));
}
hasWhiteSpace(s) {
return /\s/g.test(s);
}
saveData(form,username) {
form.validateFields((error, row) => {
if (error) {
return;
}
const newData = [...this.state.data];
const index = newData.findIndex(item => username === item.username);
const item = newData[index];
newData.splice(index, 1, {
...item,
...row
});
const token="Bearer "+ JSON.parse(localStorage.getItem("token"));
const response = API.put(`/users/${username}/update`, row,{ headers: { Authorization: token}})
.then((response) => {
this.setState({ data: newData, editingKey: ""});
this.successfullyAdded("Empolyee info is updated")
})
.catch(error => {
this.setState({ errorMessage: error.message });
this.errorHappend("Failed to save changes.")
console.error('There was an error!', error);
});
});
}
successfullyAdded = (message) => {
notification.info({
message: `Notification`,
description:message,
placement:"bottomRight",
icon: <CheckCircleFilled style={{ color: '#0AC035' }} />
});
};
errorHappend = (error) => {
notification.info({
message: `Notification`,
description:
`There was an error! ${error}`,
placement:"bottomRight",
icon: <InfoCircleFilled style={{ color: '#f53333' }} />
});
};
render() {
const components = {
body: {
row: EditableTableRow,
cell: EditableTableCell
}
};
const columns = this.columns.map(col => {
if (!col.editable) {
return col;
}
return {
...col,
onCell: record => {
const checkInput = index => {
switch (index) {
case "price":
return "number";
default:
return "text";
}
};
return {
record,
// inputType: col.dataIndex === "age" ? "number" : "text",
inputType: checkInput(col.dataIndex),
dataIndex: col.dataIndex,
title: col.title,
editing: this.isEditing(record)
};
}
};
});
const { data, loading } = this.state;
return (
<Layout>
<div>
<Link to="/add-product">
<Button style={{float:"right", background: "#0AC035",marginBottom:"1em", marginTop:"1em" }}
type="primary">New emplyee</Button>
</Link>
</div>
<Content>
<Table components={components} bordered dataSource={data} columns={columns} loading={loading} rowKey={data.username} rowClassName="editable-row"/>
</Content>
</Layout>
);
}
}
export default ProductsPage;
This is the bug I'm having:
enter image description here
And i want to have this result like its shown in Antd docs:
enter image description here
Id really appreciate if you take a look and help me figure out where im wrong
Updated Solution:
I find the issue. In render where you map the columns, you just return the column if it's not an editable column. You can check the code below. I added a check if it's dataIndex === 'actions', then return the following code:
Please Follow the link:
https://react-ts-v3fbst.stackblitz.io
Changes:
1.In columns, i remove the render function from the action object:
{
title: 'Actions',
dataIndex: 'actions',
width: '10%',
},
2. In render function where you map the columns, add the following code before this condition if(!col.editable) {,:
if (col.dataIndex === 'actions') {
return {
...col,
render: (text, record) => {
const editable = this.isEditing(record);
return editable ? (
<span>
<EditableContext.Consumer>
{(form) => (
<a onClick={() => this.saveData(form, record.username)} style={{ marginRight: 8 }}>
Save
</a>
)}
</EditableContext.Consumer>
<a onClick={this.cancel}>Cancel</a>
</span>
) : (
<Space size='middle'>
<a onClick={() => this.edit(record.username)}>Edit</a>
<Popconfirm title='Are you sure you want to delete this product?' onConfirm={() => this.remove(record.username)}>
<a style={{ color: 'red' }}>Delete</a>
</Popconfirm>
</Space>
);
}
};
}
When you click on edit, you set the username as key for that particular row for editing, make sure you have username in each record. I tested this using the following data:
const data = [
{ id: 8, name: 'baun', model: '2022', color: 'black', price: 358, quantity: 3, username: 'brvim' },
{ id: 3, name: 'galileo', model: '20221', color: 'white', price: 427, quantity: 7, username: 'john' }
];
Most important, you should select that attribute as key that is unique in all records. As you are using username, i don't know what is your business logic or data looks like, but technically each record can have same username. So you must select something that would always be unique in your complete data.

React hooks form, setting default values from a reduced array doesn't populate, but manually enterring same object does

I am using react hooks forms, and I am trying to set the default values of a form that is outputted by mapping over an array and outputting the inputs in the form. I have reduced the array to an object like this {name0:"fijs",name1:"3838"...} and if I manually pass that in the default values it maps to my inputs and populates them. However if I enter them from the variable that is doing the reduce function it doesn't populate it. I think it is because on first render it is undefined. I have tried using a useEffect, but that didn't work so I am stuck.
This is the part of the code I am working on
const test = formState?.reduce((obj, item, idx) => {
return { ...obj, [`${item.name}${idx}`]: "fdsjfs" };
}, {});
const { register, handleSubmit, errors } = useForm({
defaultValues: test,
});
console.log(test);
and this is the whole thing
import { useQuery, gql, useMutation } from "#apollo/client";
import { useEffect, useState } from "react";
import { v4 as uuidv4 } from "uuid";
import { useForm } from "react-hook-form";
const INPUT_VALUES = gql`
query GetInputValues {
allFormInputVals {
data {
name
_id
type
}
}
}
`;
const ADD_INPUT_VALUES = gql`
mutation AddInputValues(
$name: String!
$type: String!
$index: Int!
$ID: ID!
) {
createFormInputVal(
data: {
name: $name
type: $type
index: $index
formRoot: { connect: $ID }
}
) {
name
}
}
`;
const Home = () => {
const blankFormInput = {
__typename: "FormInputVal",
name: "test",
_id: uuidv4(),
type: "text",
};
const [formState, setFormState] = useState([blankFormInput]);
const [formStateVals, setFormStateVals] = useState(undefined);
const { loading, error, data } = useQuery(INPUT_VALUES);
const [createFormInputVal, { data: createInputData }] = useMutation(
ADD_INPUT_VALUES
);
useEffect(() => {
setFormState(data?.allFormInputVals?.data);
}, [data]);
const test = formState?.reduce((obj, item, idx) => {
return { ...obj, [`${item.name}${idx}`]: "fdsjfs" };
}, {});
const { register, handleSubmit, errors } = useForm({
defaultValues: test,
});
console.log(test);
const onSubmit = (data) => console.log(data);
console.log(errors);
const addInput = async () => {
const blanktext = {
__typename: "FormInputVal",
name: "Product Image",
_id: uuidv4(),
type: "text",
};
setFormState([...formState, { ...blanktext }]);
console.log(formState);
const res = await createFormInputVal({
variables: {
name: "test",
type: "text",
index: 0,
ID: "291541554941657608",
},
}).catch(console.error);
console.log(res);
};
if (loading) return <p>Loading...</p>;
if (error) return <p>Error: {error.message}</p>;
return (
<>
<form onSubmit={handleSubmit(onSubmit)}>
<input type="button" value="Add Form Input" onClick={addInput} />
{formState?.map((val, idx) => {
const nameId = `name${idx}`;
const typeId = `type-${idx}`;
return (
<div key={val._id}>
{val.type === "text" && (
<>
<label htmlFor={nameId}>{`${val.name} #${idx + 1}`}</label>
<input
type="text"
name={nameId}
id={nameId}
className={val.type}
ref={register()}
/>
{/* <label htmlFor={typeId}>{`Type #${idx + 1}`}</label>
<select name={typeId} id={typeId} className={val.type}>
{data.allFormInputVals.data.map((item) => {
return (
<option key={item._id} value={item.type}>
{item.type}
</option>
);
})}
</select> */}
</>
)}
</div>
);
})}
<button type="submit">Save Form</button>
</form>
</>
);
};
export default Home;
UPDATE: I have tried useEffect with a reset from the api, I thought this was the solution, but still no dice.
const { register, handleSubmit, errors, reset } = useForm();
useEffect(() => {
const result = test; // result: { firstName: 'test', lastName: 'test2' }
reset(result); // asynchronously reset your form values
}, [reset]);
UPDATE: I abstracted the Form to it;s own component, but it still does not work.
Form.js
import { useEffect, useState } from "react";
import { useForm } from "react-hook-form";
import { useQuery, gql, useMutation } from "#apollo/client";
import { v4 as uuidv4 } from "uuid";
const ADD_INPUT_VALUES = gql`
mutation AddInputValues(
$name: String!
$type: String!
$index: Int!
$ID: ID!
) {
createFormInputVal(
data: {
name: $name
type: $type
index: $index
formRoot: { connect: $ID }
}
) {
name
}
}
`;
export default function Form({ formState, setFormState }) {
const test = formState?.reduce((obj, item, idx) => {
return { ...obj, [`${item.name}${idx}`]: "fdsjfs" };
}, {});
console.log(test);
const { register, handleSubmit, errors } = useForm({ defaultValues: test });
const [formStateVals, setFormStateVals] = useState(undefined);
// console.log(test);
const onSubmit = (data) => console.log(data);
console.log(errors);
const addInput = async () => {
const blanktext = {
__typename: "FormInputVal",
name: "Product Image",
_id: uuidv4(),
type: "text",
};
setFormState([...formState, { ...blanktext }]);
console.log(formState);
const res = await createFormInputVal({
variables: {
name: "test",
type: "text",
index: 0,
ID: "291541554941657608",
},
}).catch(console.error);
console.log(res);
};
const [createFormInputVal, { data: createInputData }] = useMutation(
ADD_INPUT_VALUES
);
return (
<form onSubmit={handleSubmit(onSubmit)}>
<input type="button" value="Add Form Input" onClick={addInput} />
{formState?.map((val, idx) => {
const nameId = `name${idx}`;
const typeId = `type-${idx}`;
return (
<div key={val._id}>
{val.type === "text" && (
<>
<label htmlFor={nameId}>{`${val.name} #${idx + 1}`}</label>
<input
type="text"
name={nameId}
id={nameId}
className={val.type}
ref={register()}
/>
{/* <label htmlFor={typeId}>{`Type #${idx + 1}`}</label>
<select name={typeId} id={typeId} className={val.type}>
{data.allFormInputVals.data.map((item) => {
return (
<option key={item._id} value={item.type}>
{item.type}
</option>
);
})}
</select> */}
</>
)}
</div>
);
})}
<button type="submit">Save Form</button>
</form>
);
}
index.js
import { useQuery, gql, useMutation } from "#apollo/client";
import { useEffect, useState } from "react";
import { v4 as uuidv4 } from "uuid";
import Form from "../components/Form";
const INPUT_VALUES = gql`
query GetInputValues {
allFormInputVals {
data {
name
_id
type
}
}
}
`;
const Home = () => {
const blankFormInput = {
__typename: "FormInputVal",
name: "test",
_id: uuidv4(),
type: "text",
};
const [formState, setFormState] = useState([blankFormInput]);
const { loading, error, data } = useQuery(INPUT_VALUES);
useEffect(() => {
const formData = data?.allFormInputVals?.data;
setFormState(formData);
}, [data]);
if (loading) return <p>Loading...</p>;
if (error) return <p>Error: {error.message}</p>;
return (
<>
<Form formState={formState} setFormState={setFormState} />
</>
);
};
export default Home;
You could extract the form to its own component and only render it when the data is fetched. This way, when you use useForm in the child component, the default values will be set properly.
const Home = () => {
const { loading, error, data } = useQuery(INPUT_VALUES)
const blankFormInput = {
__typename: "FormInputVal",
name: "test",
_id: uuidv4(),
type: "text",
}
const [formState, setFormState] = useState([blankFormInput])
// other code
if (loading) {
return <p>Loading...</p>
}
return <MyForm defaultValues={formState} />
}
If you don't want to change the structure, you could set the input values using setValue when the data is ready.
useEffect(() => {
const formData = data?.allFormInputVals?.data
setFormState(formData)
formData?.forEach((item, idx) => {
setValue(`${item.name}${idx}`, 'whatever')
})
}, [data])

How to refuse my formik form from re-rendering after submiting to the action

I am faced with an issue where when I submit a form, whether it was successful or not, the whole of the form field resets. so I am assuming that is because the component is rerendering.
I want when I submit a form, whether the network request was successful or not, I want it to still maintain the field contents
below is how the entire code looks like:
import { Formik, FieldArray } from 'formik';
import * as Yup from 'yup';
import React, { useEffect } from 'react';
import * as Style from './create-lesson-form.styles';
import { ReactComponent as AddNewIcon } from '../../assets/add-new.svg';
import { ReactComponent as RemoveRecent } from '../../assets/remove-recent.svg';
import CustomButton from '../custom-button/custom-button.component';
import SimpleCheckBox from '../field-inputs/simple-checkbox/simple-checkbox.component';
import { createALessonNote } from '../../redux/lesson_note/lesson_note.actions';
import { LessonNotePayload } from '../../redux/lesson_note/lesson_note.types';
import { connect } from 'react-redux';
import { CreateLessonPropType } from './create-lesson-form.types';
import ToastAlert from '../toast/toast.components';
import { createStructuredSelector } from 'reselect';
import {
selectLessonCreationIsSuccess,
selectLessonNoteError,
selectLessonNoteSuccess,
} from '../../redux/lesson_note/lesson_note.selector';
import { selectSubjects } from '../../redux/subject/subject.selector';
import { fetchSubjectAction } from '../../redux/subject/subject.action';
import { fetchLevelAction } from '../../redux/level/level.action';
import { selectLevels } from '../../redux/level/level.selectors';
const CreateLessonForm: React.FC<CreateLessonPropType> = ({
createLessonNote,
fetchSubjects,
fetchLevels,
lesson_note_error,
lesson_note_success,
isLessonCreated,
subjects,
levels,
}) => {
const handleAddStepsField = (values: any, setValues: any) => {
const steps = [...values.steps];
steps.push('');
setValues({ ...values, steps });
};
const handleAddParagraphField = (values: any, setValues: any) => {
const paragraphs = [...values.paragraphs];
paragraphs.push('');
setValues({ ...values, paragraphs });
};
const handleDeleteParagraphFields = (values: any, setValues: any) => {
const paragraphs = [...values.paragraphs];
paragraphs.pop();
setValues({ ...values, paragraphs });
};
const handleDeleteStepsFields = (values: any, setValues: any) => {
const steps = [...values.steps];
steps.pop();
setValues({ ...values, steps });
};
const fetchFormValues = async (values: any) => {
const lessonPayload: LessonNotePayload = {
class_id: values.class,
subject_id: values.subject,
topic: values.topic,
is_published: values.is_published,
lesson_body: values.paragraphs,
lesson_plan: values.steps,
};
await createLessonNote(lessonPayload);
};
useEffect(() => {
fetchSubjects();
fetchLevels();
}, []);
return (
<Style.CreateLessonNoteContainer>
{lesson_note_error.length ? <ToastAlert message={lesson_note_error} type="failure" /> : null}
{isLessonCreated ? <ToastAlert message={lesson_note_success} type="success" /> : null}
<Formik
/**
*Declare field initial values
*/
initialValues={{
topic: '',
subject: '',
class: '',
is_published: false,
date_of_delivery: '',
paragraphs: [],
steps: [],
}}
/**
* validate form on client side
*/
validationSchema={Yup.object({
topic: Yup.string().required(),
subject: Yup.string().required(),
class: Yup.number().required(),
date_of_delivery: Yup.date().required(),
paragraphs: Yup.array().of(Yup.string().required()),
steps: Yup.array().of(Yup.string().required()),
})}
/**
* listen to user submit action
*/
onSubmit={async (values, { setSubmitting, resetForm }) => {
alert(values);
console.log(values);
await fetchFormValues(values);
if (isLessonCreated) {
resetForm();
console.log('reset form');
}
setSubmitting(false);
}}
>
{({ values, setValues }) => (
<>
<Style.FormTag>
<Style.LessonBodyFooter>
<Style.LessonHeadInput>
<Style.LessonParagraphHeader>
<h3>Lesson Header</h3>
</Style.LessonParagraphHeader>
<Style.CustomTextInput label="Topic" name="topic" type="text" />
<Style.CustomSelectDropdown label="Subject" name="subject">
<option value={''} disabled selected>
select subject
</option>
{subjects.map((subject) => {
return (
<option key={subject.id} value={subject.id}>
{subject.title}
</option>
);
})}
</Style.CustomSelectDropdown>
<Style.CustomSelectDropdown label="Level" name="class">
<option value={''} disabled selected>
select level
</option>
{levels.map((level) => {
return (
<option key={level.id} value={level.id}>
{level.title}
</option>
);
})}
</Style.CustomSelectDropdown>
<Style.CustomTextInput
label="Date of delivery"
name="date_of_delivery"
type="date"
/>
</Style.LessonHeadInput>
<Style.LessonParagraphContainer>
<Style.LessonParagraphHeader>
<h3>Lesson Paragraphs</h3>
<div>
<RemoveRecent
onClick={() => handleDeleteParagraphFields(values, setValues)}
/>
<AddNewIcon onClick={() => handleAddParagraphField(values, setValues)} />
</div>
</Style.LessonParagraphHeader>
<Style.LessonParagraphFieldContainer>
<FieldArray name={'paragraphs'}>
{() =>
values.paragraphs.map((_, index) => {
return (
<Style.LessonParagraphFieldDiv key={index}>
<Style.CustomTextArea
label={`Paragraph ${index + 1}`}
name={`paragraphs.${index}`}
/>
</Style.LessonParagraphFieldDiv>
);
})
}
</FieldArray>
</Style.LessonParagraphFieldContainer>
</Style.LessonParagraphContainer>
<Style.LessonStepContainer>
<Style.LessonParagraphHeader>
<h3>Lesson Steps</h3>
<div>
<RemoveRecent onClick={() => handleDeleteStepsFields(values, setValues)} />
<AddNewIcon onClick={() => handleAddStepsField(values, setValues)} />
</div>
</Style.LessonParagraphHeader>
<Style.LessonParagraphFieldContainer>
<FieldArray name={'steps'}>
{() =>
values.steps.map((_, index) => {
return (
<Style.LessonParagraphFieldDiv key={index}>
<Style.CustomTextArea
label={`Step ${index + 1}`}
name={`steps.${index}`}
/>
</Style.LessonParagraphFieldDiv>
);
})
}
</FieldArray>
</Style.LessonParagraphFieldContainer>
</Style.LessonStepContainer>
</Style.LessonBodyFooter>
<Style.LessonFormFooter>
<SimpleCheckBox name={'is_published'}>Publish Note</SimpleCheckBox>
<CustomButton type="submit">Submit</CustomButton>
</Style.LessonFormFooter>
</Style.FormTag>
</>
)}
</Formik>
</Style.CreateLessonNoteContainer>
);
};
const mapStateToProps = createStructuredSelector({
lesson_note_error: selectLessonNoteError,
isLessonCreated: selectLessonCreationIsSuccess,
lesson_note_success: selectLessonNoteSuccess,
subjects: selectSubjects,
levels: selectLevels,
});
const mapDispatchToProps = (dispatch: any) => {
return {
createLessonNote: ({
class_id,
subject_id,
topic,
is_published,
lesson_body,
lesson_plan,
}: LessonNotePayload) =>
dispatch(
createALessonNote({
class_id,
subject_id,
topic,
is_published,
lesson_body,
lesson_plan,
}),
),
fetchSubjects: () => dispatch(fetchSubjectAction()),
fetchLevels: () => dispatch(fetchLevelAction()),
};
};
export default connect(mapStateToProps, mapDispatchToProps)(CreateLessonForm);

Retrieving state data from React

I have an input form and onSubmit, the input value will be rendered into a Checkbox. The data is being stored with MongoDB - I can see the input data I've typed using Robo 3T, so I know that part is working. However, the array is empty in the console and not being added to the Checkbox.
export const QUERY_ALL_CHORES = gql`
query chores {
chores {
_id
choreBody
}
}
`;
resolvers.js
addChore: async (parent, args, context) => {
// return console.log("chores: ", args.choreBody);
if (context.user) {
const chore = await Chore.create({
...args,
choreBody: args.choreBody,
});
await User.findByIdAndUpdate(
{ _id: context.user._id },
{ $push: { chores: chore._id } },
{ new: true }
);
return chore;
}
},
Then here is my AddChoreForm.js
export default function AddChore(props) {
const [choreBody, setBody] = useState("");
const [addChore] = useMutation(ADD_CHORE, {
update(cache, { data: { addChore } }) {
try {
const { chores } = cache.readQuery({ query: QUERY_ALL_CHORES });
cache.writeQuery({
query: QUERY_ALL_CHORES,
data: { chores: [addChore, ...chores] },
});
} catch (e) {
console.log(e);
}
// append new chore to the end of the array
const { me } = cache.readQuery({ query: QUERY_ME });
cache.writeQuery({
query: QUERY_ME,
data: { me: { ...me, chores: [...me.chores, addChore] } },
});
},
});
const handleFormSubmit = async (event) => {
event.preventDefault();
try {
// add chore to database
await addChore({
variables: { choreBody },
});
// clear form value
setBody("");
} catch (e) {
console.log(e);
}
};
return (
<Container>
<Form onSubmit={handleFormSubmit}>
<Form.TextArea
onChange={(event) => setBody(event.target.value)}
/>
<Button>
Add Chore
</Button>
</Form>
</Container>
);
}
Then the input data should be put into a Checkbox here, but when I check the console, the array is empty.
export default function ChoreList({ chores }) {
// get chore data
const { choreData } = useQuery(QUERY_ALL_CHORES);
const chores = choreData?.chores || [];
console.log("Chores: ", chores);
return (
<>
<Container chores={chores} >
{chores &&
chores.map((chore) => (
<div key={chore._id}>
<Form>
<Form.Field>
<List>
<List.Item>
<List.Content>
{/* {chore.choreBody} */}
<Checkbox label={chore.choreBody} />
</List.Content>
</List.Item>
</List>
</Form.Field>
</Form>
</div>
))}
</Container>
</>
);
}
Try passing the fetchPolicy: 'network-only' option with your useQuery hook to force fetch from DB rather than apollo cache.
From the docs:
By default your component will try to read from the cache first, and if the full data for your query is in the cache then Apollo simply returns the data from the cache.

State doesn't update properly when making a post request

Hey guy's I have no idea why my state isn't updating in the DOM, I'm sure I'm missing some key react principal. Heres the photo of what my DOM looks like after I submit a post
Instead of the post displaying when I click submit with filled out info, a blank post is shown. And I have to manually reload the page before it shows what was added. I think I'm actually battling some sync issues, please let me know what I can do if you see anything.
I will put the most relevant file's code below and also attache the repository at the bottom if you want to have a complete look.
dataActions.js
import { SET_POSTS, LOADING_DATA, DELETE_POST, POST_PRODUCT, SET_ERRORS, CLEAR_ERRORS, LOADING_UI } from "../types";
import axios from 'axios';
//GET ALL PRODUCTS
export const getPosts = () => dispatch => {
dispatch({ type: LOADING_DATA });
axios.get('/posts')
.then(res => {
dispatch({
type: SET_POSTS,
payload: res.data
})
})
.catch(err => {
dispatch({
type: SET_POSTS,
payload: []
})
})
}
//POST PRODUCT
export const postProduct = (newPost) => (dispatch) => {
dispatch({ type: LOADING_UI });
axios.post('/post', newPost)
.then(res => {
dispatch({
type: POST_PRODUCT,
payload: res.data
})
console.log("success");
dispatch({ type: CLEAR_ERRORS })
})
.catch(err => {
dispatch({
type: SET_ERRORS,
payload: err.response.data
})
})
}
//DELETE PRODUCT
export const deletePost = (postId) => (dispatch) => {
axios.delete(`/post/${postId}`)
.then(() => {
dispatch({ type: DELETE_POST, payload: postId })
})
.catch(err => console.log(err))
}
dataReducer.js
import { SET_POSTS } from '../types';
import { LOADING_DATA, DELETE_POST, POST_PRODUCT/*, SET_POST*/ } from '../types';
const initialState = {
posts: [],
post: {},
loading: false
};
export default function(state = initialState, action){
switch(action.type){
case LOADING_DATA:
return {
...state,
loading: true
}
case SET_POSTS:
return{
...state,
posts: action.payload,
loading: false
}
case DELETE_POST:
let index = state.posts.findIndex(post => post.postId === action.payload);
state.posts.splice(index, 1);
return {
...state
}
case POST_PRODUCT:
return {
...state,
posts: [action.payload, ...state.posts]
}
default:
return state
}
}
PostProduct.js
import React, { Component, Fragment } from "react";
import { withStyles } from "#material-ui/core/styles";
import PropTypes from "prop-types";
import MyButton from "../util/MyButton";
//MUI Stuff
import Button from "#material-ui/core/Button";
import TextField from "#material-ui/core/TextField";
import Dialog from "#material-ui/core/Dialog";
import DialogTitle from "#material-ui/core/DialogTitle";
import DialogContent from "#material-ui/core/DialogContent";
import DeleteOutline from "#material-ui/icons/DeleteOutline";
import CircularProgress from '#material-ui/core/CircularProgress';
import AddIcon from '#material-ui/icons/Add';
import CloseIcon from "#material-ui/icons/Close";
//REDUX
import { connect } from "react-redux";
import { postProduct } from "../redux/actions/dataActions";
const styles = {
form: {
textAlign: "center"
},
image: {
margin: "20px auto 20px auto",
width: "50px"
},
pageTitle: {
margin: "10px auto 10px auto"
},
textField: {
margin: "10px auto 10px auto"
},
button: {
marginTop: 20,
postition: "relative"
},
customError: {
color: "red",
fontSixe: "0.8rem",
marginTop: 10
},
progress: {
position: "absolute"
},
submitButton: {
position: "relative"
},
progressSpinner: {
position: 'absolute'
},
closeButton: {
position: 'absolute',
left: '90%',
top: '10%'
}
};
class PostProduct extends Component {
state = {
open: false,
name: '',
errors: {}
};
UNSAFE_componentWillReceiveProps(nextProps){
if (nextProps.UI.errors) {
this.setState({
errors: nextProps.UI.errors
})
}
}
handleOpen = () => {
this.setState({ open: true })
}
handleClose = () => {
this.setState({ open: false })
}
handleChange = (event) => {
this.setState({ [event.target.name]: event.target.value })
}
handleSubmit = (event) => {
event.preventDefault();
this.props.postProduct({ body: this.state.body })
}
render(){
const { errors } = this.state;
const { classes, UI: {loading }} = this.props;
return (
<Fragment>
<MyButton onClick={this.handleOpen} tip="Post a Product">
<AddIcon />
</MyButton>
<Dialog
open={this.state.open}
onClose={this.handleClose}
fullWidth
maxWidth="sm"
>
<MyButton
tip="close"
onClick={this.handleClose}
tipClassName={classes.closeButton}
>
<CloseIcon />
</MyButton>
<DialogTitle>Post the new Product</DialogTitle>
<DialogContent>
<form onSubmit={this.handleSubmit}>
<TextField
name="name"
type="text"
lable="Post Product"
multiline
rows="3"
placeholder="name"
error={errors.body ? true : false}
helperText={errors.body}
className={classes.textFields}
onChange={this.handleChange}
fullWidth
/>
<TextField
name="images"
type="text"
lable="image"
multiline
rows="3"
placeholder="image"
error={errors.body ? true : false}
helperText={errors.body}
className={classes.textFields}
onChange={this.handleChange}
fullWidth
/>
<TextField
name="itemCategory"
type="text"
lable="Painting"
multiline
rows="3"
placeholder="Painting"
error={errors.body ? true : false}
helperText={errors.body}
className={classes.textFields}
onChange={this.handleChange}
fullWidth
/>
<TextField
name="link"
type="text"
lable="link"
multiline
rows="3"
placeholder="https://etsy.com"
error={errors.body ? true : false}
helperText={errors.body}
className={classes.textFields}
onChange={this.handleChange}
fullWidth
/>
<TextField
name="info"
type="text"
lable="blah blah blah"
multiline
rows="3"
placeholder="info"
error={errors.body ? true : false}
helperText={errors.body}
className={classes.textFields}
onChange={this.handleChange}
fullWidth
/>
<TextField
name="price"
type="text"
lable="Price"
multiline
rows="3"
placeholder="75.99"
error={errors.body ? true : false}
helperText={errors.body}
className={classes.textFields}
onChange={this.handleChange}
fullWidth
/>
<TextField
name="available"
type="text"
lable="available?"
multiline
rows="3"
placeholder="true"
error={errors.body ? true : false}
helperText={errors.body}
className={classes.textFields}
onChange={this.handleChange}
fullWidth
/>
<TextField
name="highEnd"
type="text"
lable="High-end or not?"
multiline
rows="3"
placeholder="false"
error={errors.body ? true : false}
helperText={errors.body}
className={classes.textFields}
onChange={this.handleChange}
fullWidth
/>
<Button
type="submit"
variant="contained"
color="primary"
className={classes.submitButton}
disabled={loading}
>
Submit
{loading && (
<CircularProgress
size={30}
className={classes.progressSpinner}
/>
)}
</Button>
</form>
</DialogContent>
</Dialog>
</Fragment>
);
}
} // END CLASS
PostProduct.propTypes = {
postProduct: PropTypes.func.isRequired,
UI: PropTypes.object.isRequired
}
const mapStateToProps = (state) => ({
UI: state.UI
})
export default connect(mapStateToProps, { postProduct })(withStyles(styles)(PostProduct))
The front end code is in this repository here: https://github.com/jIrwinCline/planum-front
Thanks for any help. I know this is a big question...
Post product thunk sets LOADING_UI and then POST_PRODUCT if successful.
export const postProduct = (newPost) => (dispatch) => {
dispatch({ type: LOADING_UI });
axios.post('/post', newPost)
.then(res => {
dispatch({
type: POST_PRODUCT,
payload: res.data
})
console.log("success");
dispatch({ type: CLEAR_ERRORS })
})
.catch(err => {
dispatch({
type: SET_ERRORS,
payload: err.response.data
})
})
In your reducer, there is no LOADING_UI case and thePOST_PRODUCT case just sets the post data but doesn't turn loading off:
case POST_PRODUCT:
return {
...state,
posts: [action.payload, ...state.posts]
}
I suspect you have to add a LOADING_UI case to your reducer and ensure that POST_PRODUCT sets loading to false when it updates your store with the new posts.

Categories