I need help rendering the table headers and the parsed information from an API only when the button is pressed. Along with refreshing the page when a different button is pressed so the array of 100+ objects don't stack on top of each other. I am using React to write this code. There is more to this code but this is a snippet and I want to apply it to the other buttons (3).
I'm also thinking of creating a new file for each API call to get their information and then importing them into this file to be enacted when the button gets pressed.
import { useState } from 'react';
export default function Markets() {
const API_KEY = process.env.REACT_APP_MS_API_KEY;
const [dataEndOfDay, setEndOfDay] = useState('');
async function getEndOfDay() {
try {
const response = await axios.get(
`${BASE_URL}eod?access_key=${API_KEY}&symbols=AAPL`
);
console.log(response.data.data);
setEndOfDay(
response.data.data.map((row) => {
return (
<tr>
<td>{row.date}</td>
<td>{row.symbol}</td>
<td>${row.open}</td>
<td>${row.close}</td>
</tr>
);
})
);
} catch (error) {
console.error(error);
}
}
return (
<div className="market-button">
<button onClick={getEndOfDay}>Get End of Day Report</button>
<table>
<tr>
<th>Date</th>
<th>Symbol</th>
<th>Open</th>
<th>Close</th>
</tr>
{dataEndOfDay}
</table>
}
You just need to wrap your table inside of an if statement. But also remove the empty string from your state and just leave it empty or set it to null.
const [dataEndOfDay, setEndOfDay] = useState();
return (
<div className="market-button">
<button onClick={getEndOfDay}>Get End of Day Report</button>
{ dataEndOfDay &&
<table>
<tr>
<th>Date</th>
<th>Symbol</th>
<th>Open</th>
<th>Close</th>
</tr>
{dataEndOfDay}
</table>
}
<div>
)
But on another note your code isn't the best for performance and I would recommend refactoring it. You shouldn't save the html data for your rows in state but rather just the data from the api and then just loop over it in the render.
import { useState } from 'react';
export default function Markets() {
const API_KEY = process.env.REACT_APP_MS_API_KEY;
const [dataEndOfDay, setEndOfDay] = useState();
async function getEndOfDay() {
try {
const response = await axios.get(`${BASE_URL}eod?access_key=${API_KEY}&symbols=AAPL`);
console.log(response.data.data);
setEndOfDay(response.data.data);
} catch (error) {
console.error(error);
}
}
return (
<div className="market-button">
<button onClick={getEndOfDay}>Get End of Day Report</button>
{
dataEndOfDay &&
<table>
<tr>
<th>Date</th>
<th>Symbol</th>
<th>Open</th>
<th>Close</th>
</tr>
{
dataEndOfDay.map(row => {
return (
<tr>
<td>{row.date}</td>
<td>{row.symbol}</td>
<td>${row.open}</td>
<td>${row.close}</td>
</tr>
);
}
}
</table>
}
</div>
);
}
Related
My goal is when I delete some data on my table it prevent from refreshing using the new version of userLoadData from react-router-dom#6. I don't know how to do that I already tried to ask CHATGTP but the solution they gave was not helping me.
I tried this I created some function products() with inside useLoaderData, it's working and deleting the data, but the problem is it's not refreshing when I delete. I need to refresh the page to update.
I will show my code that I've tried. Here is my component Product with my export const ProductData.
import { useLoaderData } from "react-router-dom";
import axiosClient from "../../../axios-client"
export const ProductData = () => {
return axiosClient.get('admin/product')
.then(({ data }) => {
return data.data
})
.catch((error) => {
return error
})
}
export default function Product() {
const products = () => {
return useLoaderData()
}
const ProductDelete = async(id) => {
await axiosClient.delete('admin/product/delete/'+id)
.then((data) => {
console.log(data)
products()
})
}
return (
<div className="flex flex-col">
<table className="min-w-full divide-y divide-gray-500">
<thead className="bg-red-500">
<tr>
<th>id</th>
</tr>
</thead>
<tbody className="divide-y divide-gray-200">
{products() && products().map((product) => (
<tr key={product.id}>
<td className="px-6 py-4 text-sm font-medium text-gray-800 whitespace-nowrap">
<button onClick={()=>ProductDelete(product.id)}>Delete</button>
</td>
</tr>
))}
</tbody>
</table>
</div>
);
}
Here is my router
import Product, { ProductData } from "./components/admin/product/IndexProduct"
const router = createBrowserRouter([
{
path: "product",
element: <Product />,
loader: ProductData
}
]);
The basic gist is that you should also provide a route action that issues the DELETE request, which will trigger the Route to revalidate itself and call the loader and refetch updated data.
Example:
import { useLoaderData } from "react-router-dom";
import axiosClient from "../../../axios-client"
export const productsLoader = async () => {
try {
const { data } = await axiosClient.get('admin/product')
return data.data;
} catch (error) {
return error;
};
};
export const deleteProductAction = async ({ request }) => {
const formData = await request.formData();
const id = formData.get("productId");
axiosClient.delete(`admin/product/delete/${id}`);
};
Import the Form component and wrap the delete button to submit the form and trigger the form/route action. Below uses a hidden form field to "inject" the product id that you want to work with.
import { Form, useLoaderData } from "react-router-dom";
export default function Product() {
const products = useLoaderData();
return (
<div className="flex flex-col">
<table className="min-w-full divide-y divide-gray-500">
<thead className="bg-red-500">
<tr>
<th>id</th>
</tr>
</thead>
<tbody className="divide-y divide-gray-200">
{products.map((product) => (
<tr key={product.id}>
<td className="px-6 py-4 text-sm font-medium text-gray-800 whitespace-nowrap">
<Form method="delete" replace>
<input name="productId" defaultValue={product.id} hidden />
<button type="submit">Delete</button>
</Form>
</td>
</tr>
))}
</tbody>
</table>
</div>
);
}
Import the loader and action functions and add them to the "/product" route
import Product, {
deleteProductAction,
productsLoader
} from "./components/admin/product/IndexProduct"
const router = createBrowserRouter([
{
path: "product",
element: <Product />,
loader: productsLoader,
action: deleteProductAction,
}
]);
Demo using "https://jsonplaceholder.typicode.com/" APIs.
I have created the table and I have different types of elements that I want to add. After adding the element I want the option to delete one specific element by clicking on the remove button.
import * as React from 'react';
import { useState } from 'react';
import { Table } from 'reactstrap';
import { Button } from 'reactstrap';
export default function App() {
let [myArray, updateMyArray] = useState([]);
function addElement() {
updateMyArray((arr) => [
...arr,
<Element value={myArray.length} handleDelete={handleDelete} />,
]);
}
const handleDelete = (index, e) => {
updateMyArray(myArray.filter((item) => item.value !== e.target.value));
};
return (
<div>
<Button onClick={addElement}>Add Element</Button>
<Table>
<thead>
<tr>
<th>#</th>
<th>Name</th>
<th></th>
</tr>
</thead>
<tbody>{myArray}</tbody>
</Table>
</div>
);
}
function Element({ value, handleDelete }) {
return (
<tr>
<td>{value}</td>
<td>Test</td>
<td>
<Button onClick={(e) => handleDelete(value, e)}>Remove Element</Button>
</td>
</tr>
);
}
With this code snippet, I have an option to create as many elements as I want. But if I create, let's say 10 elements, and I want to delete the 5th element, one cannot delete only that element, but all elements added after the 5th element is created. I am new at the react therefore I cannot realize what causes this problem.
The link for the code: https://stackblitz.com/edit/react-ts-7mwhmw?file=App.tsx
You should create unique index each element of array and dont store component as #Chrisg mentioned.
solution:
let [myArray, updateMyArray] = useState([]);
function addElement(value) {
updateMyArray((arr) => [...arr,value]);
}
const handleDelete = (index) => {
updateMyArray(myArray.filter((item,i) => index !== i));
};
return (
<div>
<Button onClick={() => addElement("newElement")}>Add Element</Button>
<Table>
<thead>
<tr>
<th>#</th>
<th>Name</th>
<th></th>
</tr>
</thead>
<tbody>{myArray.map((item,index) => {
return <tr>
<td>{item} {index}</td>
<td>Test</td>
<td>
<Button onClick={() => handleDelete(index, item)}>Remove Element</Button>
</td>
</tr>
})}</tbody>
</Table>
</div>
);
link : https://stackblitz.com/edit/react-ts-wqsmfq?file=App.tsx
On page load, I am using UseEffect to take an object array files. The array is mapped through, if it is male it is added to maleArray, if it is female it is added to femaleArray. At the end, i use console.log to check if the new arrays have filled and they have. I also set hasLoaded to true after this.
const[hasLoaded, setHasLoaded] = useState();
var maleArray= [];
var femaleArray= [];
useEffect(() => {
getPersonGender();
}, [props.files]);
const getPersonGender = () => {
if(!props.files.length > 0){
console.log("WAITING");
}else{
props.files.map( person => {
if(person.gender=== "male"){
maleArray.push(person);
}else{
femaleArray.push(person);
}
});
console.log(maleArray)
console.log(femaleArray)
setHasLoaded(true);
}
}
However when I get to the return statement, the new arrays I filled seem to be empty. For example, I am trying to map the maleArray to a table. I check hasLoaded and it is true but when I get to print nothing displays and I can't figure out why. When I attempted to debug, it seemed that maleArray and femaleArray never filled, but however, I am getting the console.log results saying that they are.
{!hasLoaded
?<div class="spinner-border" role="status"></div>
:
<table class="table">
<tbody>
{maleArray.map(male=> (
<tr>
<td>{male.name}</td>
<td>{male.age}</td>
</tr>
))}
</tbody>
</table>
}
Any reason why the new arrays are not being filled, yet i see the output on console.log? Thanks
The main reason it did not work is that the state was not set. You need to use the useState() hook to assign it to arrays and then use them further
Complete working example
import { useState, useEffect } from "react";
function ConfigurationList(props) {
const [hasLoaded, setHasLoaded] = useState();
// usage of hooks
const [maleArray, setMaleArr] = useState([]);
const [femaleArray, setFemaleArr] = useState([]);
useEffect(() => {
getPersonGender();
}, [props.files]);
const getPersonGender = () => {
let maleBuf = [];
let femaleBuf = [];
if (!props.files.length > 0) {
console.log("WAITING");
} else {
props.files.map((person) => {
if (person.gender === "male") {
maleBuf.push(person);
} else {
femaleBuf.push(person);
}
});
console.log(maleArray);
console.log(femaleArray);
// Setting the value to the state
setFemaleArr([...femaleBuf])
setMaleArr([...maleBuf])
setHasLoaded(true);
}
};
return (
<div>
<div class="card rounded-0">
<div class="card-header bg-dark custom-font rounded-0">Male</div>
<div class="card-body text-center">
{!hasLoaded ? (
<div class="spinner-border" role="status"></div>
) : (
<table class="table">
<thead>
<tr>
<th style={{ width: "30%" }} scope="col">
Name
</th>
<th style={{ width: "30%" }} scope="col">
Age
</th>
</tr>
</thead>
<tbody>
{maleArray.map((male) => (
<tr>
<td>{male.name}</td>
<td>{male.age}</td>
</tr>
))}
</tbody>
</table>
)}
</div>
</div>
</div>
);
}
export default ConfigurationList;
useState :
You will have to make sure that the array is not directly updated, because that will not render the data, So you should create a local variable and then assign it to the state at the end, or you can use the spread operator and then assign them, Both should work.
I think you have typo in {maleArray.map(male=>
Shouldn't it be { maleArray.map((male) => { rest of code }
I am trying to filter through an array so that when a certain tab is clicked, only those results show. I have managed to isolate the certain variables I want to remain but the others that don't fit the criteria still remain. How do I get the filter method to actually render on the page so that the results can be shown. I have searched for hours for and have tried to get the tbody and sort through that but I just get confused as I am mostly new to javascript and react. Can someone point me in the right direction?
Filter Method
const tbody = document.getElementsByTagName('tbody')
console.log(tbody)
//change active class
function addTabBackground() {
const tabs = document.querySelectorAll('[data-tab]')
window.onload = function () {
tabs.forEach(tab => {
tab.addEventListener('click', () => {
if (tab.getAttribute('data-tab') === 'gains') {
listOfOptions.map(option => {
console.log(option.totalProfit)
})
}
tabs.forEach(tab => {
tab.classList.remove('active')
})
tab.classList.add('active')
})
})
}
}
<div className="outputs" >
<table>
<thead>
<tr>
<th>Date</th>
<th>Stock Name</th>
<th>Price Of Option</th>
<th>Number Of Options</th>
<th>Total Amount Spent</th>
<th>Option Sold At</th>
<th>Amount Of Options Sold</th>
<th>Proft</th>
</tr>
</thead>
{listOfOptions.map(option => {
return (
<tbody>
<tr>
<td>{option.clock}</td>
<td>{option.name.toUpperCase()}</td>
<td>${option.price}</td>
<td>{option.amountOfOptions}</td>
<td>${option.totalAmountSpent.toFixed(2)}</td>
<td>${option.optionPriceSoldAt}</td>
<td>{option.amountOfOptionsSold}</td>
<td style={{ color: option.totalProfit >= 0 ? 'green' : 'red' }}>${option.totalProfit.toFixed(2)}</td>
</tr>
</tbody>
)
})}
</table>
</div>
I have used React-Bootstrap-v5 to get Nav, Nav.Item with eventKey and then passed selectedKey in its onSelect function to change the tabs.
Then once we get the data inside my data variable, I used the map function to go over the array. Inside Map Function I have used the required condition to filter the elements i.e. variable = 'Open' or 'Live'.
This will only show the Open type in Open Tab and Live Type Data inside Live Tab.
Hope it's clear to you.
import React, { useEffect, useState } from 'react';
const TestSeries = () => {
// Declare Variable for data to be fetched from API
const [data, setData] = useState([]);
const fetchTestData = async () => {
const response = await axios.get(site_ip + '/apiLink');
setData(response.data.Content);
};
useEffect(() => {
fetchTestData();
}, []);
// State Variable to keep track of active tab
const [activeTab, setActiveTab] = useState('Open');
return (
<>
<Nav
activeKey={activeTab}
fill
variant="pills"
onSelect={(selectedKey) => {
setActiveTab(selectedKey);
}}
>
<Nav.Item>
<Nav.Link eventKey="Open">Open</Nav.Link>
</Nav.Item>
<Nav.Item>
<Nav.Link eventKey="Live">Live</Nav.Link>
</Nav.Item>
</Nav>
<br />
<Table striped bordered hover>
<thead>
<tr>
<th>#</th>
<th>Column1</th>
<th>Column2</th
<th>Column3</th>
<th>Column4</th>
<th>Data Type</th>
</tr>
</thead>
<tbody>
{data.map((item, index) =>
// Condition by which data will be filtered
item.data_type == activeTab ? (
<tr>
<td>{index + 1}</td>
<td>{item.col1}</td>
<td>{item.col2}</td>
<td>{item.col3} </td>
<td>{item.col4}</td>
<td>{item.data_type}</td>
</tr>
) : null
)}
</tbody>
</Table>
</>
);
};
export default TestSeries;
Result
Assuming from the comment that you have something vaguely looking like:
function Page() {
return (
<>
<Navbar />
<Table />
</>
)
}
What you need to do is to store the current tab in a state, and pass this state down to the Table component so that you can use a Array.filter when rendering your table.
function Page() {
const [activeTab, setActiveTab] = useState(DEFAULT_ACTIVE_TAB)
return (
<>
<Navbar activeTab={activeTab} setActiveTab={setActiveTab} />
<Table activeTab={activeTab} />
</>
)
}
Your Navbar component should have a onClick handler where it is calling the setActiveTab function when the active tab change.
Then in your Table component you should have something like this:
function Table({ activeTab }) {
return (
<table>
...
{listOfOptions
.filter(option => /* something depending on the activeTab */)
.map(option => <... />)
}
</table>
}
Quick summary of my homework-project.
I have a REST API on my backend, which has a API method returning the Star Wars characters from an external API.
In my web Development, I am trying to fetch these data from my REST API, and I can see the data is actually shown in the browser developer tools, but I get another error.
"Warning: index.js:1437 Warning: Functions are not valid as a React child. This may happen if you return a Component instead of from render. Or maybe you meant to call this function rather than return it."
Can you please see what's going wrong in below .js file?
import React, {Component} from "react";
import {Table,} from 'react-bootstrap'
const urlCharacters = 'URL WORKS - BUT NOT SHOWING HERE.';
class Characters extends Component {
constructor(props) {
super(props);
this.state = {characters: []}
;
}
async componentDidMount() {
//async fetchCharactersFromBackend(){
const res = await fetch(urlCharacters);
const data = await res.json();
console.log(data);
const newData = data.map(e => JSON.parse(e.name));
this.setState([newData]);
}
render() {
if (!this.state.characters) {
return null
}
return (
<div>
<h2>Persons table</h2>
<Table striped bordered hover>
<thead>
<tr>
<th>Character Name</th>
</tr>
</thead>
<tbody>
{this.renderCharacters}
</tbody>
</Table>;
</div>
);
}
renderCharacters(characters) {
characters.forEach(e => {
return ("<tr><td>" + e.name + "</td></tr>")
});
}
}
/*
Persons = () => {
return (
<div>
<h2>Persons table</h2>
<Table striped bordered hover>
<thead>
<tr>
<th>Character Name</th>
</tr>
</thead>
<tbody>
{this.renderCharacters}
</tbody>
</Table>;
</div>
);
};
*/
export default Characters;
Do the following fixes and let me know if it works:
this.setState([newData]); --> this.setState({characters: newData})
{this.renderCharacters} --> {this.renderCharacters()}
update the renderCharacters to the following:
renderCharacters() {
return this.state.characters.map(e => {
return ("<tr><td>" + e.name + "</td></tr>")
});
}
}
{this.renderCharacters}
should be
{this.renderCharacters()}