I am creating a stock app that allows the user to search stock prices by calling a Stock API and search any stock and get back its price.
Im storing the prices on a table, however the I want to be able to store the users input into the table as well.
My strategy has been to store the user's search input into an array "symbolArray" and the api information into a 'dataArray."
So heres my issues. I am able to map over the "dataArray" and render it to the table. BUT i need to map over "symbolArray" and render it into the table thats already rendering items from the "data Array."
Heres what I have so far
const Quotes = ()=> {
//I call their states
const [symbolArray, setSymbolArray] = useState([])
const [dataArray, setDataArray] = useState([])
//this function stores, and calls the api data that the user searches for
function getData() {
fetch(url)
.then(res => res.json())
.then(data =>{
dataArray.push(data)
})
}
// this is also activated when the user searchs. their input is pushed to the array of stock ticker symbols
const addingStuff = ()=> {
symbolArray.push(input)
}
return (
<>
{/* here a user searches for stock */}
<div >
<input id="searchBar" type="search" placeholder="Enter Ticker Symbol" aria-label="Search"
aria-describedby="search-addon" value={input} onInput={e => setInput(e.target.value)} />
<button type="button" id="searchButton" onClick={()=> {getData(); addingStuff(); }}>Quote This Stock</button>
</div>
{/* here is my table */}
<table class='table'>
<thead>
<tr>
{/* table headers*/}
<th scope='col'>Symbol</th>
<th scope='col'>Current Price</th>
<th scope='col'>Day High</th>
<th scope='col'>Day Low</th>
<th scope='col'>Price Change</th>
<th scope='col'>Open Price</th>
<th scope='col'>Percentage Change</th>
<th scope='col'>Previous Close</th>
</tr>
</thead>
<tbody>
{/* i call the function that gets and stores the data */}
{getData ?
dataArray.map((stock, index) => {
const {c, d, dp, h, l, o, pc} = stock;
return (
<tr key={index}>
{/* here is the issue where the 2 arrays clash */}
<th scope='row'>{symbolArray.map((symbol, i) => { return i})}</th>
<td>{c}</td>
<td>{d}</td>
<td>{dp}</td>
<td>{h}</td>
<td>{l}</td>
<td>{o}</td>
<td>{pc}</td>
</tr>
)
})
: null }
</>
}
CodePudding user response:
When you use a library/framework like React, it's best to decouple the presentation from the control. In your case this means:
- the table is the presentation. It should have no clue where the data comes from (from a
symbolArray
or from adataArray
- or both). - the
symbolArray
, thedataArray
and their union is the control. This doesn't need to "know" how the data is presented (it could be an ordered/unordered list, a table or a simple data iterator displayed as a card element).
With this in mind, I think you should slice your solution in two parts:
- one part is responsible for getting & handling the data
- the other part is responsible for displaying a set of data in a table.
Here's a snippet that does this:
const { useState, useEffect } = React
// custom hook for mocking API data
const useTypicode = () => {
const [response, setResponse] = useState(null);
// only runs once
useEffect(() => {
fetch('https://jsonplaceholder.typicode.com/users')
.then(res => res.json())
.then(json => {
setResponse(() => json.map(({id, name, username, email}) => ({id, name, username, email})))
})
}, []);
return response;
};
// AddUser doesn't know what is happening when the
// button is clicked: only sends the current state
// as an argument for the function it received
// in props
const AddUser = (props) => {
const [name, setName] = useState(null)
const [userName, setUserName] = useState(null)
const [email, setEmail] = useState(null)
return (
<div>
Name: <input type="text" onInput={(e) => setName(e.target.value)}/><br />
Username: <input type="text" onInput={(e) => setUserName(e.target.value)}/><br />
Email: <input type="text" onInput={(e) => setEmail(e.target.value)}/><br />
<button
onClick={() => props.addUser({name, userName, email})}
>
ADD USER
</button>
</div>
)
}
// Table doesn't "know" where the data comes from
// API, user created - doesn't matter
const Table = (props) => {
const headers = Object.keys(props.userList[0])
return (
<table>
<thead>
<tr>
{
headers.map(header => <th key={header}>{header}</th>)
}
</tr>
</thead>
<tbody>
{
props.userList.map(user => <tr key={user.id}>{Object.values(user).map((val, i) => <td key={user.id i}>{val}</td>)}</tr>)
}
</tbody>
</table>
)
}
// App doesn't know how the custom data is being
// entered or how it is displayed - only knows
// how to update the two lists it handles and
// where to pass them on
const App = () => {
const apiUsers = useTypicode()
const [users, setUsers] = useState([])
const addUser = (userData) => {
setUsers(prevState => [
...prevState,
{
id: Date.now(),
...userData
}
])
}
return (
<div>
<AddUser
addUser={(userData) => addUser(userData)}
/><br />
{
apiUsers &&
<Table
userList={[...apiUsers, ...users]}
/>
}
</div>
)
}
ReactDOM.render(
<App />,
document.getElementById('root')
);
<script crossorigin src="https://unpkg.com/react@17/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@17/umd/react-dom.development.js"></script>
<div id="root"></div>
<iframe name="sif1" sandbox="allow-forms allow-modals allow-scripts" frameborder="0"></iframe>
Sorry for some "bad practice" in the snippet (e.g. defining functions in the JSX elements in AddUser
), but the basic logic is what I wanted to illustrate: don't think of the HTML table as an entity that stores anything. No, the HTML table only displays what you feed it with. "Play" with the data in JS (merge different sources, filter by key/value, sort, etc.), and the presentation (table) should update (because it's fed with a new set of data it can display). This is reactivity.