I want to add an array of <TokenFeed/>
functional components to the <Feeds/>
component. The problem is that when I do, the onClick()
event that passes some text to my <App/>
doesn’t work as expected. The array version of <TokenFeed/>
when clicked, will replace my <input/>
text rather than appending to the end of it.
On the contrary, when I add a copy of <TokenFeed/>
in the return of <Feeds/>
,
and click the button, it works fine.
How can I fix this?
import React, { useState } from "react"; import { Feeds } from "./Feeds"; export default function App() { const [inputValue, setInputValue] = useState(""); const [showFeeds, setShowFeeds] = useState(); function createFeeds(e) { if (e._reactName === "onClick" && showFeeds === undefined) { setShowFeeds( <Feeds value={(val) => { setInputValue(inputValue + val); createFeeds(""); }} /> ); } else { setShowFeeds(undefined); } } return ( <> <input type="text" placeholder="Message" value={inputValue} onChange={(e) => setInputValue(e.target.value)} ></input> <button onClick={(e) => createFeeds(e)}>Create Feeds</button> {showFeeds} </> ); }
import React from "react"; import { TokenFeed } from "./TokenFeed"; let tokenFeedArr = []; export const Feeds = (props) => { if (tokenFeedArr.length === 0) { tokenFeedArr.push( <TokenFeed key={"1"} onClick={() => props.value("Array")} tokenName={"Array"} tokenPrice={"Test"} /> ); } return ( <section> {/* This doesn't work */} {tokenFeedArr} {/* This does work */} <TokenFeed key={"2"} onClick={() => props.value("Direct")} tokenName={"Direct"} tokenPrice={"Test"} /> </section> ); };
import React from "react"; export const TokenFeed = (props) => { return ( <section onClick={() => props.onClick()} style={{ backgroundColor: "yellow", width: "10%", textAlign: "center" }} > <h1>{props.tokenName}</h1> <p>{props.tokenPrice}</p> </section> ); };
Advertisement
Answer
You need to declare let tokenFeedArr = [];
inside the Feeds
component.
Instead of:
let tokenFeedArr = []; export const Feeds = (props) => { if (tokenFeedArr.length === 0) { tokenFeedArr.push( <TokenFeed key={"1"} onClick={() => props.value("Array")} tokenName={"Array"} tokenPrice={"Test"} /> ); } ...
Try this:
export const Feeds = (props) => { const tokenFeedArr = []; if (tokenFeedArr.length === 0) { tokenFeedArr.push( <TokenFeed key={"1"} onClick={() => props.value("Array")} tokenName={"Array"} tokenPrice={"Test"} /> ); } ...
The reason the tokenFeedArr
that is declared outside the Feeds
component doesn’t work has to do with JavaScript closures. Specifically the issue lies within the value()
function that’s inside instances of TokenFeed
inside the tokenFeedArr
.
The value()
function that is passed to an instance of TokenFeed
inside of the tokenFeedArr
only has access to inputValue
as it was when the component was mounted (which is an empty string). It’s not connected to inputValue
via React state, because it’s outside of the scope of the exported component. This is true even though the TokenFeed
components are pushed to tokenFeedArr
inside the Feeds
component. tokenFeedArr
is still declared outside Feeds
. The setInputValue
function works, because of how the useState
hook works, but the inputValue
variable is just a variable and is subject to JavaScript closures/hoisting, which causes it to retain its original value.