When component is mounted the channel is either grabbed from existing or created a new. This channel is later saved in the state for persist-ency in component. When the component is clicked all the channel messages are updated to consumed.
The Code belongs to the 'Today's Shift Channel' column. The Right side code is separate.
Senario : When I click a User onTaskSelect is called which triggers the whole user chat in right side after setting all the messages consumed. here problem 1 and 2 occurs. Now I click another user and click back to previously clicked user problem 3 occurs
Problems :
1) It does not update the consumed messages and always return zero.
2) It stops receiving the new messages from the .on listener called in joinOrCreate function.
3) on recurrent click response with error
SyncError: Access forbidden for identity (status: 403, code: 54007)
at mapTransportError (http://localhost:3000/static/js/0.chunk.js:162153:12)
at http://localhost:3000/static/js/0.chunk.js:162210:20
NOTE : Channels are set once and never left unless page refreshes
NOTE : The whole chat on right side is working fine and is in separate module.
NOTE : The Client was initialized at the start of application as context which persists for the whole lifecycle of application from opening to closing.
const TaskCard = props => {
const [lastMessage, setLastMessage] = useState({})
const [open, setOpen] = useState(false)
const [unread, setUnread] = useState(0)
const [channel, setChannel] = useState({})
const joinOrCreate = useCallback(async() => {
try{
const fetchedChannel = await props.client.getChannelByUniqueName(props.task.id.toString())
let joined = fetchedChannel
if(fetchedChannel.state.status !== "joined") {
joined = await fetchedChannel.join()
}
console.log()
joined.getMessages()
.then(messages => {
if(messages.items.length > 0) {
if(joined.lastConsumedMessageIndex < messages.items.length - 1) {
setUnread(true)
}
const recent_message = messages.items[messages.items.length - 1]
const limit_message = recent_message.body.slice(0,14)
setLastMessage({
body: limit_message.length === 14? (limit_message + '...') : limit_message,
time: recent_message.timestamp,
index: recent_message.index,
})
}
})
joined.on('messageAdded', messageUpdated)
setChannel(joined)
}
catch(ch) {
console.log(ch)
try{
const newChannel = await props.client.createChannel({
uniqueName: props.task.id.toString(),
friendlyName: 'General Chat Channel'
})
let joined = newChannel
if(newChannel.state.status !== "joined") {
joined = await newChannel.join()
}
joined.getMessages()
.then(messages => {
if(messages.items.length > 0) {
const recent_message = messages.items[messages.items.length - 1]
setLastMessage({
body: recent_message.body,
time: recent_message.timestamp,
})
}
})
joined.on('messageAdded', messageUpdated)
setChannel(joined)
}
catch(e) {
console.log(e)
}
}
}, [props.client, props.task.id])
const messageUpdated = message => {
const limit_message = message.body.slice(0,14)
setLastMessage({
body: limit_message.length === 14? (limit_message + '...') : limit_message,
time: message.timestamp,
index: message.index
})
}
const onTaskSelect = () => {
// console.log(lastMessage.index)
console.log(channel.uniqueName)
if(lastMessage.body) {
channel.updateLastConsumedMessageIndex(+lastMessage.index)
.then(res => {
console.log(res)
})
.catch(e => {
// console.log(props.client)
// console.log(channel)
console.log(e)
})
}
props.onTaskClick(props.task.id.toString())
}
useEffect(() => {
if(props.channelId === props.task.id) {
setOpen(true)
setUnread(false)
}
else {
setOpen(false)
}
}, [props.channelId, props.task.id])
useEffect(() => {
joinOrCreate()
}, [joinOrCreate])
useEffect(() => {
if(channel.lastConsumedMessageIndex < lastMessage.index && !open) {
setUnread(true)
}
}, [channel.lastConsumedMessageIndex, lastMessage, open])
return (
<Row key={props.task.id} >
<Col className='justify-center'>
<Card className={'more-than-90 ' + (open? 'background-active' : null)}
onClick={e => onTaskSelect()}
>
<Row>
<Col md={2} style={{alignSelf: 'center', paddingLeft:'15px'}}>
{
props.task.worker.pic_url === "" || props.task.worker.pic_url === null ?
<div className="name-image">
{props.task.worker.first_name[0] + props.task.worker.last_name[0]}
</div>
:
<Image width={50} height={50} src={props.task.worker.pic_url} roundedCircle />
}
</Col>
<Col md={10}>
<Row>
<Col md={8}>
<p style={{fontSize:'.9rem'}}>{props.task.worker.name}</p>
</Col>
<Col>
<p style={{fontSize:'.7rem'}} className='left-align-text'>{lastMessage.time? moment(lastMessage.time).format('hh:mm A') : null}</p>
</Col>
</Row>
<Row>
<Col md={8}>
<p style={{fontSize:'.7rem'}}>{lastMessage.body? lastMessage.body : null}</p>
</Col>
<Col>
{
unread ?
<FontAwesomeIcon
icon={faEnvelopeOpenText}
size="lg"
color={"#0064bb"}
/>
:
null
}
</Col>
</Row>
</Col>
</Row>
</Card>
</Col>
</Row>
)
}
Twilio developer evangelist here.
I think Will Sams has a good point in their comment. You do need to set an unread index to start with for a channel to then have a valid unread index. From the docs:
So, when creating a channel, I would set the channel's messages to unconsumed to kick off that measure. You can also use the function
setAllMessagesConsumed
rather than having to read the index of the last message.I also noticed that when you set the last message you miss setting its index at one point:
which could cause trouble.
I am concerned about the way your callbacks and effects are set up. When you trigger
onTaskSelect
it looks as though you send the task ID back to the parent.Presumably this is so you can see all the messages in the main panel.
However, I also assume this sets a task in the parent, which is then passed as
props.tasks
to this child. ThejoinOrCreate
callback is set to update whenprops.task.id
is updated anduseEffect
is predicated onjoinOrCreate
changing. So my guess is that whenever you change between tasks you trigger the cleanup and re-evaluation ofjoinOrCreate
. Except there is no cleanup function, so you end up reloading the channel, for each of these components, each time. I would guess that had something to do with breaking the events and the eventual errors you get when you click again.Instead, I would probably re-architect this so that the
TaskCard
component doesn't control the lifecycle of the channel object. I would load and join the list of channels you intend to render in a parent component and then pass the channel object to theTaskCard
. That would simplify the components as the parent would deal with the data and theTaskCard
could handle just rendering. It would also mean that when the status of a card is changed (from showing to not showing) then you are only re-rendering the data. This way, you could also share the channel object between theTaskCard
and the chat view on the right of your screenshot (otherwise I'm guessing you have to load it in that component too, and that's probably not helping either).These are just some ideas based on what I can see in your application. I hope they help!