Using Draft js mention plugin with react hooks - reactjs

I have been trying to get draft js mention plugin to work with react hooks but can't seem to figure what's wrong with the code. Appreciate any help on this.
import React, { useRef, useState, useEffect } from "react";
import { EditorState } from "draft-js";
import Editor from "draft-js-plugins-editor";
import createMentionPlugin, { defaultSuggestionsFilter } from "draft-js-mention-plugin";
import mentions from "./mentions";
export default function MentionEditor() {
const [editorState, setEditorState] = useState(EditorState.createEmpty());
const [suggestions, setSuggestions] = useState(mentions);
const editor = useRef(null);
useEffect(() => {
editor.current.focus();
}, [])
const mentionPlugin = createMentionPlugin();
const { MentionSuggestions } = mentionPlugin;
const plugins = [mentionPlugin];
const onSearchChange = ({ value }) => {
setSuggestions(defaultSuggestionsFilter(value, mentions))
};
return (
<div style={{ border: "1px solid gray" }}>
<Editor
editorState={editorState}
onChange={editorState => setEditorState(editorState)}
plugins={plugins}
ref={editor}
/>
<MentionSuggestions
onSearchChange={onSearchChange}
suggestions={suggestions}
/>
</div>
);
}

You need to move the draft-js plugin configuration outside the component arrow function. This is a pretty basic Draft-JS implementation using a functional component and hooks:
import React, { useState, useRef } from 'react'
import { EditorState } from 'draft-js'
import Editor from 'draft-js-plugins-editor'
import createMentionPlugin, { defaultSuggestionsFilter } from 'draft-js-mention-plugin'
import 'draft-js/dist/Draft.css'
import 'draft-js-mention-plugin/lib/plugin.css'
import mentions from "./mentions"
// Draft-JS-Mentions plugin configuration
const mentionPlugin = createMentionPlugin()
const { MentionSuggestions } = mentionPlugin
const plugins = [mentionPlugin]
const MyEditor= () => {
const [suggestions, setSuggestions] = useState(mentions)
// Draft-JS editor configuration
const [editorState, setEditorState] = useState(
() => EditorState.createEmpty(),
)
const editor = useRef(null)
// Check editor text for mentions
const onSearchChange = ({ value }) => {
setSuggestions(defaultSuggestionsFilter(value, mentions))
}
const onAddMention = () => {
}
// Focus on editor window
const focusEditor = () => {
editor.current.focus()
}
return (
<div onClick={() => focusEditor()}>
<Editor
ref={editor}
editorState={editorState}
plugins={plugins}
onChange={editorState => setEditorState(editorState)}
placeholder={'Type here...'}
/>
<MentionSuggestions
onSearchChange={onSearchChange}
suggestions={suggestions}
onAddMention={onAddMention}
/>
</div>
)
}
export default MyEditor

Just move these lines outside component and it will work:
const mentionPlugin = createMentionPlugin();
const { MentionSuggestions } = mentionPlugin;
const plugins = [mentionPlugin];
export default function MentionEditor() {
const [editorState, setEditorState] = useState(EditorState.createEmpty());
.. ... ...
}

!!!!!!!!!!!!!!!! PAY ATTENTION !!!!!!!!!!!!
The onSearchChange method will be triggered once the '#' character is typed, so in this case it will return just 5 items that fit the empty string...
To prevent this to be happened, just check that the value we want to search is not empty:
const onSearchChange = ({ value }) => {
if (value) {
setSuggestions(defaultSuggestionsFilter(value, mentions));
}
};

Related

TypeError: Cannot read properties of null (reading 'useContext')

