FredrikOseberg / react-chatbot-kit

MIT License
301 stars 141 forks source link

How to render a widget according to answer, not to state. #6

Closed rugggger closed 3 years ago

rugggger commented 3 years ago

My chat bot works in the following way.

  1. parses message (async operation from server).
  2. receives an answer that contains a JSON, which sometimes contains a
    {
    "widget":"Links",
    options: [... array of links]
    }
  3. My ActionProvider sets the state with the last answer received, and then a creates a chatbot message. (sometimes with the Links widget)

Problems is - the Links widget always renders according to the last answer that is saved in the state. So If I got 2 links answers, they will both look the same. (both are updated to the latest state)

How can I set the widget instance with a specific answer, instead of having multiple instances that all look the same since they access the same state ?

FredrikOseberg commented 3 years ago

@rugggger

Hi. If I understand it correctly, you have a Links widget that receives links from an API call and should render those to the screen?

The fact that both Links component update with the same information is due to how react works. Whenever a piece of state is updated in React, all components that are children of that component will re-render, and if they depend on that state they will receive the updated state. Your problem stems from having two components accessing the same global state, and when the state changes your previous Links components are being re-rendered.

There are multiple ways you could handle this.

  1. Use multiple state pieces, and return some information from the api that decides which state to update. Then create new wrapper widgets around Link.
// Create and register widget <SomeLinks />
// Create and register widget <SomeOtherLinks /> 
// Both wrap Links component

 state: {
     someLinks: []
     someOtherLinks: []
}

// API return 
{ widget: "Links", options: [...array of links] }

// setState
this.setState(prevState => ({ ...prevState, messages: [...prevState.messages, message], "someLinks": apiResponse.options }))
  1. Return an endpoint URL from your API to call and update local state in your Links component

  2. If you use class components, use the lifecycle method shouldComponentUpdate to return false if you are certain this component should never update. Or implement it with hooks: https://reactjs.org/docs/hooks-faq.html#how-do-i-implement-shouldcomponentupdate

And there are more ways to handle this, I'm sure, but these are the ones I could think about right now.

rugggger commented 3 years ago

Hi @FredrikOseberg , thanks for your quick answer, and really great chatbot package.

Let's say I have a <Links/> widget, and I return it in my chatbot response multiple times during the conversation. I would like my chatbot history to include all the past responses, including the rendered <Links/> widget for a specific response, not for the current state.

I have no way to know ahead of time how many <Links/> widgets I'll have and what data I want to send to them. And they are all rendered as one, aren't they ? The part of updating the state, and handling the API requests is already handled by me. My problem is that I have no way of showing the same <Links/> widget in my chat history, with different rendered results due to the different data I pass it. Maybe it can be solved if the widget can receive in its props also the message that it responded to ?

I need a way to create different <Links/> widgets instances and that the rendered result would appear on the chatbot history at the same time, and yet contain different information.

I hope my explanation was clear.

FredrikOseberg commented 3 years ago

Yeah. To do that you need to break the global state connection to the Links component and implement one of the solutions I mentioned. I have already done something similar to what you mention here: https://github.com/FredrikOseberg/react-chatbot-kit-docs/blob/master/src/bots/skybot/widgets/FlightList/FlightList.jsx

Here I use local state to handle the data, which means that the data will be localized to that component. Even if you render multiple ones in the chat.

rugggger commented 3 years ago

Thank you, I will look at your link.