Reinier68
Reinier68

Reputation: 3242

How to set new value in two dimensional array with useState()

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

Answers (1)

Umer Abbas
Umer Abbas

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

Related Questions