Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Callback is not working in ajax request?

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)
}
like image 290
Nane Avatar asked Nov 27 '22 02:11

Nane


1 Answers

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:

  1. You have to determine if you need to clear the tempResults. If so you need to implement the logic for it at the appropriate place.
  2. To store the tempResults you can make use of technique called memoization. The above described one is a simple one.
  3. Since your results are memozied, this might be an advantage for you if the 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.
  4. The data you store in your tempResults should be the response from the api call or something from which you can determine the arguments that you need to pass to callback.
  5. I think, you might need to debounce the forceRenderEditormethod to avoid repeated update if there are many api being called for each render.
  6. Finally, i could not find a place where 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.

like image 109
Panther Avatar answered Nov 28 '22 17:11

Panther