I am trying to build a contentEditor with draft js. Exactly the feature is Extract the data from url like Facebook. But I am stuck with this part. Callback is not working.
First I wrapped my state with compositeDecorator
Like this
constructor(props) {
super(props);
const compositeDecorator = new CompositeDecorator([
.... {
strategy: linkStrategy,
component: decorateComponentWithProps(linkComp, {
passit
})
}
....
]);
}
// This is my strategy
function linkStrategy(contentBlock, callback, contentState) {
findLinkInText(LINK_REGEX, contentBlock, callback)
}
function findLinkInText(regex, contentBlock, callback) {
const text = contentBlock.getText();
let matchArr, start;
if ((matchArr = regex.exec(text)) !== null) {
start = matchArr.index;
let URL = matchArr[0];
console.log(URL);
axios.post('/url', {
url: URL
}).then(response => {
passit = response.data
//not working
callback(start, start + URL.length)
})
//working
callback(start, start + URL.length)
}
}
If the callback won't work, the component will not render.. I don't know this is a basic javascript problem. But the thing is I want to fetch the url data from my server and I have to pass the data via props to my component and render it.
UPDATE FOR THE ANSWER
function findLinkInText(regex, contentBlock, callback) {
const text = contentBlock.getText();
let matchArr, start;
if ((matchArr = regex.exec(text)) !== null) {
start = matchArr.index;
let url = matchArr[0];
axios.post('/url', {
url: URL
}).then(response => {
passit = response.data
handleWithAxiosCallBack(start, start + matchArr[0].length, callback)
}).catch(err => console.log(err))
}
}
function handleWithAxiosCallBack(start, startLength, callback) {
console.log(callback); //Spits out the function But Not working
callback(start, startLength)
}
The below described technique would help you to achieve the behaviour you are expecting.
Why your solution does not works: The reason the desired action that needs to be performed by the callback
is not performing is because, draft
expects the callback
to be called synchronous. Since you are using an async
function(the axios api call) and asynchronously calling the callback
has no effect.
Solution: This may not be a efficient solution, but could get the job done. In simple words, all you have to do is to store the results from the axios
call in a variable(temporarily) and then trigger the re-render
for your editor
, retrieve the result store earlier and use it to call the callback.
I'm following based on this example here. Assuming that you store the editor state in the component's state. Below is a pseudo-code which you might need to implement as per your needs.
Let's assume your component state is like below which holds the Editor
's state.
constructor(props){
super(props);
// .. your composite decorators
// this is your component state holding editors state
this.state = {
editorState: EditorState.createWithContent(..)
}
// use this to temporarily store the results from axios.
this.tempResults = {};
}
Assuming you are rendering your Editor
to something like below. Notice the ref
. Here the editors reference is stored in the component's editor
variable which you can access later. Using a string as ref would work, but this is the recommended way to store refs.
<Editor
ref={ (editor) => this.editor }
editorState={this.state.editorState}
onChange={this.onChange}
// ...your props
/>
In your component, write a function to update the editor with currentState which would force the re-render
. Make sure this function is bound to your component so that we get the correct this
(context).
forceRenderEditor = () => {
this.editor.update(this.state.editorState);
}
In your findLinkInText
function do the below.
First make sure it(findLinkInText) is bound to your component so we get the correct this
. You can use arrow function to do this or bind it in the component constructor.
Second, check if we have the result for the url
already in tempResults
which we declared in the component's constructor. If we have one, then call the callback immediately with appropriate arguments.
If we don't have a result already, the make the call and store the result in the tempResults
. After storing, call the already defined this.forceRenderEditor
method which would invoke draft to re-check and this time, since we have stored the results in the tempResults
, the callback will be called and appropriate changes would reflect.
function findLinkInText(regex, contentBlock, callback) {
const text = contentBlock.getText();
let matchArr, start;
if ((matchArr = regex.exec(text)) !== null) {
start = matchArr.index;
let URL = matchArr[0];
console.log(URL);
// do we have the result already,??
// call the callback based on the result.
if(this.tempResults[url]) {
// make the computations and call the callback() with necessary args
} else {
// if we don't have a result, call the api
axios.post('/url', {
url: URL
}).then(response => {
this.tempResults[url] = response.data;
this.forceRenderEditor();
// store the result in this.tempResults and
// then call the this.forceRenderEditor
// You might need to debounce the forceRenderEditor function
})
}
}
}
Note:
memoization
. The above described one is a simple one.axios api
call results are not gonna be changing for the same input. You might not have to hit the api again for the same query.callback
.debounce
the forceRenderEditor
method to avoid repeated update if there are many api being called for each render.draft
uses or suggests for an async
callback. You might have to check with the library's team if they support/need such a feature. (If needed make the changes and raise a PR if their team is ok with one.)Update
To bind you can move the functions within the component and write it the below manner.
linkStrategy = (contentBlock, callback, contentState) => {
this.findLinkInText(LINK_REGEX, contentBlock, callback)
}
findLinkInText = (...args) => {
}
And in your constructor you can call it like this
const compositeDecorator = new CompositeDecorator([
.... {
strategy: this.linkStrategy,
component: decorateComponentWithProps(linkComp, {
passit
})
}
....
]);
}
Or if you want to reuse the function across multiple components then you can bind it in the below manner. But make sure to use the same state
in all of the sharing components(or use callback to define custom states)
Your constructor will be like
const compositeDecorator = new CompositeDecorator([
.... {
strategy: linkStrategy.bind(this),
component: decorateComponentWithProps(linkComp, {
passit
})
}
....
]);
}
Your link strategy will be like this
function linkStrategy(contentBlock, callback, contentState) {
findLinkInText.call(this,LINK_REGEX, contentBlock, callback);
}
You can use either of the above method to bind your functions.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With