1

I'm building chat app using ReactJS, NestJS, Socket.io .

And it's multi channel using rooms in socket.io

const [messages, setMessages] = useState({
        ReactJS: [],
        NestJS: [],
        Typescript: [],
        MySQL: [],
        Neo4j: [],
        Redis: [],
        ELK: [],
        Docker: [],
        Kubernetes: [],
        AWS: [],
        SocketIO: [],
    });

This is array with useState for pushing message.

Question

messages['ReactJS'].push(someMessage);

How useState push element to array inside object?

6
  • messages contains no roomname property. Commented Jan 16, 2021 at 3:49
  • It is just example. Commented Jan 16, 2021 at 3:55
  • @gman I believe it is intended to be a dynamic key. Commented Jan 16, 2021 at 3:57
  • I'm sorry for misunderstanding you. I'm not native english speaker. Commented Jan 16, 2021 at 4:01
  • @Drew Reese, You can't push something into a non-existent array. And further the state is of APIs but 'roomname' is not an API. It's not a reasonable question and you should vote to close for more details. Answering will not actually help this user. BTW: your answers will fail with TypeError: messages[roomKey] is not iterable. Commented Jan 16, 2021 at 4:03

2 Answers 2

3

Given the state

const [messages, setMessages] = useState({
  ReactJS: [],
  NestJS: [],
  Typescript: [],
  MySQL: [],
  Neo4j: [],
  Redis: [],
  ELK: [],
  Docker: [],
  Kubernetes: [],
  AWS: [],
  SocketIO: [],
});

Then the following is a way to update a specific room via a roomKey identifier nested in the state object. In React when you update state you must always return a new object reference, and this includes any nested state/properties that are being updated. array.prototype.push mutates the original array, it doesn't create a new array reference for React purposes.

setMessages(messages => ({
  ...messages, // <-- shallow copy state
  // copy existing nested state array into new array and append new element
  [roomKey]: [...messages[roomKey], newMessage],
}));

An alternative to the array literal is to use array.prototype.concat, which does return a new array.

setMessages(messages => ({
  ...messages, // <-- shallow copy state
  // copy existing nested state array into new array and append new element
  [roomKey]: messages[roomKey].concat(newMessage),
}));

Note: This assumes your roomKey variable will reference one of the keys actually defined in your state. If you use an unspecified key then messages[unknownKey] will be undefined. In this case, if you've truly dynamic keys, then you can provide a fallback value to spread into state.

setMessages(messages => ({
  ...messages, // <-- shallow copy state
  // copy existing nested state array into new array and append new element
  [roomKey]: [
    ...messages[roomKey] || [], // <-- provide fallback
    newMessage,
  ],
}));
Sign up to request clarification or add additional context in comments.

5 Comments

Thanks. But do you know how set type in useState? I'm using typescript and code above show error because of type.
You need to make an interface. You can do useState<any>({ ReactJS: [], NestJS: [], Typescript: [], MySQL: [], Neo4j: [], Redis: [], ELK: [], Docker: [], Kubernetes: [], AWS: [], SocketIO: [], }) to get around it
@camlyport Unfortunately no, I'm not very familiar with Typescript. You'll need some type/interface definition, but other than that I'm a bit of a fish out of water. Perhaps you should refine your question and provide more of your requirements. Try to be as specific as possible.
that's right. but I guess, this will give error when spreading [...message[roomkey], newMessage] as message[roomkey] will return undefined initially when there is no roomKey key available in the object. Am I right??
@Rajiv If the keys used are the ones already in state, then all those state values are defined, truthy, values, i.e. an empty array to copy from and add to. I added an edit to handle unknown keys, essentially provide a fallback value.
0

If you can install some additional utilities here are some other way to do it

Ramda

rootPath = 'ReactJS'
const newArray = R.append(someMessage, messages[rootPath])
const newMessages = R.assocPath([rootPath], newArray, messages);
setMessages(newMessages)

// combined
const rootPath = 'ReactJS'
setMessages(
    R.assocPath(
        [rootPath],
        R.append(
            someMessage,
            messages[rootPath]
        ),
        messages
    )
)

Immerjs

import produce from 'immer'

const rootPath = 'ReactJS'
const newMessages = produce(messages, draftState => {
    draftState[rootPath].push = someMessage
})

setMessages(newMessages)

// combined
import p from 'immer'

const rootPath = 'ReactJS'
setMessages(p(messages, draft => {
    draft[rootPath].push = someMessage
}))

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.