I want add {searchWord} from useContext, to function with API call getServerSideProps(), but I got different errors.
The last one: Cannot read properties of null (reading 'useContext').
Maybe something wrong with my provider???
File with API call function /search-results:
import Link from 'next/link';
import { Text, Stack, Box, SimpleGrid, IconButton, Button, useQuery } from '#chakra-ui/react'
import ListOfCards from '../components/ListOfCards';
import { SearchContext, SearchContextProps } from './_app';
import { useContext, useEffect, useState } from 'react';
import { ChevronLeftIcon } from '#chakra-ui/icons';
function SearchResult({ searchResultForMapping }) {
console.log(searchResultForMapping)
const [queryKey, setQueryKey] = useState('lebowski');
const { searchWord } = useContext<SearchContextProps>(SearchContext);
useEffect(() => {
setQueryKey(searchWord);
}, [searchWord]);
const mockup = {"status":"OK","copyright":"Copyright (c) 2022 The New York Times Company. All Rights Reserved.","has_more":false,"num_results":1,"results":[{"display_title":"The Big Lebowski","mpaa_rating":"R","critics_pick":1,"byline":"Janet Maslin","headline":"Big Lebowski, the (Movie)","summary_short":"In a film both sweeter and loopier than \u0026quot;Fargo,\u0026quot; Joel and Ethan Coen and their rogues' gallery of actors explore Southern California's post-hippie, surfer-dude bowling scene. The results are irresistible, at least for audiences on the film's laid-back wavelength and in tune with its deadpan humor. With shaggy, baggy looks that belie his performance's fine comic control, Jeff Bridges plays a guy who truly deserves to be called Dude, and is by turns oblivious and indignant once a ruined rug leads him into neo-noir escapades. In a film that certainly keeps its audience guessing what might happen next (Saddam Hussein renting bowling shoes?), John Turturro takes the cake as the most fiendish of bowlers. — Janet Maslin","publication_date":"1998-03-06","opening_date":"1998-03-06","date_updated":"2017-11-02 04:17:54","link":{"type":"article","url":"https://www.nytimes.com/1998/03/06/movies/film-review-a-bowling-ball-s-eye-view-of-reality.html","suggested_link_text":"Read the New York Times Review of The Big Lebowski"},"multimedia":null}]}
return (
<><Button leftIcon={<ChevronLeftIcon />} colorScheme='pink' variant='solid' position='absolute' right='20px;' top='25px'>
<Link href="/">Home</Link>
</Button>
<Box p={5}>
<SimpleGrid gap={12} p={12} columns={1}>
<Text fontSize='4xl'> Search Result for keyword "{queryKey} i {searchWord}"</Text>
<Stack spacing={3} rowGap={3}>
<ListOfCards movieDetails={mockup} />
</Stack>
<Text fontSize='xl'><Link href="/">Search last key word..</Link> </Text>
</SimpleGrid>
</Box></>
);
}
const { searchWord } = useContext<SearchContextProps>(SearchContext);
export async function getServerSideProps() {
// const queryWord = 'iron'
const queryWord = searchWord;
const apiKey = "nTCRt5WnuaiL5Q5VsPEgeGM8oZifd3Ma";
const endpoint = "https://api.nytimes.com/svc/movies/v2/reviews/search.json?";
const createUrl = `${endpoint}query=${queryWord}&api-key=${apiKey}`;
const res = fetch(createUrl);
const searchResultForMapping = (await res).json();
return { props: { searchResultForMapping } };}
export default SearchResult
Page where i setSearchWord SearchLine.tsx:
import { Button } from "#chakra-ui/button";
import { Input, InputGroup, InputRightElement } from "#chakra-ui/input";
import React, { useContext } from "react";
import { useRouter } from 'next/router'
import { SearchContext } from "../pages/_app";
const SearchLine = () : JSX.Element => {
const { searchWord, setSearchWord, searchHistory, setSearchHistory } = useContext(SearchContext);
const router = useRouter()
const handleChange = (keyword) => setSearchWord(keyword.target.value);
const handleOnclick = () => {
setSearchHistory([...searchHistory, searchWord]);
router.push('/search-result');
}
return (
<InputGroup size="md">
<Input
onChange={handleChange}
placeholder='Enter the name of the movie or keyword'
size='md'
/>
<InputRightElement width="4.5rem">
<Button onClick={handleOnclick}>
Enter
</Button>
</InputRightElement>
</InputGroup>
)
}
export default SearchLine;
my _app.tsx:
import { ChakraProvider } from '#chakra-ui/react'
import { QueryClientProvider } from '#tanstack/react-query';
import { AppProps } from 'next/app';
import { createContext, useContext, useMemo, useState } from 'react';
export interface SearchContextProps {
searchWord: string;
setSearchWord: (f: string) => void;
searchHistory: string[];
setSearchHistory: (f: string[]) => void;
}
export const SearchContext = createContext<SearchContextProps>({
searchWord: '',
setSearchWord: () => {
// do nothing
},
searchHistory: [],
setSearchHistory: () => {
// do nothing
},
});
const SearchProvider = (props: any) => {
const [searchWord, setSearchWord] = useState('');
const [searchHistory, setSearchHistory] = useState('');
const value = useMemo(() => ({ searchWord, setSearchWord, searchHistory, setSearchHistory }), [searchWord]);
return <SearchContext.Provider value={value} {...props} />;
};
function MyApp({ Component, pageProps }: AppProps): JSX.Element {
const context = useContext(SearchContext);
return (
<ChakraProvider>
<SearchProvider>
<Component {...pageProps} />
</SearchProvider>
</ChakraProvider>
)
}
export default MyApp
It seems your SearchProvider is wrong.
const SearchProvider = (props: any) => {
const [searchWord, setSearchWord] = useState('');
const [searchHistory, setSearchHistory] = useState('');
const value = useMemo(() => ({ searchWord, setSearchWord, searchHistory, setSearchHistory }), [searchWord]);
return (
<SearchContext.Provider value={value}>
{props.children}
</SearchContext.Provider>
);
};

