Manually Focusing a DraftJS Component Only Works the First Time - javascript

I have rendered a a React Draft WYSIWYG component using the following code:
InputTextArea.js (parent component in charge of managing focus()
import { useRef } from "react"
import EditorConvertToHTML from "./EditorConvertToHTML";
export default function InputTextArea({ editing }) { // this prop is a state variable passed from parent (setter function not needed here so not passed)
const editorRef = useRef()
useEffect(() => { // if user clicks a button which sets 'editing' to true, set focus to the rich text editor component
if(editing) {
editorRef.current.focus()
}
}, [editing])
return(
<EditorConvertToHTML
innerRef={editorRef}
readOnly={!editing}
/>
)
}
EditorConvertToHTML (actual rich text editor component
import { Component } from 'react';
import { EditorState } from 'draft-js';
import { Editor } from 'react-draft-wysiwyg';
export default class EditorConvertToHTML extends Component {
state = {
editorState: EditorState.createEmpty()
}
onEditorStateChange = (editorState) => {
this.setState({
editorState,
});
};
render() {
const { editorState } = this.state;
return (
<Editor
editorRef={(ref) => this.props.innerRef.current = ref}
editorState={editorState}
onEditorStateChange={this.onEditorStateChange}
readOnly={this.props.readOnly}
/>
);
}
}
The first time that editing is set to true, ReactJS successfully focuses EditorConvertToHTML component. However, when I set it to false and then again to true, the component no longer becomes focused.
I know that the useEffect and contained if(editing) conditions are triggered each time that editing is set to true because I have added a logger in there which confirmed it's being called. I get no errors in console.
Why is focus working only the first time that editing is set to true?
If there's any other information that you'd find helpful, please let me know.
Edit:
I logged the editorRef object into the console just before the focus() method gets called and see that the object of the variable is different between the first time (when focus() works) and second time (when focus() doesn't work), where first time it's a DraftEditor object, and second time it's a different object.
Why is the object in editorRef changing and is there a way I can ensure that focus() works every time that editing is set to true?

Related

Forcefully update the component on updated value inside the render method react native

I am trying to forcefully rerender the complete component on value change inside the render function.
Below is a sample code to explain my problem. Profile component I am calling after the login and it's called and two functions inside this getting called and the value is set in the state. Now I am on another screen where I am updating mssidnNumber inside the context and that change I can see in the Profile render() function val. let val = this.context.mssidnNumber; and this val value will change on condition so on this basis I am trying to rerender the complete Profile component again so all values will update. How can I do this please help? Is that possible?
import React, { Component } from 'react';
import { View} from 'react-native';
class Profile extends Component {
constructor(props) {
super(props);
}
async componentDidMount() {
this.Profilefunction();
}
Profilefunction = () =>{
this.setState({})
await this.profile2fucntion();
}
profile2fucntion =() => {
}
render() {
let val = this.context.mssidnNumber;
return (
<View>
</View>
);
}
}
export default Profile;

defaultIndex property doesn't change after re-render cycle in "React Native Searchable Dropdown"

I'm trying to use Searchable-Dropdown in a pretty simple component like that:
import React from 'react';
import {View} from 'react-native';
import SearchableDropdown from 'react-native-searchable-dropdown';
const DropDownList = props => {
return (
<SearchableDropdown
onItemSelect={(item) => {
props.onChoosingItem(item);
}}
defaultIndex = {props.defaultIndex}
containerStyle={styles.containerDDL}
itemStyle={{...styles.itemDDL,...props.itemStyle}}
itemTextStyle={{...styles.itemDDLText, ...props.textStyle}}
itemsContainerStyle={styles.itemContainerDDL}
items={props.ddlItems}
resetValue={false}
textInputStyle={{...styles.ddlTextInput,...props.textInputStyle}}
onTextChange = { text => console.log(text)}
listProps={
{
nestedScrollEnabled: true,
}
}
/>
);
};
const styles = StyleSheet.create({
*<"Styles definitions">*
}
});
export default DropDownList;
But for some reason, props.defaultIndex property is read, and assigned to the defaultIndex property of the react-native-searchable-dropdown itself very only first render cycle. After "DropDownList" 's rendered first time, SearchableDropdown doesn't try to read this value though the whole component ("DropDownList") is rendered every time when the props change. Is it the react-native-searchable-dropdown issue, or I use it incorrectly?
I see the reason, defaultIndex property is set on componentDidMount call only (inside react-native-searchable-dropdown component),as result defaultIndex won't be changed after first time, when the component has been mounted

Throw error when creating react component conditionally

I have a react component called ListChats and another called Chat.
ListChats calls the rendering of a Chat
I need that if an error occurs when rendering the Chat, ListChats knows and comes back with the list of available chats.
A possible error, its capture has not yet been implemented, would be if the user has no name, Chat captures this error and returns to ListChat.
ListChats component:
import React, { Component } from "react";
import Chat from "../Chat";
import { Button } from 'react-bootstrap';
export default class ListChats extends Component {
constructor(props) {
super(props);
this.state = {
chat: <div />
}
this.toSala = this.toSala.bind(this);
}
toSala() {
//this is where I render a Chat component, I need that, if it returns an error, I set this.state.chat to "<div />"
this.setState({chat: <Chat/> });
}
render() {
const { chat} = this.state;
return (
<>
<Button onClick={this.toSala}>abrir chat</Button>
{chat}
</>
)
}
};
Chat component
import React,{ useState, useEffect } from 'react'
const Chat = (props) => {
const [name, setName] = useState('');
const [room, setRoom] = useState('');
const [users, setUsers] = useState([]);
useEffect(() => {
//depending on the value of a variable of mine I have the need or not to throw an error when rendering this component
}, []);
return (
<div>
my chat
</div>
);
};
export default Chat;
In short, I need to throw an error in the Chat component and catch it in ListChats component
you can do this in two ways:
first you can pass props from the parent to the child and communicate between the two components by this.set your variable in the parent component and pass that via props to the child component.in this case your child component is responsible to show whatever comes from the parent.
the second one is using a global state management like Redux,ContextApi or mobX to catch error in the child and save it to an state and then use that state in the parent component or wherever you want to use.
depend on the size of your project you can use either way.
Thanks to the responses and comments I managed to elaborate the following solution:
1- I created a function in ListChat that will receive the error message (msg) by parameter:
setError = (msg) => {
this.setState({error:msg});
}
2- I passed the function as props in rendering the chat:
this.setState({chat: <Chat setError={this.setError}/> });
3- When I need to now pass an error by the Chat for ListChat,
I call in the Chat component:
props.setError("Name is null");

Updating component's state using props with functional components

I am trying to update the state of my component using props that I get from the parent component, but I get the following error message:
Too many re-renders. React limits the number of renders to prevent an infinite loop.
I want the local state to update if the prop changes.
The similar posts (Updating component's state using props, Updating state with props on React child component, Updating component's state using props) did not fixed it for me.
import React, {useState} from "react"
const HomeWorld = (props) => {
const [planetData, setPlanetData] = useState([]);
if(props.Selected === true){
setPlanetData(props.Planet)
console.log(planetData)
}
return(
<h1>hi i am your starship, type: {planetData}</h1>
)
}
export default HomeWorld
You need to use the useEffect hook to run it only once.
import { useEffect } from 'react'
...
const HomeWorld = (props) => {
const [planetData, setPlanetData] = useState([]);
useEffect(() => {
if(props.Selected === true){
setPlanetData(props.Planet)
console.log(planetData)
}
}, [props.Selected, props.Planet, setPlanetData]) // This will only run when one of those variables change
return(
<h1>hi i am your starship, type: {planetData}</h1>
)
}
Please notice that if props.Selected or props.Planet change, it will re run the effect.
Why Do I Get This Error ?
Too many re-renders. React limits the number of renders to prevent an infinite loop.
What is happening here is that when your component renders, it runs everything in the function, calling setPlanetData wich will rerender the component, calling everything inside the function again (setPlanetData again) and making a infinite loop.
You're generally better off not updating your state with props. It generally makes the component hard to reason about and can often lead to unexpected states and stale data. Instead, I would consider something like:
const HomeWorld = (props) => {
const planetData = props.Selected
? props.Planet
//... what to display when its not selected, perhaps:
: props.PreviousPlanet
return(
<h1>hi i am your starship, type: {planetData}</h1>
)
}
This might require a bit more logic in the parent component, to control what displays when the Selected prop is false, but it's a lot more idiomatic React.

React Meteor, reactive prop and inner state

I have a component
import React, { Component } from 'react'
import { EditorState, convertToRaw } from 'draft-js'
import { Editor } from 'react-draft-wysiwyg'
import 'react-draft-wysiwyg/dist/react-draft-wysiwyg.css'
import draftToHtml from 'draftjs-to-html'
import toolbarOptions from './JpuriTextEditorOptions'
export default class TextEditor extends Component {
state = {
editorState: this.props.editorState
}
componentWillReceiveProps = nextProps => {
console.warn('componentWillReceiveProps')
console.log('nextProps.editorState', nextProps.editorState)
console.log('this.props.editorState', this.props.editorState)
this.setState({editorState: nextProps.editorState})
}
onEditorStateChange = editorState => this.setState({editorState})
onBlur = () => this.props.onBlur({key: this.props.myKey, value: draftToHtml(convertToRaw(this.state.editorState.getCurrentContent()))})
render () {
return (
<Editor
wrapperClassName={this.props.wrapperClassName}
editorClassName={this.props.editorClassName}
toolbarClassName={`toolbarAbsoluteTop ${this.props.toolbarClassName}`}
wrapperStyle={this.props.wrapperStyle}
editorStyle={this.props.editorStyle}
toolbarStyle={this.props.toolbarStyle}
editorState={this.state.editorState}
onEditorStateChange={this.onEditorStateChange}
onChange={this.props.onChange}
toolbarOnFocus
toolbar={toolbarOptions}
onFocus={this.props.onFocus}
onBlur={this.onBlur}
onTab={this.props.onTab}
/>
)
}
}
I pass to it a reactive prop, this.props.editorState
Then I set it inside the internal state to handle changes there. And only onBlur I save my changes to the mongo db.
Now there is a problem.
Whenever I click on the editor I see componentWillReceiveProps logs a few times. And it happens on every change thus I am not able to use my editor component properly. As it's cursor is being reset to the first letter with every click and change.
I am using this library of draftjs https://github.com/jpuri/react-draft-wysiwyg
EDIT
More specifics for the question.
setting the state to this.props.editorState in the constructor or in the componentDidMount is solving the issue for the initial state.
But there is still just one problem left.
In another component I have undo redo functionality that works directly with the db. Now if I type some text. Blur it my change is saved to the db and I can see the text because of the internal text. However if I click the undo button, the text will be undone from the db, however it will still be visible in the editor because of the internal state. So I will have to refresh to see my action undone.
With componentWillReceiveProps this issue is solved, however for some reason componentWillReceiveProps is being called every time the state changes even though the props are not changed. Thus bringing above mentioned issues.
If you are using the Controlled pattern of this component then you should set the state internally and not set it from the outside via props.
For example, if you set the state on each new prop received then theres no reason to use an internal state.
According to their DOCS it seems like you are following their example of controlled EditorState except you are overriding your state on each new prop.
I think if you will just remove this behavior from componentWillReceiveProps
i.e setting the state: this.setState({editorState: nextProps.editorState})
It should work like expected.
This is their example by the way:
import React, { Component } from 'react';
import { EditorState } from 'draft-js';
import { Editor } from 'react-draft-wysiwyg';
class ControlledEditor extends Component {
constructor(props) {
super(props);
this.state = {
editorState: EditorState.createEmpty(),
};
}
onEditorStateChange: Function = (editorState) => {
this.setState({
editorState,
});
};
render() {
const { editorState } = this.state;
return (
<Editor
editorState={editorState}
wrapperClassName="demo-wrapper"
editorClassName="demo-editor"
onEditorStateChange={this.onEditorStateChange}
/>
)
}
}
EDIT
A followup to your comment, state initialization is usually made in the constructor.
But when you want to initialize state asynchronously you can't and should not do it in the constructor (nor in componentWillMount) because by the time the async operation will finish the render method will already be invoked.
The best place to do that is componentDidMount or eventHandlers, so in your case the onBlur eventHandler:
import React, { Component } from 'react';
import { EditorState } from 'draft-js';
import { Editor } from 'react-draft-wysiwyg';
class ControlledEditor extends Component {
constructor(props) {
super(props);
this.state = {
editorState: EditorState.createEmpty(),
};
}
onEditorStateChange = (editorState) => {
this.setState({
editorState,
});
};
onBlur = (event) => {
// do async stuff here and update state
};
render() {
const { editorState } = this.state;
return (
<Editor
editorState={editorState}
wrapperClassName="demo-wrapper"
editorClassName="demo-editor"
onEditorStateChange={this.onEditorStateChange}
onBlur={this.onBlur}
/>
)
}
}

Categories

Resources