Children prop triggering re-render when using map and redux
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
add a comment |
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
add a comment |
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
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
javascript reactjs redux
edited Nov 13 '18 at 13:19
sugartix
asked Nov 13 '18 at 12:54
sugartixsugartix
407
407
add a comment |
add a comment |
2 Answers
2
active
oldest
votes
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.
add a comment |
Okay so I figured it out:
I had done two bad things:
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.
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
add a comment |
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
);
);
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
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
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.
add a comment |
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.
add a comment |
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.
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.
answered Nov 13 '18 at 15:51
user195257user195257
1,04942042
1,04942042
add a comment |
add a comment |
Okay so I figured it out:
I had done two bad things:
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.
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
add a comment |
Okay so I figured it out:
I had done two bad things:
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.
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
add a comment |
Okay so I figured it out:
I had done two bad things:
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.
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
Okay so I figured it out:
I had done two bad things:
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.
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
edited Nov 13 '18 at 16:04
answered Nov 13 '18 at 15:56
sugartixsugartix
407
407
add a comment |
add a comment |
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.
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
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
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
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