My SetState is erasing my previous content

I return this content from my ReviewPictures.tsx screen.
My problem is when i take a new Photo with my Photo Button
My SetState erase the previous Photos List
The Code :
ReviewPictures.tsx : Here i return my photos and my PhotoIcons Button
which can retake a photo if the user want so.
import {NavigationProp, RouteProp, useNavigation, useRoute} from '#react-navigation/native'; import React, {useContext, useEffect, useState} from 'react'; import {Image, StyleSheet, Text, View, ScrollView} from 'react-native'; import {Button} from 'react-native-paper'; import AnalyseContext from '../../contexts/Photoshoot/AnalyseContext'; import {Step, StepAlias} from '../../domain/photoShoot/Step'; import {PhotoshootStackParamList} from '../../navigation/PhotoshootNavigator'; import {IconButton, Colors} from 'react-native-paper'; import I18n from 'i18n-js'; import {AppStackParamList} from '../../navigation/AppNavigator'; import {OnboardedStackParamList} from '../../navigation/OnboardedNavigator';
const styles = StyleSheet.create({ image: {
width: 150,
height: 150, }, container: {
flex: 1, }, box: {height: 250}, row: {flexDirection: 'column', alignItems: 'center'}, });
const ReviewPictures: React.FC = () => {
const {params} = useRoute<RouteProp<OnboardedStackParamList, 'Photoshoot'>>();
const {picturePaths} = useContext(AnalyseContext);
const [content, setContent] = useState<JSX.Element[]>([]);
const navigation = useNavigation<NavigationProp<PhotoshootStackParamList>>();
const Appnavigation = useNavigation<NavigationProp<AppStackParamList>>();
const toRedo = !params?.redo;
useEffect(() => {
setContent(setPageContent());
}, []);
const setPageContent = () => {
const c: JSX.Element[] = [];
picturePaths.forEach((value: string, step: StepAlias) => {
console.log(value);
c.push(
<View style={[styles.box, styles.row]}>
<Text>{I18n.t(`${step}`)}</Text>
<Image style={styles.image} source={{uri: value}} />
<IconButton
icon="camera"
color={Colors.blue500}
size={40}
onPress={() => {
console.log('Pressed');
Appnavigation.navigate('Logged', {
screen: 'Onboarded',
params: {screen: 'Photoshoot', params: {redo: toRedo, onboarded: false, review: step}},
});
}}
/>
</View>,
);
});
return c; };
return (
<>
<ScrollView style={styles.container}>{content}</ScrollView>
<Button onPress={() => navigation.navigate('Mileage')}>Valider</Button>
</> ); };
export default ReviewPictures;
And Photoshoot.tsx which take and store the photos :
import {RouteProp, useRoute} from '#react-navigation/native';
import I18n from 'i18n-js';
import React, {useContext, useEffect, useState} from 'react';
import {StatusBar} from 'react-native';
import Loading from '../../components/UI/loading/Loading';
import AnalysesContext from '../../contexts/Analyses/AnalysesContext';
import {UserContext} from '../../contexts/User/UserContext';
import {ONBOARDING_SCENARIO, WEAR_SCENARIO} from '../../domain/photoShoot/Scenario';
import {fromStepAlias, Step} from '../../domain/photoShoot/Step';
import {OnboardedStackParamList} from '../../navigation/OnboardedNavigator';
import PhotoshootNavigator from '../../navigation/PhotoshootNavigator';
import {
createRetakeScenario,
extractReferenceTyresToRetake,
extractWearTyresToRetake,
} from '../../utils/retakeTyres.utils';
/**
* Wrap AnalyseWearNavigator.
* Ensures steps and/or referenceId are loaded before use of AnalyseWearNavigator.
* Meanwhile, it shows a waiting loader.
*
* We have to wait before mounting AnalyseWearNavigator. Otherwise, Navigator take configuration it had at first mount and don't care if you update state later.
*/
const Photoshoot = () => {
const {params} = useRoute<RouteProp<OnboardedStackParamList, 'Photoshoot'>>();
const {user, vehicleId} = useContext(UserContext);
const {wear, reference, fetchingAnalysis} = useContext(AnalysesContext);
const [isLoading, setIsLoading] = useState(true);
const [referenceId, setReferenceId] = useState<number>();
const [steps, setSteps] = useState<Step[]>([]);
useEffect(() => {
if (!fetchingAnalysis) {
if (user && vehicleId) {
if (params?.redo) {
loadRetakeScenario();
}
if (params?.review) {
console.log('Joué ' + params?.review);
loadReviewScenario();
} else {
loadScenario();
}
}
}
}, [user, vehicleId, wear, reference, params, fetchingAnalysis]);
const loadReviewScenario = () => {
if (params?.review) {
setSteps([fromStepAlias(params.review)]);
setIsLoading(false);
// HERE THE PROBLEM WITH SETSTEPS
}
};
const loadRetakeScenario = () => {
const wearTyresToRetake = extractWearTyresToRetake(wear?.tyreWears);
const referenceTyresToRetake = extractReferenceTyresToRetake(reference?.tyreReferences);
const scenario = createRetakeScenario(wearTyresToRetake, referenceTyresToRetake);
setSteps(scenario);
setIsLoading(false);
};
const loadScenario = async () => {
setReferenceId(reference?.id);
setSteps(!!params?.onboarded ? WEAR_SCENARIO : ONBOARDING_SCENARIO);
setIsLoading(false);
};
const content = isLoading ? (
<Loading waitingText={I18n.t('ANALYSIS_PAGE.LOADING_MESSAGE')} />
) : (
<>
<StatusBar barStyle="dark-content" />
<PhotoshootNavigator steps={steps} referenceId={referenceId} redo={!!params?.redo} />
</>
);
return content;
};
export default Photoshoot;
Step.ts which guarantee the position of the tyre, it's a Tyre Recognition app :
import {PhotoType} from '../../models/PhotoType';
import {TyrePosition} from '../TyrePosition';
/** Step of a photo shoot */
export type Step = {
tyre: TyrePosition;
whichSideToTake: PhotoType;
};
export type StepAlias = `${TyrePosition}_${PhotoType}`;
export const toStepAlias = (step: Step): StepAlias => {
return `${step.tyre}_${step.whichSideToTake}`;
};
export const fromStepAlias = (stepAlias: StepAlias): Step => {
console.log(stepAlias.split('_'));
const split = stepAlias.split('_');
return {tyre: split[0] as TyrePosition, whichSideToTake: split[1] as PhotoType};
};
What's wrong with setStep ?
From what I can understand of your post, you are having some issue with the step state and updating it. In the three places in Photoshoot.tsx file where you enqueue any steps state updates you should probably use a functional state update to shallowly copy and update from any previously existing state instead of fully replacing it.
Example:
const loadReviewScenario = () => {
if (params?.review) {
setSteps(steps => [...steps, fromStepAlias(params.review)]);
setIsLoading(false);
}
};
const loadRetakeScenario = () => {
const wearTyresToRetake = extractWearTyresToRetake(wear?.tyreWears);
const referenceTyresToRetake = extractReferenceTyresToRetake(reference?.tyreReferences);
const scenario = createRetakeScenario(wearTyresToRetake, referenceTyresToRetake);
setSteps(steps => [...steps, scenario]);
setIsLoading(false);
};
const loadScenario = async () => {
setReferenceId(reference?.id);
setSteps(steps => [
...steps,
!!params?.onboarded ? WEAR_SCENARIO : ONBOARDING_SCENARIO
]);
setIsLoading(false);
};

