Children prop triggering re-render when using map and redux










1















I am using Redux to create a quiz app that includes a form with some nested fields. I have just realized (I think) that every key press to my input fields triggers a re-render if I use the children prop, i.e. designing the app like this:



const keys = Object.keys(state)

<QuizContainer>
keys.map(key =>
<QuizForm key=key>
state[key].questions.map(( questionId ) =>
<Question key=questionId questionId=questionId>
state[key]questions[questionId].answers.map(( answerId )=>
<Answer answerId=answerId key=answerId />
)
</Question>
)
</QuizForm>
)
</QuizContainer>


QuizContainer is connected to redux with mapStateToProps and mapDispatchToProps and spits out an array of arrays that all have objects inside them. The store structure is designed according to Dan Abramov's "guide to redux nesting" (using the store kind of like a relational database) and could be described like this.



{
['quizId']:
questions: ['questionId1'],
['questionId1']:
question: 'some question',
answers: ['answerId1', 'answerId2']
,
['answerId1']: some: 'answer',
['answerId2']: some: 'other answer'



The code above works in terms of everything being updated etc, etc, no errors but it triggers an insane amount of re-renders, but ONLY if I use the composition syntax. If I put each component inside another (i.e. not using props.children) and just send the quizId-key (and other id-numbers as props) it works as expected - no crazy re-rendering. To be crystal clear, it works when I do something like this:



// quizId and questionId being passed from parent component's map-function (QuizForm)

const Question ( answers ) =>
<>
answers.map(( answerId ) =>
<Answer key=answerId answerId=answerId />
)
</>

const mapStateToProps = (state, questionId, quizId ) => (
answers: state[quizId][questionId].answers
)

export default connect(mapStateToProps)(Question)


But WHY? What is the difference between the two? I realize that one of them is passed as a prop to the parent instead than being rendered as the child of that very parent, so to speak, but why does that give a different result in the end? Isn't the point that they should be equal but allow for better syntax?



Edit: I can now verify that the children prop is causing the problem. Setting



shouldComponentUpdate(nextProps) 
if (nextProps.children.length === this.props.children.length)
return false
else
return true




fixes the problem. However, seems like a pretty black-box solution, not really sure what I am missing out on right now...










share|improve this question




























    1















    I am using Redux to create a quiz app that includes a form with some nested fields. I have just realized (I think) that every key press to my input fields triggers a re-render if I use the children prop, i.e. designing the app like this:



    const keys = Object.keys(state)

    <QuizContainer>
    keys.map(key =>
    <QuizForm key=key>
    state[key].questions.map(( questionId ) =>
    <Question key=questionId questionId=questionId>
    state[key]questions[questionId].answers.map(( answerId )=>
    <Answer answerId=answerId key=answerId />
    )
    </Question>
    )
    </QuizForm>
    )
    </QuizContainer>


    QuizContainer is connected to redux with mapStateToProps and mapDispatchToProps and spits out an array of arrays that all have objects inside them. The store structure is designed according to Dan Abramov's "guide to redux nesting" (using the store kind of like a relational database) and could be described like this.



    {
    ['quizId']:
    questions: ['questionId1'],
    ['questionId1']:
    question: 'some question',
    answers: ['answerId1', 'answerId2']
    ,
    ['answerId1']: some: 'answer',
    ['answerId2']: some: 'other answer'



    The code above works in terms of everything being updated etc, etc, no errors but it triggers an insane amount of re-renders, but ONLY if I use the composition syntax. If I put each component inside another (i.e. not using props.children) and just send the quizId-key (and other id-numbers as props) it works as expected - no crazy re-rendering. To be crystal clear, it works when I do something like this:



    // quizId and questionId being passed from parent component's map-function (QuizForm)

    const Question ( answers ) =>
    <>
    answers.map(( answerId ) =>
    <Answer key=answerId answerId=answerId />
    )
    </>

    const mapStateToProps = (state, questionId, quizId ) => (
    answers: state[quizId][questionId].answers
    )

    export default connect(mapStateToProps)(Question)


    But WHY? What is the difference between the two? I realize that one of them is passed as a prop to the parent instead than being rendered as the child of that very parent, so to speak, but why does that give a different result in the end? Isn't the point that they should be equal but allow for better syntax?



    Edit: I can now verify that the children prop is causing the problem. Setting



    shouldComponentUpdate(nextProps) 
    if (nextProps.children.length === this.props.children.length)
    return false
    else
    return true




    fixes the problem. However, seems like a pretty black-box solution, not really sure what I am missing out on right now...










    share|improve this question


























      1












      1








      1








      I am using Redux to create a quiz app that includes a form with some nested fields. I have just realized (I think) that every key press to my input fields triggers a re-render if I use the children prop, i.e. designing the app like this:



      const keys = Object.keys(state)

      <QuizContainer>
      keys.map(key =>
      <QuizForm key=key>
      state[key].questions.map(( questionId ) =>
      <Question key=questionId questionId=questionId>
      state[key]questions[questionId].answers.map(( answerId )=>
      <Answer answerId=answerId key=answerId />
      )
      </Question>
      )
      </QuizForm>
      )
      </QuizContainer>


      QuizContainer is connected to redux with mapStateToProps and mapDispatchToProps and spits out an array of arrays that all have objects inside them. The store structure is designed according to Dan Abramov's "guide to redux nesting" (using the store kind of like a relational database) and could be described like this.



      {
      ['quizId']:
      questions: ['questionId1'],
      ['questionId1']:
      question: 'some question',
      answers: ['answerId1', 'answerId2']
      ,
      ['answerId1']: some: 'answer',
      ['answerId2']: some: 'other answer'



      The code above works in terms of everything being updated etc, etc, no errors but it triggers an insane amount of re-renders, but ONLY if I use the composition syntax. If I put each component inside another (i.e. not using props.children) and just send the quizId-key (and other id-numbers as props) it works as expected - no crazy re-rendering. To be crystal clear, it works when I do something like this:



      // quizId and questionId being passed from parent component's map-function (QuizForm)

      const Question ( answers ) =>
      <>
      answers.map(( answerId ) =>
      <Answer key=answerId answerId=answerId />
      )
      </>

      const mapStateToProps = (state, questionId, quizId ) => (
      answers: state[quizId][questionId].answers
      )

      export default connect(mapStateToProps)(Question)


      But WHY? What is the difference between the two? I realize that one of them is passed as a prop to the parent instead than being rendered as the child of that very parent, so to speak, but why does that give a different result in the end? Isn't the point that they should be equal but allow for better syntax?



      Edit: I can now verify that the children prop is causing the problem. Setting



      shouldComponentUpdate(nextProps) 
      if (nextProps.children.length === this.props.children.length)
      return false
      else
      return true




      fixes the problem. However, seems like a pretty black-box solution, not really sure what I am missing out on right now...










      share|improve this question
















      I am using Redux to create a quiz app that includes a form with some nested fields. I have just realized (I think) that every key press to my input fields triggers a re-render if I use the children prop, i.e. designing the app like this:



      const keys = Object.keys(state)

      <QuizContainer>
      keys.map(key =>
      <QuizForm key=key>
      state[key].questions.map(( questionId ) =>
      <Question key=questionId questionId=questionId>
      state[key]questions[questionId].answers.map(( answerId )=>
      <Answer answerId=answerId key=answerId />
      )
      </Question>
      )
      </QuizForm>
      )
      </QuizContainer>


      QuizContainer is connected to redux with mapStateToProps and mapDispatchToProps and spits out an array of arrays that all have objects inside them. The store structure is designed according to Dan Abramov's "guide to redux nesting" (using the store kind of like a relational database) and could be described like this.



      {
      ['quizId']:
      questions: ['questionId1'],
      ['questionId1']:
      question: 'some question',
      answers: ['answerId1', 'answerId2']
      ,
      ['answerId1']: some: 'answer',
      ['answerId2']: some: 'other answer'



      The code above works in terms of everything being updated etc, etc, no errors but it triggers an insane amount of re-renders, but ONLY if I use the composition syntax. If I put each component inside another (i.e. not using props.children) and just send the quizId-key (and other id-numbers as props) it works as expected - no crazy re-rendering. To be crystal clear, it works when I do something like this:



      // quizId and questionId being passed from parent component's map-function (QuizForm)

      const Question ( answers ) =>
      <>
      answers.map(( answerId ) =>
      <Answer key=answerId answerId=answerId />
      )
      </>

      const mapStateToProps = (state, questionId, quizId ) => (
      answers: state[quizId][questionId].answers
      )

      export default connect(mapStateToProps)(Question)


      But WHY? What is the difference between the two? I realize that one of them is passed as a prop to the parent instead than being rendered as the child of that very parent, so to speak, but why does that give a different result in the end? Isn't the point that they should be equal but allow for better syntax?



      Edit: I can now verify that the children prop is causing the problem. Setting



      shouldComponentUpdate(nextProps) 
      if (nextProps.children.length === this.props.children.length)
      return false
      else
      return true




      fixes the problem. However, seems like a pretty black-box solution, not really sure what I am missing out on right now...







      javascript reactjs redux






      share|improve this question















      share|improve this question













      share|improve this question




      share|improve this question








      edited Nov 13 '18 at 13:19







      sugartix

















      asked Nov 13 '18 at 12:54









      sugartixsugartix

      407




      407






















          2 Answers
          2






          active

          oldest

          votes


















          0














          When you use the render prop pattern you are effectively declaring a new function each time the component renders.
          So any shallow comparison between props.children will fail. This is a known drawback to the pattern, and your 'black-box' solution is a valid one.






          share|improve this answer






























            0














            Okay so I figured it out:



            I had done two bad things:



            1. I had my top component connected to state like so: mapStateToProps(state) => (keys: Object.keys(state)). I thought the object function would return a "static" array and prevent me from listening to the entire state but turns out I was wrong. Obviously (to me now), every time I changed the state I got a fresh array (but with the same entries). I now store them once on a completely separate property called quizIds.



            2. I put my map-function in a bad place. I now keep render the QuizContainer like so:



               <QuizContainer>
              quizIds.map(quizId =>
              <QuizForm>
              <Question>
              <Answer />
              </Question>
              </QuizForm>
              )
              </QuizContainer>


            And then I render my arrays of children, injecting props for them to be able to use connect individually like so:



             questions.map((questionId, index) => (
            <React.Fragment key=questionId>
            React.cloneElement(this.props.children,
            index,
            questionId,
            quizId
            )
            </React.Fragment>
            ))


            That last piece of code will not work if you decide to put several elements as children. Anyway, looks cleaner and works better now! :D






            share|improve this answer
























              Your Answer






              StackExchange.ifUsing("editor", function ()
              StackExchange.using("externalEditor", function ()
              StackExchange.using("snippets", function ()
              StackExchange.snippets.init();
              );
              );
              , "code-snippets");

              StackExchange.ready(function()
              var channelOptions =
              tags: "".split(" "),
              id: "1"
              ;
              initTagRenderer("".split(" "), "".split(" "), channelOptions);

              StackExchange.using("externalEditor", function()
              // Have to fire editor after snippets, if snippets enabled
              if (StackExchange.settings.snippets.snippetsEnabled)
              StackExchange.using("snippets", function()
              createEditor();
              );

              else
              createEditor();

              );

              function createEditor()
              StackExchange.prepareEditor(
              heartbeatType: 'answer',
              autoActivateHeartbeat: false,
              convertImagesToLinks: true,
              noModals: true,
              showLowRepImageUploadWarning: true,
              reputationToPostImages: 10,
              bindNavPrevention: true,
              postfix: "",
              imageUploader:
              brandingHtml: "Powered by u003ca class="icon-imgur-white" href="https://imgur.com/"u003eu003c/au003e",
              contentPolicyHtml: "User contributions licensed under u003ca href="https://creativecommons.org/licenses/by-sa/3.0/"u003ecc by-sa 3.0 with attribution requiredu003c/au003e u003ca href="https://stackoverflow.com/legal/content-policy"u003e(content policy)u003c/au003e",
              allowUrls: true
              ,
              onDemand: true,
              discardSelector: ".discard-answer"
              ,immediatelyShowMarkdownHelp:true
              );



              );













              draft saved

              draft discarded


















              StackExchange.ready(
              function ()
              StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f53281471%2fchildren-prop-triggering-re-render-when-using-map-and-redux%23new-answer', 'question_page');

              );

              Post as a guest















              Required, but never shown

























              2 Answers
              2






              active

              oldest

              votes








              2 Answers
              2






              active

              oldest

              votes









              active

              oldest

              votes






              active

              oldest

              votes









              0














              When you use the render prop pattern you are effectively declaring a new function each time the component renders.
              So any shallow comparison between props.children will fail. This is a known drawback to the pattern, and your 'black-box' solution is a valid one.






              share|improve this answer



























                0














                When you use the render prop pattern you are effectively declaring a new function each time the component renders.
                So any shallow comparison between props.children will fail. This is a known drawback to the pattern, and your 'black-box' solution is a valid one.






                share|improve this answer

























                  0












                  0








                  0







                  When you use the render prop pattern you are effectively declaring a new function each time the component renders.
                  So any shallow comparison between props.children will fail. This is a known drawback to the pattern, and your 'black-box' solution is a valid one.






                  share|improve this answer













                  When you use the render prop pattern you are effectively declaring a new function each time the component renders.
                  So any shallow comparison between props.children will fail. This is a known drawback to the pattern, and your 'black-box' solution is a valid one.







                  share|improve this answer












                  share|improve this answer



                  share|improve this answer










                  answered Nov 13 '18 at 15:51









                  user195257user195257

                  1,04942042




                  1,04942042























                      0














                      Okay so I figured it out:



                      I had done two bad things:



                      1. I had my top component connected to state like so: mapStateToProps(state) => (keys: Object.keys(state)). I thought the object function would return a "static" array and prevent me from listening to the entire state but turns out I was wrong. Obviously (to me now), every time I changed the state I got a fresh array (but with the same entries). I now store them once on a completely separate property called quizIds.



                      2. I put my map-function in a bad place. I now keep render the QuizContainer like so:



                         <QuizContainer>
                        quizIds.map(quizId =>
                        <QuizForm>
                        <Question>
                        <Answer />
                        </Question>
                        </QuizForm>
                        )
                        </QuizContainer>


                      And then I render my arrays of children, injecting props for them to be able to use connect individually like so:



                       questions.map((questionId, index) => (
                      <React.Fragment key=questionId>
                      React.cloneElement(this.props.children,
                      index,
                      questionId,
                      quizId
                      )
                      </React.Fragment>
                      ))


                      That last piece of code will not work if you decide to put several elements as children. Anyway, looks cleaner and works better now! :D






                      share|improve this answer





























                        0














                        Okay so I figured it out:



                        I had done two bad things:



                        1. I had my top component connected to state like so: mapStateToProps(state) => (keys: Object.keys(state)). I thought the object function would return a "static" array and prevent me from listening to the entire state but turns out I was wrong. Obviously (to me now), every time I changed the state I got a fresh array (but with the same entries). I now store them once on a completely separate property called quizIds.



                        2. I put my map-function in a bad place. I now keep render the QuizContainer like so:



                           <QuizContainer>
                          quizIds.map(quizId =>
                          <QuizForm>
                          <Question>
                          <Answer />
                          </Question>
                          </QuizForm>
                          )
                          </QuizContainer>


                        And then I render my arrays of children, injecting props for them to be able to use connect individually like so:



                         questions.map((questionId, index) => (
                        <React.Fragment key=questionId>
                        React.cloneElement(this.props.children,
                        index,
                        questionId,
                        quizId
                        )
                        </React.Fragment>
                        ))


                        That last piece of code will not work if you decide to put several elements as children. Anyway, looks cleaner and works better now! :D






                        share|improve this answer



























                          0












                          0








                          0







                          Okay so I figured it out:



                          I had done two bad things:



                          1. I had my top component connected to state like so: mapStateToProps(state) => (keys: Object.keys(state)). I thought the object function would return a "static" array and prevent me from listening to the entire state but turns out I was wrong. Obviously (to me now), every time I changed the state I got a fresh array (but with the same entries). I now store them once on a completely separate property called quizIds.



                          2. I put my map-function in a bad place. I now keep render the QuizContainer like so:



                             <QuizContainer>
                            quizIds.map(quizId =>
                            <QuizForm>
                            <Question>
                            <Answer />
                            </Question>
                            </QuizForm>
                            )
                            </QuizContainer>


                          And then I render my arrays of children, injecting props for them to be able to use connect individually like so:



                           questions.map((questionId, index) => (
                          <React.Fragment key=questionId>
                          React.cloneElement(this.props.children,
                          index,
                          questionId,
                          quizId
                          )
                          </React.Fragment>
                          ))


                          That last piece of code will not work if you decide to put several elements as children. Anyway, looks cleaner and works better now! :D






                          share|improve this answer















                          Okay so I figured it out:



                          I had done two bad things:



                          1. I had my top component connected to state like so: mapStateToProps(state) => (keys: Object.keys(state)). I thought the object function would return a "static" array and prevent me from listening to the entire state but turns out I was wrong. Obviously (to me now), every time I changed the state I got a fresh array (but with the same entries). I now store them once on a completely separate property called quizIds.



                          2. I put my map-function in a bad place. I now keep render the QuizContainer like so:



                             <QuizContainer>
                            quizIds.map(quizId =>
                            <QuizForm>
                            <Question>
                            <Answer />
                            </Question>
                            </QuizForm>
                            )
                            </QuizContainer>


                          And then I render my arrays of children, injecting props for them to be able to use connect individually like so:



                           questions.map((questionId, index) => (
                          <React.Fragment key=questionId>
                          React.cloneElement(this.props.children,
                          index,
                          questionId,
                          quizId
                          )
                          </React.Fragment>
                          ))


                          That last piece of code will not work if you decide to put several elements as children. Anyway, looks cleaner and works better now! :D







                          share|improve this answer














                          share|improve this answer



                          share|improve this answer








                          edited Nov 13 '18 at 16:04

























                          answered Nov 13 '18 at 15:56









                          sugartixsugartix

                          407




                          407



























                              draft saved

                              draft discarded
















































                              Thanks for contributing an answer to Stack Overflow!


                              • Please be sure to answer the question. Provide details and share your research!

                              But avoid


                              • Asking for help, clarification, or responding to other answers.

                              • Making statements based on opinion; back them up with references or personal experience.

                              To learn more, see our tips on writing great answers.




                              draft saved


                              draft discarded














                              StackExchange.ready(
                              function ()
                              StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f53281471%2fchildren-prop-triggering-re-render-when-using-map-and-redux%23new-answer', 'question_page');

                              );

                              Post as a guest















                              Required, but never shown





















































                              Required, but never shown














                              Required, but never shown












                              Required, but never shown







                              Required, but never shown

































                              Required, but never shown














                              Required, but never shown












                              Required, but never shown







                              Required, but never shown







                              這個網誌中的熱門文章

                              Barbados

                              How to read a connectionString WITH PROVIDER in .NET Core?

                              Node.js Script on GitHub Pages or Amazon S3