i writen a function to convert HTML to editorState. it's work as i want but i can't use it when i assign it into the draft-wysiwyg
first function i use to call to change HTML to EditorState this function will call the other function and create EditorState on it own
function convertToEditor(markup: string): EditorState {
if (markup === '<p></p>') {
return EditorState.createEmpty()
}
const root = markupToObject(markup)
const blockContentArray: Array<ContentBlock> = []
_.forEach(root, (node, i) => {
const contentBlock = new ContentBlock({
key: genKey(),
text: node.textContent,
type: 'unstyled',
characterList: Immutable.List(getCharactorList(node.childNodes)),
data: node.getAttribute('style')?.includes('text-align') ? { 'text-align': node.getAttribute('style').split(':')[1].replace(';', '') } : {},
})
blockContentArray.push(contentBlock)
})
const contentState: ContentState = ContentState.createFromBlockArray(blockContentArray)
const editorState: EditorState = EditorState.createWithContent(contentState)
console.log(editorState)
return editorState
}
markupToObject and getCharactorList is just a function that i write to lower the complexity of the coding
function markupToObject(markup): HTMLCollection {
const div = document.createElement('div')
div.innerHTML = markup.trim()
return div.children
}
function getCharactorList(nodes: NodeListOf<ChildNode>, style?: Array<'BOLD' | 'ITALIC' | 'UNDERLINE'>): Array<CharacterMetadata> {
const characterList: Array<CharacterMetadata> = []
_.forEach(nodes, (node) => {
if (node.nodeName === '#text') {
_.forEach(node.textContent, () => {
characterList.push(CharacterMetadata.create({ style: style, entity: null }))
})
} else if (node.nodeName === 'A') {
_.forEach(node.textContent, () => {
characterList.push(CharacterMetadata.create({ style: [...(style || []), 'BOLD'], entity: null })) /* entity ID? */
})
} else {
const newStyle: Array<'BOLD' | 'ITALIC' | 'UNDERLINE'> = []
if (node.nodeName === 'STRONG') {
newStyle.push('BOLD')
} else if (node.nodeName === 'EM') {
newStyle.push('ITALIC')
} else if (node.nodeName === 'INS') {
newStyle.push('UNDERLINE')
}
characterList.push(...(getCharactorList(node.childNodes, [...newStyle, ...(style || [])]) || []))
}
})
return characterList
}
how i use
<RichText
onChange={(e) => {}}
value={convertToEditor(
'<p>text <strong>bold</strong> <strong><em>bold+italic</em></strong> </p> <p style="text-align:center;">center</p> <p><strong> link </strong></p> <p><strong> #name surname </strong></p>'
)}
/>
this is the error i got after use the Editorstate that convert from HTML
Uncaught TypeError: Cannot read properties of undefined (reading 'toList')
Related
i get data from server by useReducer and do some action on that and showing them on table and for that i use .filter() but it gives me an Error that my data is Undefined
this is my UseReducer :
function savedEventsReducer(state, { type, payload }) {
switch (type) {
case "push":
return [...state, payload];
case "update":
return state.map((evt) =>
evt.id === payload.id ? payload : evt
);
case "delete":
return state.filter((evt) => evt.id !== payload.id);
default:
throw new Error();
}
}
const [SavedEvents, dispatchcallEvent] =
useReducer(savedEventsReducer, [])
useEffect(() => {
axios.get('http://localhost:8000/SavedEvents/').then(resp => {
dispatchcallEvent({ type: 'push', payload: resp.data });
})
}, [])
this is my actions functions that filter my data :
const [Lables, SetLables] = useState([])
const filteredEvents = useMemo(() => {
if(SavedEvents[0]){
console.log(SavedEvents[0]); // it's gives me my Data and not Undefine.
console.log(SavedEvents);
return SavedEvents[0].filter((evt) => // this is the line that mentioned in Error
Lables
.filter((lbl) => lbl.checked)
.map((lbl) => lbl.label)
.includes(evt.label)
);}
}, [SavedEvents, Lables])
useEffect(() => {
SetLables((prevLabels) => {
if(SavedEvents[0]){
return [...new Set(SavedEvents[0].map((evt) => evt.label))].map(
(label) => {
const currentLabel = prevLabels.find(
(lbl) => lbl.label === label
);
return {
label,
checked: currentLabel ? currentLabel.checked : true,
};
}
);
}
});
}, [SavedEvents])
all this Codes are in my Context and i use them and after first render all of them are render
this is My whole Error:
Uncaught (in promise) TypeError: Cannot read properties of undefined (reading 'filter')
at ContextWrapper.js:58:1
at Array.filter (<anonymous>)
at ContextWrapper.js:58:1
at updateMemo (react-dom.development.js:15867:1)
at Object.useMemo (react-dom.development.js:16413:1)
at useMemo (react.development.js:1532:1)
at ContextWrapper (ContextWrapper.js:54:1)
this is SavedEvents[0] :
you should add another condition on top of your SetLables Like :
useEffect(() => {
if(SavedEvents[0]){
SetLables((prevLabels) => {
// console.log(SavedEvents[0]);
return [...new Set(SavedEvents[0].map((evt) => evt.label))].map(
(label) => {
const currentLabel = prevLabels.find(
(lbl) => lbl.label === label
);
return {
label,
checked: currentLabel ? currentLabel.checked : true,
};
}
);
});
}
// console.log(Lables);
}, [SavedEvents])
I have an action in my redux toolkit that's attempting to set some state. Relevant code below:
interfaces
export interface ProposalTag {
id: number;
name: string;
hex: string;
color: string;
}
export interface ProposalSelectedTag {
proposal_id: number;
proposal_tag_id: number;
}
redux
import { ProposalTag, ProposalSelectedTag } from '../../types/proposalTags';
interface ProposalTagsSlice {
proposalTags: ProposalTag[];
selectedProposalTags: ProposalTag[];
}
const initialState: ProposalTagsSlice = {
proposalTags: [],
selectedProposalTags: [],
};
const matchTags = (
selectedTags: ProposalSelectedTag[],
proposalTags: ProposalTag[],
): ProposalTag[] => {
const tags = selectedTags.map((selectedTag: ProposalSelectedTag) => {
return proposalTags.find(proposalTag => proposalTag.id === selectedTag.proposal_tag_id);
});
return tags ?? [];
};
export const proposalTagsSlice = createSlice({
name: 'proposalTags',
initialState,
reducers: {
setSelectedProposalTags: (state, action: PayloadAction<ProposalSelectedTag[]>) => {
if (state.proposalTags === undefined) return;
state.selectedProposalTags =
action.payload === null ? [] : matchTags(action.payload, state.proposalTags);
},
},
});
The goal of matchTags is to convert the payload of ProposalSelectedTag[] to ProposalTag[]. So in theory, ProposalSelectedTag.proposal_tag_id
The type errors I get back are the following:
Did I lose typing somewhere in matchTags?
That's because Array.prototype.find will return undefined if the element is not found.
If you are sure that the item exists in the list, you can make an assertion that will calm TypeScript down.
const tags = selectedTags.map((selectedTag: ProposalSelectedTag) => {
const item = proposalTags.find(proposalTag => proposalTag.id === selectedTag.proposal_tag_id);
if (!item) throw new Error('item was not found')
return item
});
You can also use !
const tags = selectedTags.map((selectedTag: ProposalSelectedTag) => {
return proposalTags.find(proposalTag => proposalTag.id === selectedTag.proposal_tag_id)!;
});
Or you can set a default value
const tags = selectedTags.map((selectedTag: ProposalSelectedTag) => {
return proposalTags.find(proposalTag => proposalTag.id === selectedTag.proposal_tag_id) ?? 10;
});
My React state:
//...
this.state = {
mylist: [
{
"id": 0,
"trueorfalse": false
},
{
"id": 1,
"trueorfalse": false
}
]
}
//...
I am trying to update the trueorfalse value based on the id
Here is what I did so far but didn't work:
var idnum = e.target.id.toString().split("_")[1] //getting the id via an element id (0 or 1 in this case)
var TorF = true
if (type === 1) {
this.setState({
mylist: this.state.mylist.map(el => (el.id === idnum ? Object.assign({}, el, { TorF }) : el))
})
}
I really want to make it dynamic so the trueorfase will be opposite of what it is now:
var idnum = e.target.id.toString().split("_")[1] //getting the id via an element id (0 or 1 in this case)
if (type === 1) {
this.setState({
mylist: this.state.mylist.map(el => (el.id === idnum ? Object.assign({}, el, { /* if already true set to false or vice versa */ }) : el))
})
}
How can I update my code to have the dynamicity shown in the second example (if possible), otherwise the first example would do just fine
Another solution using map:
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
mylist: [
{
id: 0,
trueorfalse: false
},
{
id: 1,
trueorfalse: true
}
]
};
}
toggleBoolean = () => {
const ID = Number(this.state.selectedID);
this.setState(prevState => ({
mylist: prevState.mylist.map(item => {
if (item.id === ID) {
return { ...item, trueorfalse: !item.trueorfalse };
} else {
return item;
}
})
}));
};
render() {
return (
<div className="App">
<p>{`State values: ${JSON.stringify(this.state.mylist)}`}</p>
<button onClick={this.toggleBoolean}>Change true/false values</button>
<label>Insert ID:</label>
<input
type="number"
onChange={event => this.setState({ selectedID: event.target.value })}
/>
</div>
);
}
}
I think the following code would accomplish your second question.
var idnum = e.target.id.toString().split("_")[1]
let newList = Array.from(this.state.mylist) //create new array so we don't modify state directly
if (type === 1) {
let objToUpdate = newList.find((el) => el.id === idnum) // grab first element with matching id
objToUpdate.trueorfalse = !objToUpdate.trueorfalse
this.setState( { mylist: newList } )
}
I have 2 Components: Community.js and Edit.js
I call to Edit from Community below:
<DetailModal
DetailModal={Detail}
errors={this.state.errors}
uploadFile={this.props.uploadFileActions.uploadFile}
onSave={this.save}
onChange={this.onChange}
mode={this.state.mode}
data={this.state.details}
isOpen={this.state.modalIsOpen}
closeModal={this.closeModal}
editable={isHasEditPermisson}
/>
At Community, I have a function onchange() below:
onChange = (field, data) => {
let value = null;
if (data) {
value = data
}
this.setState(state => ({
details: {
...state.details,
[field]: value
},
errors: {
...state.errors,
[field]: undefined
}
}));
// }
}
At Edit, I have a function which called to select image/video file:
selectFile = (file) => {
if (file && file.target.files.length > 0) {
const checkType = file.target.files[0].type.split('/')[0]
const extendType = file.target.files[0].type.split('/')[1]
const fileArr = [];
// if (checkType === "video") {
// console.log('this.getDuration(file)', this.getDuration(file))
// if (this.getDuration(file) > 60) {
// alert("stop");
// return;
// }
// }
// this.props.uploadFile(file.target.files[0], (res) => {
// this.props.onChange('ResourceUrl', `${this.props.data.ResourceUrl ? `${this.props.data.ResourceUrl};` : ''}${res.data.Data}`);
// });
fileArr.push({
file: file.target.files[0],
urlFile: URL.createObjectURL(file.target.files[0]),
});
this.props.onChange('ResourceUrl', `${this.props.data.ResourceUrl ? `${this.props.data.ResourceUrl};` : ''}${fileArr[0].urlFile}`);
this.props.onChange('ResourceFile', this.props.data.ResourceFile ? this.props.data.ResourceFile : fileArr[0].file);
if (checkType === "image") {
this.setState({
fileType: "image/*",
extend: extendType
})
} else {
this.setState({
fileType: "video/*",
countVideo: 1,
extend: extendType
})
}
// file.target.value = '';
}
}
This is Init state in Community:
constructor(props) {
super(props);
this.escFunction = this.escFunction.bind(this);
this.state = {
modalIsOpen: false,
mode: 'add',
details: {},
errors: {},
defaultRole: constants.CollaboratorRole.default,
permanentRole: constants.CollaboratorRole.permanent,
isOpenDeleteConfirm: false
};
}
Here, I call to onchange() in Community to set value for 2 field: ResourceUrl, ResourceFile
But I have an issue when set value for ResourceFile. When I choose second file then I still get value of first file.
I don't know how to set the value of the second file into ResourceFile, which means that I expect that ResourceFile is an array containing the information of the two files I just selected.
I have a react native codebase in which I import a component and use the same function twice but with slight differences. I would like to outsource it into a new component somehow. Any ideas?
It looks like this :
handleConfirmApplication = async () => {
const checkVals =
get('shiftInvite.account.accountName', this.props) === ONBOARDING_ACCOUNT
? omit('payRate', this.props.confirmationCheckValues)
: this.props.confirmationCheckValues;
if (Object.values(checkVals).every(val => val)) {
this.props.onToggleConfirmPopUp();
this.props.onToggleLoadingApply();
try {
await this.handleShiftInviteDecision('ACCEPT')();
} catch (e) {
Alert.alert('Error', parseError(e));
} finally {
this.props.onToggleLoadingApply();
}
} else {
Alert.alert('Error', 'Please confirm all shift requirements');
}
};
And the second one is the following :
handleConfirmApplication = async () => {
const checkVals =
get('shift.account.accountName', this.props) === ONBOARDING_ACCOUNT
? omit('payRate', this.props.confirmationCheckValues)
: this.props.confirmationCheckValues;
if (Object.values(checkVals).every(val => val)) {
this.props.onToggleConfirmPopUp();
this.props.onToggleLoadingApply();
try {
const shiftId = this.props.shift.id;
const {
data: { updatedShifts },
} = await this.props.updateMyApplication(shiftId, 'APPLY');
this.setState({
updatedShift: updatedShifts.find(({ id }) => id === shiftId),
});
} catch (e) {
Alert.alert('Error', parseError(e));
} finally {
this.props.onToggleLoadingApply();
}
} else {
Alert.alert('Error', 'Please confirm all shift requirements');
}
};
Simply use an if/else statement in your try/catch and a ternary condition to create your string. Choosing between one or another should be done by passing a parameter to your function :
handleConfirmApplication = async (isInvite) => {
const checkVals =
get(`shift${isInvite ? 'Invite' : ''}.account.accountName`, this.props) === ONBOARDING_ACCOUNT
? omit('payRate', this.props.confirmationCheckValues)
: this.props.confirmationCheckValues;
if (Object.values(checkVals).every(val => val)) {
this.props.onToggleConfirmPopUp();
this.props.onToggleLoadingApply();
try {
if(isInvite){
await this.handleShiftInviteDecision('ACCEPT')();
}
else{
const shiftId = this.props.shift.id;
const {
data: { updatedShifts },
} = await this.props.updateMyApplication(shiftId, 'APPLY');
this.setState({
updatedShift: updatedShifts.find(({ id }) => id === shiftId),
});
}
} catch (e) {
Alert.alert('Error', parseError(e));
} finally {
this.props.onToggleLoadingApply();
}
} else {
Alert.alert('Error', 'Please confirm all shift requirements');
}
};
And calling it :
handleConfirmApplication(true)
Have I missed any other differences between your functions ?
To Use it in a reusable component :
handleConfirmApplication = async () => {
const { isInvite } = this.props
const checkVals =
And calling it :
<MyComponent isInvite={false} /> //Just switch it to true to get the other version