I just learned the hooks in react, so I used it to write a small feature that displays hidden texts when users click on hyperlinks. I finally made the code work, but it seems that my code is messed up. Could someone tell me:
- Why the "true" is printed two times in the console and what indeed happens when I call the update function returned by React.useState()? How can I improve my code to prevent this?
- How to clearly pass attributes into a functional React component? It seems that my method is really complicated (compressing in an attribute object).
This is the feature: Before click After click
Here's my code:
/*
params:
attributes: {
// Required params
text: the text using the style of hiddenTextLinks with each part separated by a '#'.
Ex: "Hey, here is the #HiddenTextLinks#, a wonderful style for texts#!"
// Optional params
plainStyle: customized style for plain contents.
plainFont: customized font for plain content (no use if plainStyle is specified).
plainColor: customized font for plain text color (no use if plainStyle is specified).
linkStyle: customized style for link contents.
Notice: the link here is indeed a button styled as a "link". Therefore, I suggest you to provide
the following attributes:
background: "none",
border: "none",
padding: 0,
textDecoration: "none",
fontSize: "16px",
linkFont: customized font for links (no use if linkStyle is specified).
linkColor: customized font for link color (no use if linkStyle is specified).
hiddenStyle: customized style for hidden texts.
hiddenFont: customized font for hidden texts. (no use if hiddenStyle is specified).
hiddenColor: customized color for hidden texts. (no use if hiddenStyle is specified).
}
*/
function HiddenTextLinks(props) {
console.log("runned");
props = props.attributes;
var text = props.text;
const plainStyle = props.plainStyle || {
fontFamily: props.plainFont || "arial, sans-serif",
color: props.plainColor || "#000000",
};
const linkStyle = props.linkStyle || {
background: "none",
border: "none",
padding: 0,
fontSize: "16px",
textDecoration: "none",
fontFamily: props.linkFont || "arial, sans-serif",
color: props.linkColor || "#000000",
cursor: "pointer",
};
const hiddenStyle = props.hiddenStyle || {
position: "relative",
fontFamily: props.hiddenFont || "arial, sans-serif",
color: props.hiddenColor || "#9e9a9a",
};
const [temp] = React.useState(text.split(/(?<!\\)#/));
const [visibility, setVisibility] = React.useState(
Array(Math.floor(temp.length / 3)).fill(false)
);
const [renderedContent, setRenderedContent] = React.useState(render(temp));
function render(array) {
console.log("render");
return array.map((value, index) => {
if (index % 3 === 0) {
return (
<span key={`content${Math.floor(index / 3)}`} style={plainStyle}>
{value}
</span>
);
} else if (index % 3 === 1) {
return (
<button
key={`link${Math.floor(index / 3)}`}
style={linkStyle}
onClick={() => setVisible(Math.floor(index / 3))}
>
{value}
</button>
);
} else {
console.log(visibility[Math.floor(index / 3)]);
if (visibility[Math.floor(index / 3)]) {
return (
<span key={`hidden${Math.floor(index / 3)}`} style={hiddenStyle}>
{value}
</span>
);
} else {
return <span key={`hidden${Math.floor(index / 3)}`}></span>;
}
}
});
}
function setVisible(index) {
visibility[index] = !visibility[index];
setVisibility(visibility);
setRenderedContent(render(temp));
}
console.log("returned");
return <span>{renderedContent}</span>;
}
This is how I used it in an upper level:
const attributes = {
text: "Hey, here is the #HiddenTextLinks#, a wonderful style for texts#!",
plainFont: "Bakbak One, cursive",
linkFont: "Bakbak One, cursive",
hiddenFont: "Bakbak One, cursive",
linkColor: "#d1519c",
hiddenColor: "#9e9a9a",
};
return <HiddenTextLinks attributes={attributes} />;
Here's the console log (I clicked only once): console log
Thank you so much.
CodePudding user response:
Your render()
function runs every time the component updates for the following line:
const [renderedContent, setRenderedContent] = React.useState(render(temp));
You can fix it by passing a value-generating function instead of the value itself:
const [renderedContent, setRenderedContent] = React.useState(() => render(temp));