Fetch data from the backend, then pass the data to editorState using draft-js

I'm creating a component that will display the data from backend by using draft-js. The data from the backend also is being stored using draft-js. The data is not being display and also there's no error message.
Sample Data from the backend is being parse before passing to the viewContent.js
Hello World
I tried to create a variable to check if the code is working. I tried this approach const sample = <p>Hello World. This one is working if pass this on contenBlocks.
viewContent.js
import {
EditorState,
ContentState,
convertFromHTML,
} from 'draft-js';
import { Editor } from 'react-draft-wysiwyg';
import draftToHtml from 'draftjs-to-html';
const viewContent = ({ content }) => {
const [editorState, setEditorState] = useState();
const clearState = () => {
ContentState.createFromText('');
};
const handleEditorChange = (state) => {
setEditorState(state);
let currentContentAsHTML = JSON.stringify(
draftToHtml(convertToRaw(editorState.getCurrentContent()))
);
convertedContent(currentContentAsHTML);
};
useEffect(() => {
const contentBlocks = convertFromHTML(content);
const contentState = ContentState.createFromBlockArray(
contentBlocks.contentBlocks,
contentBlocks.entityMap
);
setEditorState(EditorState.createWithContent(contentState));
}, [content]);
return (
<div className='comment-container p-2 border rounded-md'>
<Editor
editorState={editorState}
onEditorStateChange={handleEditorChange}
wrapperClassName='wrapper-class'
editorClassName='editor-class'
toolbarClassName='toolbar-class'
onClick={clearState}
/>
</div>
);
};
export default viewContent;
Parent Compontent
<viewContent
content={taskInfo.taskNote}
convertedContent={(convertedContent) =>
setTaskInfo((prevState) => ({
...prevState,
taskNote: convertedContent,
}))
}
/>
Error
You should set editor state after ViewContent component rendered completely. update your component as below:
...
useEffect(() => {
const contentBlocks = convertFromHTML(content);
const contentState = ContentState.createFromBlockArray(
contentBlocks.contentBlocks,
contentBlocks.entityMap
);
setEditorState(EditorState.createWithContent(contentState));
}, [content]);
...

