Add element to a state React
Asked Answered
T

3

6

I already have a state with this:

 this.setState({
      conversation:
          (
            <div>
              {conversation.map(element => {
                 if (element.id === this.props.id) {
                      return(
                        <div className="row msg_container base_sent">
                          <div className="col-md-10 col-xs-10">
                             <div className="messages msg_sent">
                                 <p>{element.message}</p>
                             </div>
                             </div>
                        </div>
                   )
             }
              else {
                 return(
                     <div className="row msg_container base_receive">
                        <div className="col-md-10 col-xs-10">
                          <div className="messages msg_receive">
                              <p>{element.message}</p>
                          </div>
                      </div>
                      </div>
                )
               }
             })}
           </div>
          )
       })

Now I would like to update it with new information. So add another div to it.

Something like that:

this.setState({conversation: previousConversation + new div})

How can I do it? Or I need to set a new state from zero

Thumping answered 18/12, 2017 at 19:24 Comment(2)
storing jsx in the state does not seem like a good idea. Actually, I have never seen anybody doing that before...Vanillic
That shouldn't be a problem. I think what you want is a mapping (consider importing lodash library and using _.map([1,2,3], function(x) {...});) so when its state changes react takes care of the adding messages on its ownFierro
S
6

I don't think it's a good idea to store jsx components in the state of a component. I think you should only save the data in the state needed to render the component.

If you really want to store jsx in the state, why won't you define your 'conversation' property as array? Then you'll be able to add new components to it.

this.setState({
  conversation: [
        (<div>first</div)
  ]      
});

const currentConversation = state.conversation;
currentConversation.push((<div>new div</div>));
this.setState({conversation: currentConversation})

But better to only store the data ie 'first' and 'new div'

Sudden answered 18/12, 2017 at 19:34 Comment(4)
When I send a message to my server, it's useless for my opinion to do a call and take the new conversation. So I decided to add a new message to an existing state. And when I refresh the page make the call that grabs the new conversation. I think that's work and help users to see a real-time chat. I don't know is the the best solutionThumping
That actually works fine! But I don't know if is the best approach. Considering I'm using socket for the new Messages in real-timeThumping
If it works in your application, it's fine :). But as pointed before and by MEnf I think its better to only store the data in the state, regardless whether you use a socket or not. It will make your maintenance easier. If you want your users to see a real time chat you also consider implementing optimistic response for your service callsSudden
It'll seem to work well as long as it's just simple HTML elements used in the state. As soon as a custom React element is used, its lifecycle will be all messed up. So, rule of thumb, never put JSX in the state, or even outside the render cycle.Ecstatic
I
17

As per React docs (webarchive source):

What Should Go in State?

State should contain data that a component's event handlers may change to trigger a UI update. In real apps this data tends to be very small and JSON-serializable. When building a stateful component, think about the minimal possible representation of its state, and only store those properties in this.state. Inside of render() simply compute any other information you need based on this state. You'll find that thinking about and writing applications in this way tends to lead to the most correct application, since adding redundant or computed values to state means that you need to explicitly keep them in sync rather than rely on React computing them for you.

What Shouldn’t Go in State?

this.state should only contain the minimal amount of data needed to represent your UI's state. As such, it should not contain:

  • Computed data: Don't worry about precomputing values based on state — it's easier to ensure that your UI is consistent if you do all computation within render(). For example, if you have an array of list items in state and you want to render the count as a string, simply render this.state.listItems.length + ' list items' in your render() method rather than storing it on state.
  • React components: Build them in render() based on underlying props and state.
  • Duplicated data from props: Try to use props as the source of truth where possible. One valid use to store props in state is to be able to know its previous values, because props can change over time.

In your case, move your html into your render function. Then store a condition or data in your state that would be used to trigger the addition of another div to your html either by conditionally rendering some html or by adding another div to an array inside a .map function based on adding more data to your state.

Example:

