Reputation: 3242
See here for a Codesandbox displaying my problem. Try typing a few words in both the textareas
to see what is going wrong.
I'm new to React. I am trying to dynamically create a svg element
based on user input. Im splitting up the individual words, calculating the width of these words, and push them into a content array.
Without using the useState React hook (left side on Codesandbox), this all works fine. I just declared a variable const content = [[]]
and push the individual words to the array. However, for some reason when using useState (const [content, setContent] = useState([[]])
), the array is updated totally different.
How can I make my (left) example from the Codesandbox example work with the React useState hook (right example from Codesandbox) and what am I doing wrong?
Upvotes: 1
Views: 1408
Reputation: 1876
So I fixed your code the way you have it working without using useState
hook for the content
. I created two new states and used useEffect
as well which felt necessary.
When state changes component re-renders, so you need to be careful about the state changes, in your case you don't need to copy the state of content
into the newContent
instead you need to re-initialize it same as you did at the top when initializing the state.
Similarly you also need to use useEffect
hook where you can run a part of code which is dependent on some variable in your case it's dummyText, wordsWithComputedWidth, spaceWidth
on separate locations.
import { useState, useEffect } from "react";
export default function WithUseState() {
const [dummyText, setDummyText] = useState("");
const [wordsWithComputedWidth, setWordsWithComputedWidth] = useState(1);
const [spaceWidth, setSpaceWidth] = useState(0);
const [content, setContent] = useState([[]]);
function calculateWordWidths(v) {
const words = v.split(/\s+/);
let svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
let text = document.createElementNS("http://www.w3.org/2000/svg", "text");
text.style =
"font: 10pt LiberationSans, Helvetica, Arial, sans-serif; white-space: pre;";
svg.appendChild(text);
document.body.appendChild(svg);
const wordsWithComputedWidth = words.map((word) => {
text.textContent = word;
return { word, width: text.getComputedTextLength() };
});
text.textContent = "\u00A0"; // Unicode space
const spaceWidth = text.getComputedTextLength();
document.body.removeChild(svg);
return { wordsWithComputedWidth, spaceWidth };
}
useEffect(() => {
let { wordsWithComputedWidth, spaceWidth } = calculateWordWidths(dummyText);
setWordsWithComputedWidth(wordsWithComputedWidth);
setSpaceWidth(spaceWidth);
}, [dummyText]);
useEffect(() => {
let xPos = 0;
let yPos = 16;
let page = 1;
let i = 0;
let len = wordsWithComputedWidth.length;
let newContent = [[]];
while (i < len) {
if (yPos > 25) {
newContent.push([]);
page++;
xPos = 0;
yPos = 16;
}
newContent[page - 1].push(
<text
x={xPos}
y={yPos}
key={i + xPos + yPos + wordsWithComputedWidth[i].word}
style={{
font: "10pt LiberationSans, Helvetica, Arial, sans-serif",
whiteSpace: "pre"
}}
>
{wordsWithComputedWidth[i].word}
</text>
);
xPos += wordsWithComputedWidth[i].width + spaceWidth;
if (xPos > 25) {
yPos += 16;
xPos = 0;
}
i++;
}
setContent(newContent);
}, [wordsWithComputedWidth, spaceWidth]);
function renderContentStructure() {
return content.map((page, pageIdx) => {
return (
<>
<span style={{ fontWeight: "bold" }}>Page: {pageIdx}</span>
<ol key={pageIdx}>
{page.map((fields, fieldIdx) => {
return <li key={fieldIdx}> {fields} </li>;
})}
</ol>
</>
);
});
}
return (
<div className="with-use-state">
<div>
Currently NOT working <strong>With</strong> useState
</div>
<div style={{ marginTop: "50px" }}>
<div className="content">
<div>Content</div>
<textarea
rows="6"
cols="24"
onChange={(e) => setDummyText(e.target.value)}
placeholder="type here..."
/>
</div>
<div className="preview">
<div>Preview</div>
<>
{content.map((page, idx) => (
<svg
viewBox="0 0 50 50"
xmlns="http://www.w3.org/2000/svg"
style={{
border: "1px solid #aaa",
width: "100px",
display: "block"
}}
key={idx}
>
{page}
</svg>
))}
</>
</div>
<div>{renderContentStructure()}</div>
</div>
</div>
);
}
Upvotes: 1