how to detect the bug of filter not working reactjs

Hi I click the "delete" button using filterto delete the data I do not need,but not working and there is no any bug notification,so I do not know how to detect out my project.
import ColorCards from "./ColorCards";
import React, { useState } from "react";
import data from "./initialState.json";
export default function CardsafterEvents() {
const [colors, setColors] = useState(data.colors);
const onRemove = (id) => setColors(colors.filter((color) => color.id !== id));
return (
<>
<ColorCards handleDelete={onRemove} />
</>
);
}
full project link:https://codesandbox.io/s/material-demo-forked-62eh0?file=/CardsafterEvents.js:0-379
You need to pass the colors to your ColorCards element
CardsafterEvents.js
export default function CardsafterEvents() {
const [colors, setColors] = useState(data.colors);
const onRemove = (id) => setColors(colors.filter((color) => color.id !== id));
return (
<>
<ColorCards colors={colors} handleDelete={onRemove} />
</>
);
}
In your ColorCards element, not only use the data from initialState, also receive the colors prop from its parent, and use it prioritily with useMemo:
ColorCards.js
// ...
import React, { useMemo } from "react";
import data from "./initialState.json";
export default function ColorCards({ colors, handleDelete = (f) => f }) {
const dataColors = useMemo(() => colors || data.colors, [colors]);
return (...);
}
Here's the working example

React freezing when updating context

using react hooks
Experiencing a weird bug that seems to cause some sort of infinite loop or something. Has anyone run into something like this before.
Here's what I have:
import { createContext, useState, useCallback } from "react";
export type ModalValue = string | null;
const DEFAULT_ACTION = null;
export const useModalContext = (): ModalContextContents => {
const [
modal,
setModalInner,
] = useState<ModalValue>(DEFAULT_ACTION);
const setModal = useCallback((nv: ModalValue) => {
setModalInner(nv);
}, []);
return {
modal,
setModal,
};
};
interface ModalContextContents {
modal: ModalValue;
setModal: (nv: ModalValue) => void;
}
export const ModalContext = createContext<ModalContextContents>({modal: null, setModal: () => {}});
modal.tsx
import React, {useContext, useCallback} from 'react';
import {Box, Button, Aligner} from 'components';
import {ModalContext} from './modalContext';
import './modal.scss';
export const Modal = () => {
const modalApi = useContext(ModalContext);
if (modalApi.modal === null) {
return <></>
}
return <div key="lobby-modal" className='modal'>
<Aligner>
<Box style={{background: 'rgba(0, 0, 0, 0.72)'}}>
{modalApi.modal || ''}
<Button text="close" onClick={() => {modalApi.setModal(null)}}/>
</Box>
</Aligner>
</div>;
}
For some reason when I call:
modalApi.setModal('some-text');
then:
modalApi.setModal(null);
the entire page freezes.
Anyone have any idea what's going on here?
elsewhere in the app I had:
const callbackMapping: {[key: string]: Action} = useMemo(() => {
return {
callback: () => modelApi.setModal('wardrobe')
}
}, [room, modelApi])
then I was invoking it in a useEffect.
so I was causing an infinite in the app by changing the context value and then re-changing it.

Resources