Class Example render React.Component{
  state = {
    comments: [
      { message:"comment 1", id: 1, timeStamp: "" },
      { message:"comment 2", id: 2, timeStamp: "" },
      { message:"comment 3", id: 3, timeStamp: "" }
    ]
  }

  componentDidMount = () => {
    //update data from api...
    .then(data => this.setState({ comments: data }))
  }

  render(){
    conversation.map(element => {
      if (element.id === this.props.id) {
        return(
          <div className="row msg_container base_sent">
            <div className="col-md-10 col-xs-10">
              <div className="messages msg_sent">
                <p>{element.message}</p>
              </div>
            </div>
          </div>
        )
      }
      else {
        return(
          <div className="row msg_container base_receive">
            <div className="col-md-10 col-xs-10">
              <div className="messages msg_receive">
                <p>{element.message}</p>
              </div>
            </div>
          </div>
        )
      }
    })
  }
}
Invoice answered 18/12, 2017 at 19:33 Comment(6)
Can you give an example?Thumping
Added an example. I don't know the context of your application, so I'm just assuming your getting new data from an api callInvoice
I have something like that but I need a real-time feedback. I receive new message in real time with socket and this is the real reason about my questionThumping
I would suggest using something like the socket.io-client library to handle real-time feedback between sockets. Try looking through this article to figure out how to do so. medium.com/dailyjs/… Here's the npm for the library npmjs.com/package/socket.io-clientInvoice
Not a fan at all of answers like this that say what you should do without saying anything about why you should do it. I have no way of knowing whether or not your answer is a good one if you don't explain your reasoning.Jewish
Would you mind providing some clarification? My answer provides reasoning directly from the React docs.Invoice
S
6

I don't think it's a good idea to store jsx components in the state of a component. I think you should only save the data in the state needed to render the component.

If you really want to store jsx in the state, why won't you define your 'conversation' property as array? Then you'll be able to add new components to it.

this.setState({
  conversation: [
        (<div>first</div)
  ]      
});

const currentConversation = state.conversation;
currentConversation.push((<div>new div</div>));
this.setState({conversation: currentConversation})

But better to only store the data ie 'first' and 'new div'

Sudden answered 18/12, 2017 at 19:34 Comment(4)
When I send a message to my server, it's useless for my opinion to do a call and take the new conversation. So I decided to add a new message to an existing state. And when I refresh the page make the call that grabs the new conversation. I think that's work and help users to see a real-time chat. I don't know is the the best solutionThumping
That actually works fine! But I don't know if is the best approach. Considering I'm using socket for the new Messages in real-timeThumping
If it works in your application, it's fine :). But as pointed before and by MEnf I think its better to only store the data in the state, regardless whether you use a socket or not. It will make your maintenance easier. If you want your users to see a real time chat you also consider implementing optimistic response for your service callsSudden
It'll seem to work well as long as it's just simple HTML elements used in the state. As soon as a custom React element is used, its lifecycle will be all messed up. So, rule of thumb, never put JSX in the state, or even outside the render cycle.Ecstatic
E
0

just because you can doesn't mean you should

While storing a JSX element in state is possible, there are serious pitfalls to watch out for. There's often a better way to meet your needs without incurring these hazards and therefore considered an anti-pattern. See the following demonstration of such a trap -

function App() {
  const [state, setState] = React.useState(<Counter />)
  
  return <div>
    {state} click the counter ✅
    <hr />
    <button onClick={() => setState(<Counter />)} children="A" />
    <button onClick={() => setState(<Counter init={10} />)} children="B" />
    <button onClick={() => setState(<Counter init={100} />)} children="C" />
    change the counter ❌
  </div>
}

function Counter(props) {
  const [state, setState] = React.useState(props.init || 0)
  return <button onClick={() => setState(_ => _ + 1)} children={state} />
}

ReactDOM.createRoot(document.querySelector("#app")).render(<App />)
<script crossorigin src="https://unpkg.com/react@18/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@18/umd/react-dom.development.js"></script>
<div id="app"></div>

The program runs, but changing from one component state to another does not work as we might expect. React detects that state in App changed, but when {state} is rendered, React doesn't know that is a new Counter component, as distinct from any other. So the same counter renders and the state stays the same.

This demonstration was originally provided in this Q&A, where you can explore the issue further and see conventional patterns that avoid storing JSX in state.

Eleonoraeleonore answered 27/9 at 17:5 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.