I want to all the suggestions on "input-focus" and when the user further types the text so also the suggestions will appear in react-autosuggest.
import "./styles.css";
import Autosuggest from "react-autosuggest";
import { useState } from "react";
const options = ["Suggestion 1", "Suggestion 2", "Suggestion 3"];
export default function App() {
const [s, setS] = useState(options);
const [x, setX] = useState("Suggestion 1");
const [y, setY] = useState(false);
return (
<div className="App">
<Autosuggest
inputProps={{
value: x,
onChange: (event, { newValue }) => {
setX(newValue);
},
onFocus: () => {
setY(true);
},
onBlur: () => {
setY(false);
}
}}
suggestions={s}
onSuggestionsFetchRequested={({ value }) => {
setS(options.filter((x) => x.includes(value)));
}}
onSuggestionsClearRequested={() => {
setS([]);
}}
renderSuggestion={(text: string) => {
return <>{text}</>;
}}
getSuggestionValue={(a) => {
return a;
}}
alwaysRenderSuggestions={y}
/>
</div>
);
}
Code sandbox here. HTH.
Related
Why when user click "remove button" in ListItem component, the item text are disappear but the button itself are still there, if possible how to get rid of that "remove button" too?
P.S The atom family item are got removed but the ui are not get updated ("remove button" are still there), is that a normal things?
import React, {useState} from "react";
import { atom, useRecoilState, useResetRecoilState, useRecoilCallback, atomFamily, useRecoilValue, useSetRecoilState } from "recoil";
import "./styles.css";
const idsState = atom({
key: "circleColor",
default: [],
});
const noteState = atomFamily({
key: "noteState",
default: []
})
const ListItem = ({ id }) => {
const [note, setNote] = useRecoilState(noteState(id));
const handleRemoveNote = useResetRecoilState(noteState(id));
return (
<div key={note.id} className="list-item">
<p>{note.text}</p>
<button onClick={handleRemoveNote}>Remove</button>
</div>
)
}
const App = () => {
const ids = useRecoilValue(idsState);
const nextId = ids.length;
const addNote = useRecoilCallback(({set}) => (newNote) => {
set(idsState, [...ids, nextId])
set(noteState(nextId), newNote);
})
const [text, setText] = useState("");
const handleAddNote = (e) => {
e.preventDefault();
const id = Math.round(Math.random() * 1000);
const newNote = {
text,
id,
subNote: [
{
label: "zero",
value: "0"
},
{
label: "one",
value: "1"
},
{
label: "two",
value: "two"
}
]
};
addNote(newNote);
}
return (
<div>
<form className="form-container" onSubmit={handleAddNote}>
<input onChange={e => setText(e.target.value)} />
<button>Add</button>
</form>
<div>
{ids.map(id => (
<ListItem id={id} />
))}
</div>
</div>
);
};
export default App;
I am trying to implement DraftJS within an existing functional component and I am unable to figure out how to do this. It appears that all of the documentation and user-submitted content refers to class components.
I try to set it up using the following:
import { Editor } from "react-draft-wysiwyg";
import { EditorState } from 'draft-js'
export default function myFunctionalComponent() {
const [editorState, setEditorState] = useState(EditorState.createEmpty())
return(
<Editor
editorState={editorState}
onChange={setEditorState}
/>
)
}
However, unfortunately, I get this error in the console:
Warning: Can't call setState on a component that is not yet mounted.
This is a no-op, but it might indicate a bug in your application.
Instead, assign to this.state directly or define a state = {};
class property with the desired state in the r component.
Is there a way to make this work in a functional component?
As my very first answer in StackOverflow. :)
I took the example from https://github.com/facebook/draft-js/blob/main/examples/draft-0-10-0/rich/rich.html and converted it into a functional components 'RTEditor', .. .
Use the component with setContent as a prop. It takes the function to update parent elements state from useState
const [content, setContent] = useState<any>({})
...
<RTEditor setContent={setContent} />
RTEditor.tsx
import React, { useState, useRef } from 'react'
import {
Editor,
EditorState,
RichUtils,
getDefaultKeyBinding,
ContentBlock,
DraftHandleValue,
convertFromHTML,
convertFromRaw,
convertToRaw,
ContentState,
RawDraftContentState,
} from 'draft-js'
import 'draft-js/dist/Draft.css'
import BlockStyleControls from './BlockStyleControls'
import InlineStyleControls from './InlineStyleControls'
type Props = {
setContent: (state: RawDraftContentState) => void
}
const RTEditor = ({ setContent }: Props) => {
const editorRef = useRef(null)
const [editorState, setEditorState] = useState(EditorState.createEmpty())
const styleMap = {
CODE: {
backgroundColor: 'rgba(0, 0, 0, 0.05)',
fontFamily: '"Inconsolata", "Menlo", "Consolas", monospace',
fontSize: 16,
padding: 2,
},
}
const getBlockStyle = (block: ContentBlock) => {
switch (block.getType()) {
case 'blockquote':
return 'RichEditor-blockquote'
default:
return ''
}
}
const onChange = (state: EditorState) => {
setEditorState(state)
setContent(convertToRaw(editorState.getCurrentContent()))
}
const mapKeyToEditorCommand = (e: any): string | null => {
if (e.keyCode === 9 /* TAB */) {
const newEditorState = RichUtils.onTab(e, editorState, 4 /* maxDepth */)
if (newEditorState !== editorState) {
onChange(newEditorState)
}
return null
}
return getDefaultKeyBinding(e)
}
const handleKeyCommand = (
command: string,
editorState: EditorState,
eventTimeStamp: number
): DraftHandleValue => {
const newState = RichUtils.handleKeyCommand(editorState, command)
if (newState) {
onChange(newState)
return 'handled'
}
return 'not-handled'
}
const toggleBlockType = (blockType: string) => {
onChange(RichUtils.toggleBlockType(editorState, blockType))
}
const toggleInlineStyle = (inlineStyle: string) => {
onChange(RichUtils.toggleInlineStyle(editorState, inlineStyle))
}
return (
<>
<BlockStyleControls
editorState={editorState}
onToggle={toggleBlockType}
/>
<InlineStyleControls
editorState={editorState}
onToggle={toggleInlineStyle}
/>
<Editor
ref={editorRef}
editorState={editorState}
placeholder='Tell a story...'
customStyleMap={styleMap}
blockStyleFn={(block: ContentBlock) => getBlockStyle(block)}
keyBindingFn={(e) => mapKeyToEditorCommand(e)}
onChange={onChange}
spellCheck={true}
handleKeyCommand={handleKeyCommand}
/>
</>
)
}
export default React.memo(RTEditor)
BlockStyleControls.tsx
import React from 'react'
import { EditorState } from 'draft-js'
import StyleButton from './StyleButton'
const BLOCK_TYPES = [
{ label: 'H1', style: 'header-one' },
{ label: 'H2', style: 'header-two' },
{ label: 'H3', style: 'header-three' },
{ label: 'H4', style: 'header-four' },
{ label: 'H5', style: 'header-five' },
{ label: 'H6', style: 'header-six' },
{ label: 'Blockquote', style: 'blockquote' },
{ label: 'UL', style: 'unordered-list-item' },
{ label: 'OL', style: 'ordered-list-item' },
{ label: 'Code Block', style: 'code-block' },
]
type Props = {
editorState: EditorState
onToggle: (bockType: string) => void
}
const BlockStyleControls = ({ editorState, onToggle }: Props) => {
const selection = editorState.getSelection()
const blockType = editorState
.getCurrentContent()
.getBlockForKey(selection.getStartKey())
.getType()
return (
<div className='RichEditor-controls'>
{BLOCK_TYPES.map((type) => (
<StyleButton
key={type.label}
active={type.style === blockType}
label={type.label}
onToggle={onToggle}
style={type.style}
/>
))}
</div>
)
}
export default React.memo(BlockStyleControls)
InlineStyleControls.tsx
import React from 'react'
import { EditorState } from 'draft-js'
import StyleButton from './StyleButton'
const INLINE_STYLES = [
{ label: 'Bold', style: 'BOLD' },
{ label: 'Italic', style: 'ITALIC' },
{ label: 'Underline', style: 'UNDERLINE' },
{ label: 'Monospace', style: 'CODE' },
]
type Props = {
editorState: EditorState
onToggle: (bockType: string) => void
}
const InlineStyleControls = ({ editorState, onToggle }: Props) => {
const currentStyle = editorState.getCurrentInlineStyle()
return (
<div className='RichEditor-controls'>
{INLINE_STYLES.map((type) => (
<StyleButton
key={type.label}
active={currentStyle.has(type.style)}
label={type.label}
onToggle={onToggle}
style={type.style}
/>
))}
</div>
)
}
export default React.memo(InlineStyleControls)
StyleButton.tsx
import React from 'react'
type Props = {
active: boolean
style: string
label: string
onToggle: (bockType: string) => void
}
const StyleButton = ({ active, style, label, onToggle }: Props) => {
const _onToggle = (e: any) => {
e.preventDefault()
onToggle(style)
}
const className = 'RichEditor-styleButton'
return (
<button
className={className + `${active ? ' RichEditor-activeButton' : ''}`}
onClick={_onToggle}
>
{label}
</button>
)
}
export default React.memo(StyleButton)
Sry for not covering all typings. Hope that helps.
I was able to solve this using React useCallback hook and it works for me.
import { EditorState } from 'draft-js'
export default function myFunctionalComponent() {
const [editorState, setEditorState] = useState(EditorState.createEmpty())
const onEditorStateChange = useCallback(
(rawcontent) => {
setEditorState(rawcontent.blocks[0].text);
},
[editorState]
);
return(
<Editor
placeholder="Tell a story..."
onChange={onEditorStateChange}
/>
)
}
I'm testing react component use enzyme and jest test. I'm try use find method in enzyme but it not found, i'm sure this element have been render because when I print actionClass const it return value "ts-cbb-item".
I have a combobox component:
/* eslint-disable no-use-before-define */
import Autocomplete from '#material-ui/lab/Autocomplete';
import PropTypes from 'prop-types';
import React, { useState, useRef } from 'react';
import './index.scss';
import InputCombobox from './input-combobox';
const ComboBox = (props) => {
const {
loading,
options,
onDataBinding,
onChange,
customRenderItem,
placeholder,
renderStartAdornment,
closeIcon,
disabled,
customGetOptionLabel,
onInputChange,
style,
clearOnBlur = false,
defaultValue,
...rest
} = props;
const currentSearch = useRef();
const [currentOption, setOption] = useState(null);
const [isInput, setIsInput] = useState(false);
const handleInputChange = (_, value, reason) => {
const isReasonInput = reason === 'input';
if (isReasonInput) {
setIsInput(false);
if (onInputChange)
onInputChange(value);
}
currentSearch.current = value;
if (value?.length < 3) return;
if (isReasonInput) onDataBinding(value);
}
const handleChangeOpt = (opt) => {
setIsInput(true);
if (onChange) onChange(opt);
}
return (
<Autocomplete
clearOnBlur={clearOnBlur}
disabled={disabled}
closeIcon={closeIcon}
className="ts-combobox"
options={options}
loading={loading}
onInputChange={handleInputChange}
defaultValue={defaultValue}
getOptionLabel={(option) => customGetOptionLabel
? customGetOptionLabel(option)
: option.label}
getOptionSelected={option => {
if (!currentOption || !currentOption.value) return false;
return option.value === currentOption.value;
}}
style={style ? style : { width: '100%' }}
renderOption={(option, state) => {
const actionClass = state?.selected ? "ts-ccb-item active" : "ts-ccb-item";
console.log('class:', actionClass);
return <div
onClick={() => {
setOption(option);
handleChangeOpt(option);
}}
className={actionClass}>
{ customRenderItem
? customRenderItem(option, currentSearch)
: option.label }
</div>
}}
);
}
export default ComboBox;
This is my test :
let initProps = {
loading: false,
options: [],
onDataBinding: () => {},
onChange: () => {},
customRenderItem: () => {},
renderStartAdornment: () => {},
closeIcon: null,
disabled: false,
customGetOptionLabel: () => {},
onInputChange: () => {},
style: null,
clearOnBlur: false,
placeholder: '',
defaultValue: null
}
const options = [
{
label: 'Cristiano Ronaldo',
value: 'Portugal'
},
{
label : 'Leo Messi',
value : 'Argentina'
},
{
label : 'Jesse Lingard',
value : 'England'
}
]
const event = {
preventDefault() {},
}
const onInputChangeMockFn = jest.fn((value) => value);
const onDataBindingMockFn = jest.fn( (value) => value? true: false);
const renderStartAdornmentMockFn = jest.fn((option) => option ? option.value : null );
const customGetOptionLabelMockFn = jest.fn((option) => option? option.label : null)
const renderInputParams = {
id: '',
disabled: false,
fullWidth: true,
size: 'small',
InputLabelProps: {},
InputProps: {},
inputProps: {}
}
it("Test_Comobox_With_RenderInput_Active(RenderStartAdornment_Have_Value)", () => {
initProps.renderStartAdornment = renderStartAdornmentMockFn;
initProps.customGetOptionLabel = customGetOptionLabelMockFn;
initProps.options = options;
const wrapper = mount(
<ComboBox {...initProps} />
);
const autoCompleted = wrapper.find(Autocomplete);
autoCompleted.props().renderOption(options[1], autoCompletedRenderOptionState);
autoCompleted.props().renderInput(renderInputParams);
expect(autoCompleted.find('div .ts-cbb-item')).toHaveLength(1);
const inputCombobox = wrapper.find(InputCombobox);
expect(inputCombobox.props().renderStartAdornment).toBeUndefined();
})
How can I find exactly element div has ClassName 'ts-cbb-item' in this case?
I want to add a debounce function to not launch the search api for every character:
export const debounce = <F extends ((...args: any) => any)>(
func: F,
waitFor: number,
) => {
let timeout: number = 0;
const debounced = (...args: any) => {
clearTimeout(timeout);
setTimeout(() => func(...args), waitFor);
};
return debounced as (...args: Parameters<F>) => ReturnType<F>;
};
Search.tsx:
import React, { useCallback } from "react";
import { useSelector, useDispatch } from "react-redux";
import { getAllTasks } from "./taskSlice";
import { ReturnedTask } from "../../api/tasks/index";
import TextField from "#material-ui/core/TextField";
import Autocomplete from "#material-ui/lab/Autocomplete";
import { AppState } from "../../app/rootReducer";
import { debounce } from "../../app/utils";
const SearchTask = () => {
const dispatch = useDispatch();
const [open, setOpen] = React.useState(false);
const debounceOnChange = useCallback(debounce(handleChange, 400), []);
const { tasks, userId } = useSelector((state: AppState) => ({
tasks: state.task.tasks,
userId: state.authentication.user.userId,
}));
const options = tasks.length
? tasks.map((task: ReturnedTask) => ({ title: task.title }))
: [];
const handleOpen = () => setOpen(true);
const handleClose = () => setOpen(false);
function handleChange(
e: React.ChangeEvent<{}>,
value: string,
) {
const queryParams = [`userId=${userId}`];
if (value.length) {
console.log(value);
queryParams.push(`title=${value}`);
dispatch(getAllTasks(queryParams));
}
}
return (
<Autocomplete
style={{ width: 300 }}
open={open}
onOpen={handleOpen}
onClose={handleClose}
onInputChange={(e: React.ChangeEvent<{}>, value: string) =>
debounceOnChange(e, value)}
getOptionSelected={(option, value) => option.title === value.title}
getOptionLabel={(option) => option.title}
options={options}
renderInput={(params) => (
<TextField
{...params}
label="Search Tasks"
variant="outlined"
InputProps={{ ...params.InputProps, type: "search" }}
/>
)}
/>
);
};
export default SearchTask;
The actual behavior of the component is launching the api for every character typed. I want to launch the api only after an amount of time. How can I fix it?
As #Jayce444 mentioned in the comment, the the context was not preserved between every call and the function.
export const debounce = <F extends ((...args: any) => any)>(
func: F,
waitFor: number,
) => {
let timeout: NodeJS.Timeout;
return (...args: any) => {
clearTimeout(timeout);
timeout = setTimeout(() => func(...args), waitFor);
};
};
Dears
I am working on React project and currently struggling with small problem.
what is an issue ? When app is loading on a mobile view I see that all components are showing and after few ms unneeded data disappear. I would like to modify my app to display only needed components.
import React, { useState, useEffect } from "react";
import styled from "styled-components";
import PropTypes from "prop-types";
import RegistrationInputSide from "./../RegistrationInputSide/RegistrationInputSide";
import AppInfoSide from "./../AppInfoSide/AppInfoSide";
import SuccessWindow from "./../SuccessWindow/SuccessWindow";
import { names, labels, types} from "./registrationFormData";
const FormStyle = styled.div.attrs(({ className }) => ({
className,
}))`
display: flex;
width: 100%;
height: 100%;
.fields {
display: ${({ fieldsVisibility }) => (fieldsVisibility ? "flex" : "none")};
}
.info {
display: ${({ infoVisibility }) => (infoVisibility ? "flex" : "none")};
}
`;
const RegistrationForm = ({user, onChange, validation, onSubmit, formCorrectness, formModified}) => {
const [isMobile, setIsMobile] = useState(false);
const [isMoved, setIsMoved] = useState(false);
const [firstRender, setFirstRender] = useState(true);
const {password, repeatedPassword, email } = user;
const {emailValidation, passwordValidation, repeatedPasswordValidation} = validation;
const calculateMobileVisibility = (form) => {
const {name, isMobile} = form;
const visibilityRequirements = {
"info": [{"isMobile":true, "isMoved":false, "isModified":false, "formCorrectness": false}],
"fields": [{"isMobile":true, "isMoved":true, "isModified":false, "formCorrectness": false},{"isMobile":true, "isMoved":false, "isModified":true, "formCorrectness": false}, {"isMobile":true, "isMoved":true, "isModified":true, "formCorrectness": false}],
"success": [{"isMobile":true, "isMoved":true, "isModified":false, "formCorrectness": true},{"isMobile":true, "isMoved":false, "isModified":true, "formCorrectness": true}, {"isMobile":true, "isMoved":true, "isModified":true, "formCorrectness": true}]
}
if(isMobile === false){
return true;
}
const requirements = visibilityRequirements[name];
delete form.name;
let resultArr = requirements.map(requirement => {return Object.keys(form).map(formItem => requirement[formItem] === form[formItem])});
const finalArr = resultArr.map(singleRes => singleRes.every((val) => val === true));
return finalArr.includes(true);
};
const calculateInfoSideVisiblity = () => {
return calculateMobileVisibility({"isMobile":isMobile, "isMoved": isMoved, "isModified": formModified ,"formCorrectness": formCorrectness, "name": "info"});
}
const calculateFieldsSideVisibility = () => {
return calculateMobileVisibility({"isMobile":isMobile, "isMoved": isMoved, "isModified": formModified ,"formCorrectness": formCorrectness, "name": "fields"});
}
const onClickHandleMobile = (e) => {
e.preventDefault();
setIsMoved(true);
};
const handleResize = () => {
window.visualViewport.width > 576 ? setIsMobile(false) : setIsMobile(true);
};
useEffect(() => {
if(firstRender===true){
handleResize();
setFirstRender(false);
}
window.addEventListener("resize", handleResize);
return () => {
window.removeEventListener("resize", handleResize);
};
});
return (
<FormStyle fieldsVisibility={calculateFieldsSideVisibility()} infoVisibility={calculateInfoSideVisiblity()}>
<form onSubmit={onSubmit}>
<AppInfoSide
className="form-info-side info"
onClick={onClickHandleMobile}
/>
{!formCorrectness && (<RegistrationInputSide
className="form-user-input-side fields"
inputFieldsData={inputFieldsData}
onClick={onSubmit}
onChange={onChange}
user={user}
/>)}
{formCorrectness && (<SuccessWindow
className="success-window fields"
successMessage="Account successfully created!"
/>)}
</form>
</FormStyle>
);
};
I do not know how to prevent displaying unwanted elements.