I have a data called person in JSON format getting from API in the file User.js:
const [person, setPerson] = useState([]);
const url = "http://localhost:8080/api/persons";
useEffect(() => {
axios.get(url).then((response) => {
setPerson(response.data);
});
}, [url]);
In another file called UpdatePersonForm.js I'm trying to show that data in popup windows after clicking a button.
export const UpdatePersonForm= ({ person, personEditOnSubmit }) => {
return (
<div>
{person.map((item) => (
<tr>
<td>{item.name}</td>
</tr>
))}
</div>
}
then it shows a white blank screen again. If I called an API directly from UpdatePersonForm.js then it works fine. For example:
export const UpdatePersonForm= ({ personEditOnSubmit }) => {
const [person, setPerson] = useState([]);
const url = "http://localhost:8080/api/persons";
useEffect(() => {
axios.get(url).then((response) => {
setPerson(response.data);
});
}, [url]);
return (
<div>
{person.map((item) => (
<tr>
<td>{item.name}</td>
</tr>
))}
</div>
}
However, if I get data from the parent file like the above then I got wrong. Anyone know what’s wrong?
Related
The below is the reponse data from api which I get
[
{
zip_file: [
{zip: "a.zip"},
{zip: "b.zip"},
{zip: "c.zip"}
],
text_files: [
{text: "a.txt"},
{text: "b.txt"},
{text: "c.txt"}
],
pdf_files: [
{pdf: "a.pdf"},
{pdf: "b.pdf"},
{pdf: "c.pdf"},
]
}
];
I am trying to create dynamic tabs from json data where each key name will be name of tab(i,e zip_file,text_file,pdf_file) and key's array object will be body of tab as list of check boxes (i,e for zip file it will have ☑ a.zip , ☑ b.zip, ☑ c.zip)and have select all checkbox( ☑ Select All) for every tab where it should select only list of that particular tab(i,e in zip tab select All should select all the three entries).
Issue that I am facing now is when I click on select all option, all the list items are being fetched and value is stored in an array but only the last element of the list is being checked(i,e it has ☑ mark) and not the rest others(i.e ☐)
second issue is when I click on select All option in one tab and switch to next tab ,that tab's select All check Box is also selected (which I don't want ,it should be independent for each tabs)
Below is the Code snippet
import { useEffect, useState } from "react";
import Tab from "./Tab";import "bootstrap/dist/css/bootstrap.css";
import Download from "./Download";
const SimpleTab = () => {
const [data, setData] = useState([]);
const [file, setFile] = useState([]);
const [checked, setChecked] = useState([]);
useEffect(() => {
const fetchData = async () => {
const response = await fetch(`http://localhost:9080/...........`);
const responseData = await response.json();
setData(responseData);
};
});
// This is to add or remove checked value into an array
const addOrRemove = (name) => {
const index = file.indexOf(name);
if (index === -1) {
file.push(name);
} else {
file.splice(index, 1);
}
setFile(file);
};
//To retain the state of checked on change
const handleChange = (e, name) => {
setChecked({
...checked,
[name]: e.target.checked,
});
console.log("checked " + checked);
};
return (
<>
<Header />
<Download arraylink={file} />
<Tab>
{data.map((tab, id) =>
Object.entries(tab).map(([key, value]) => (
<Tab.TabPane key={`Tab-${key}`} tab={key} className="tabs">
<input
type="checkbox"
onChange={(e) => {
value.map((tab1, idx) =>
Object.entries(tab1).map(([key1, value1]) =>
addOrRemove(value1)
)
);
value.map((tab1, idx) =>
Object.entries(tab1).map(([key1, value1]) =>
handleChange(e, value1)
)
);
}}
//checked={checked[value1] || ""}
></input>
{value.map((tab1, idx) =>
Object.entries(tab1).map(([key1, value1]) => (
<table className="table table-bordered">
<tbody>
<tr key={value1}>
<th scope="row">
<input
type="checkbox"
onChange={(e) => {
addOrRemove(value1);
handleChange(e, value1);
}}
checked={checked[value1] || ""}
></input>
</th>
<td>{value1}</td>
</tr>
</tbody>
</table>
))
)}
</Tab.TabPane>
))
)}
</Tab>
</>
);
};
export default SimpleTab;
I'm new to react, if someone can please help me figure out what's wrong in my code or even suggest any other approach it would be great.
Thank you in Advance.
I would like to update text which is displayed inside a <div> element. I would love to do it when the cursor enters the <div> element.
Basically I'm fetching some data from the API and I display only one parameter (name). If a user enters the <div> with the name displayed I would like to show some details, i.e. description and price.
This is my code which I tried to complete my task.
import {useEffect, useState} from "react";
import requestOptionsGet from "../utilities/requestOptions";
import validateResponse from "../utilities/validators";
const Warehouse = () => {
const [items, setItems] = useState([]);
const [texts, setTexts] = useState([]);
const getItems = async () => {
const url = "http://localhost:8000/api/items/"
return await fetch(url, requestOptionsGet)
.then((response) => validateResponse(response, url))
.then((response) => response.json())
.then((data) => setItems(data))
};
useEffect(() => {
getItems();
}, []);
useEffect(() => {
setTexts(items.map((item) => (
{
id: item.id,
name: item.name,
description: item.description,
price: item.price,
currentDisplay: <h2>{item.name}</h2>,
})
))
}, [items]);
const displayName = (data) => {
console.log(
"displayName"
);
};
const displayDetails = (data) => {
const itemID = parseInt(data.currentTarget.getAttribute("data-item"));
const displayInfo = texts.find(text => text.id === itemID);
displayInfo.currentDisplay = <p>{displayInfo.description}</p>
setTexts(texts);
console.log(texts);
console.log(
"displayDetails"
);
return(
displayInfo.currentDisplay
);
};
return(
<div className="container">
<h1>Your warehouse.</h1>
<h2>All your items are listed here.</h2>
<hr />
{texts.map((text) => (
<button className="container-for-single-item" id={text.id} key={text.id}
onMouseEnter={displayDetails} onMouseLeave={displayName} data-item={text.id}>
{text.currentDisplay}
</button>
))}
</div>
);
}
export default Warehouse;
The functions work (everything is displayed in the console as it should be) and even the texts change. However the paragraph does not appear. How can I fix my code? Thanks!
Never modify state directly
const newTexts = texts.map(text => text.id === itemID ? { ...text, currentDisplay: <p>{text.description}</p> } : text);
setTexts(newTexts);
I am new to React Redux and I am trying to setState on a prop change in Redux using a useEffect hook.
I have the following code:
const DeploymentOverview = ({diagram, doSetDiagram}) => {
const { diagram_id } = useParams()
const [instances, setinstances] = useState(null)
const [error, seterror] = useState([false, ''])
useEffect(() => {
GetDiagram(diagram_id).then(d => doSetDiagram(d)).catch(err => seterror([true, err]))
}, [doSetDiagram])
useEffect(() => {
if (diagram) {
if (diagram.instances) {
let statusList = []
diagram.instances.forEach(instance => {
InstanceStatus(instance.key)
.then(status => statusList.push(status))
.catch(err => seterror([true, err]))
});
setinstances(statusList)
}
}
}, [diagram])
return (
<Container>
{error[0] ? <Row><Col><Alert variant='danger'>{error[1]}</Alert></Col></Row> : null}
{instances ?
<>
<Row>
<Col>
<h1>Deployment of diagram X</h1>
<p>There are currently {instances.length} instances associated to this deployment.</p>
</Col>
</Row>
<Button onClick={setinstances(null)}><FcSynchronize/> refresh status</Button>
<Table striped bordered hover>
<thead>
<tr>
<th>Status</th>
<th>Instance ID</th>
<th>Workflow</th>
<th>Workflow version</th>
<th>Jobs amount</th>
<th>Started</th>
<th>Ended</th>
<th></th>
</tr>
</thead>
<tbody>
{instances.map(instance =>
<tr>
<td>{ <StatusIcon status={instance.status}/> }</td>
<td>{instance.id}</td>
{/* <td>{instance.workflow.name}</td>
<td>{instance.workflow.version}</td> */}
{/* <td>{instance.jobs.length}</td> */}
<td>{instance.start}</td>
<td>{instance.end}</td>
<td><a href='/'>Details</a></td>
</tr>
)}
</tbody>
</Table>
</>
: <Loader />}
</Container>
)
}
const mapStateToProps = state => ({
diagram: state.drawer.diagram
})
const mapDispatchToProps = {
doSetDiagram: setDiagram
}
export default connect(mapStateToProps, mapDispatchToProps)(DeploymentOverview)
What I want in the first useEffect is to set de Redux state of diagram (this works), then I have a other useEffect hook that will get a list from one of the diagrams attributes named instances next I loop over those instances and do a fetch to get the status of that instance and add this status to the statusList. Lastly I set the instances state using setinstances(statusList)
So now I expect the list of statusresults being set into instances and this is the case (also working?). But then the value is changed back to the initial value null...
In my console it's first shows null (ok, initial value), then the list (yes!) but then null again (huh?). I read on the internet and useEffect docs that the useEffect runs after every render, but I still don't understand why instances is set and then put back to it's initial state.
I am very curious what I am doing wrong and how I can fix this.
If you have multiple async operations you can use Promise.all:
useEffect(() => {
if (diagram) {
if (diagram.instances) {
Promise.all(
diagram.instances.map((instance) =>
InstanceStatus(instance.key)
)
)
.then((instances) => setInstances(instances))
.catch((err) => setError([true, err]));
}
}
}, [diagram]);
Here is a working example:
const InstanceStatus = (num) => Promise.resolve(num + 5);
const useEffect = React.useEffect;
const App = ({ diagram }) => {
const [instances, setInstances] = React.useState(null);
const [error, setError] = React.useState([false, '']);
//the exact same code from my answer:
useEffect(() => {
if (diagram) {
if (diagram.instances) {
Promise.all(
diagram.instances.map((instance) =>
InstanceStatus(instance.key)
)
)
.then((instances) => setInstances(instances))
.catch((err) => setError([true, err]));
}
}
}, [diagram]);
return (
<pre>{JSON.stringify(instances, 2, undefined)}</pre>
);
};
const diagram = {
instances: [{ key: 1 }, { key: 2 }, { key: 3 }],
};
ReactDOM.render(
<App diagram={diagram} />,
document.getElementById('root')
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>
<div id="root"></div>
What you did wrong is the following:
diagram.instances.forEach(instance => {
InstanceStatus(instance.key)//this is async
//this executes later when the promise resolves
//mutating status after it has been set does not
//re render your component
.then(status => statusList.push(status))
.catch(err => seterror([true, err]))
});
//this executes immediately so statusList is empty
setinstances(statusList)
I am using react-table to display fetched data within a table. You also have different buttons within that table to interact with the data such as deleting an entry, or updating its data (toggle button to approve a submitted row).
The data is being fetched in an initial useEffect(() => fetchBars(), []) and then being passed to useTable by passing it through useMemo as suggested in the react-table documentation. Now I can click on the previously mentioned buttons within the table to delete an entry but when I try to access the data (bars) that has been set within fetchBars()it returns the default state used by useState() which is an empty array []. What detail am I missing? I want to use the bars state in order to filter deleted rows for example and thus make the table reactive, without having to re-fetch on every update.
When calling console.log(bars) within updateMyData() it displays the fetched data correctly, however calling console.log(bars) within handleApprovedUpdate() yields to the empty array, why so? Do I need to pass the handleApprovedUpdate() into the cell as well as the useTable hook as well?
const EditableCell = ({
value: initialValue,
row: { index },
column: { id },
row: row,
updateMyData, // This is a custom function that we supplied to our table instance
}: CellValues) => {
const [value, setValue] = useState(initialValue)
const onChange = (e: any) => {
setValue(e.target.value)
}
const onBlur = () => {
updateMyData(index, id, value)
}
useEffect(() => {
setValue(initialValue)
}, [initialValue])
return <EditableInput value={value} onChange={onChange} onBlur={onBlur} />
}
const Dashboard: FC<IProps> = (props) => {
const [bars, setBars] = useState<Bar[]>([])
const [loading, setLoading] = useState(false)
const COLUMNS: any = [
{
Header: () => null,
id: 'approver',
disableSortBy: true,
Cell: (props :any) => {
return (
<input
id="approved"
name="approved"
type="checkbox"
checked={props.cell.row.original.is_approved}
onChange={() => handleApprovedUpdate(props.cell.row.original.id)}
/>
)
}
}
];
const defaultColumn = React.useMemo(
() => ({
Filter: DefaultColumnFilter,
Cell: EditableCell,
}), [])
const updateMyData = (rowIndex: any, columnId: any, value: any) => {
let barUpdate;
setBars(old =>
old.map((row, index) => {
if (index === rowIndex) {
barUpdate = {
...old[rowIndex],
[columnId]: value,
}
return barUpdate;
}
return row
})
)
if(barUpdate) updateBar(barUpdate)
}
const columns = useMemo(() => COLUMNS, []);
const data = useMemo(() => bars, [bars]);
const tableInstance = useTable({
columns: columns,
data: data,
initialState: {
},
defaultColumn,
updateMyData
}, useFilters, useSortBy, useExpanded );
const fetchBars = () => {
axios
.get("/api/allbars",
{
headers: {
Authorization: "Bearer " + localStorage.getItem("token")
}
}, )
.then(response => {
setBars(response.data)
})
.catch(() => {
});
};
useEffect(() => {
fetchBars()
}, []);
const handleApprovedUpdate = (barId: number): void => {
const approvedUrl = `/api/bar/approved?id=${barId}`
setLoading(true)
axios
.put(
approvedUrl, {},
{
headers: {Authorization: "Bearer " + localStorage.getItem("token")}
}
)
.then(() => {
const updatedBar: Bar | undefined = bars.find(bar => bar.id === barId);
if(updatedBar == null) {
setLoading(false)
return;
}
updatedBar.is_approved = !updatedBar?.is_approved
setBars(bars.map(bar => (bar.id === barId ? updatedBar : bar)))
setLoading(false)
})
.catch((error) => {
setLoading(false)
renderToast(error.response.request.responseText);
});
};
const renderTable = () => {
const {
getTableProps,
getTableBodyProps,
headerGroups,
rows,
prepareRow
} = tableInstance;
return(
<table {...getTableProps()}>
<thead>
{headerGroups.map(headerGroup => (
<tr {...headerGroup.getHeaderGroupProps()}>
{headerGroup.headers.map(column => (
<th {...column.getHeaderProps()}>
<span {...column.getSortByToggleProps()}>
{column.render('Header')}
</span>{' '}
<span>
{column.isSorted ? column.isSortedDesc ? ' ▼' : ' ▲' : ''}
</span>
<div>{column.canFilter ? column.render('Filter') : <Spacer/>}</div>
</th>
))}
</tr>
))}
</thead>
<tbody {...getTableBodyProps()}>
{rows.map(row => {
prepareRow(row)
const rowProps = {...row.getRowProps()}
delete rowProps.role;
return (
<React.Fragment {...rowProps}>
<tr {...row.getRowProps()}>
{row.cells.map(cell => {
return (
<td {...cell.getCellProps()}>{cell.render('Cell')}</td>
)
})}
</tr>
{row.isExpanded ? renderRowSubComponent({row}): null}
</React.Fragment>
)})
}
</tbody>
</table>
)
}
}
export default Dashboard;
You're seeing stale values within handleApprovedUpdate because it's capturing bars the first time the component is rendered, then never being updated since you're using it inside COLUMNS, which is wrapped with a useMemo with an empty dependencies array.
This is difficult to visualize in your example because it's filtered through a few layers of indirection, so here's a contrived example:
function MyComponent() {
const [bars, setBars] = useState([]);
const logBars = () => {
console.log(bars);
};
const memoizedLogBars = useMemo(() => logBars, []);
useEffect(() => {
setBars([1, 2, 3]);
}, []);
return (
<button onClick={memoizedLogBars}>
Click me!
</button>
);
}
Clicking the button will always log [], even though bars is immediately updated inside the useEffect to [1, 2, 3]. When you memoize logBars with useMemo and an empty dependencies array, you're telling React "use the value of bars you can currently see, it will never change (I promise)".
You can resolve this by adding bars to the dependency array for useMemo.
const memoizedLogBars = useMemo(() => logBars, [bars]);
Now, clicking the button should correctly log the most recent value of bars.
In your component, you should be able to resolve your issue by changing columns to
const columns = useMemo(() => COLUMNS, [bars]);
You can read more about stale values in hooks here. You may also want to consider adding eslint-plugin-react-hooks to your project setup so you can identify issues like this automatically.
I am making a request to a local server. And server returns to me the follow response:
{"total":7,
"perPage":3,
"page":1,
"lastPage":3,
"data":[
{"id":1,"title":"animals","created_at":"/...","updated_at":"/..."},
{"id":2,"title":"space","created_at":"/...","updated_at":"/..."},
{"id":3,"title":"sport","created_at":"/...","updated_at":"/..."}
]}
data - it list of categories. And I display my list in the form of a table.
total, perPage, page, lastPage - it query param which I will insert into URL to filter my list.
page - the current page number. If in field page would be number two, then it would be in data the other three objects. That is, there would be in data other categories, with other title and id.
I have a task:
make pagination with three buttons. Because I have three pages "lastPage":3.And how I implemented it (I comment some line):
file Home.js:
const Home = () => {
const [value, setValue] = useState({
listCategory: [], // here I put my list of categories
currentPage: 1 // here I set initial page
});
useEffect(() => {
fetchData();
},[]);
async function fetchData() {
try {
const res = await apiCategory('api/categories', { //apiCategory it function I'll write it below
method:'GET'
});
console.log(res);
setValue({
listCategory:res.data, // I put the received data in an empty array to display the category list on the page
currentPage:res.page // set page which is default in response from server, now it-("page":1)
});
} catch(e) {
console.error(e);
}
};
const changePage = (argPage) => {
setValue({
currentPage: argPage // change page depending on which button was pressed
});
}
return (
<div>
<Search/>
<Table dataAttribute = {value.listCategory} /> // dispaly category list
{Object.keys(value.currentPage).map((item, index) => (
<button key={item} onClick={() => changePage(index)}>{item}</button>
))} // dispaly button and attach method changePage
</div>
)}
There is function apiCategory:
export const apiCategory = async (url, args) => {
const getToken = localStorage.getItem('myToken');
const valuePage = value.currentPage; //currentPage- props of my state in useState
const response = await fetch(`${apiUrl}${url}?page=${valuePage}`, { //I substitute in URL value from valuePage that implement pagination
...args,
headers: {
"Content-type": "application/json; charset=UTF-8 ",
"Authorization": `Bearer ${getToken}`,
"Accept": 'application/json',
...args.headers,
},
});
return response.json();
}
But I did not have buttons that switch pages (pagination). Only category list displays(screenshot):
https://i.piccy.info/i9/0b04d1777b6769e3a4dcb500faa3554a/1587741736/24398/1372209/Screenshot_1.png
Maybe even they do not change their condition. I don't know...
Also in function apiCategory I have an error:
'value' is not defined no-undef
in this line:
const valuePage = value.currentPage;
What should I change in the code so that buttons appear and pagination works?
I will also write a file Table.js:
export default ({dataAttribute}) => (
<table className="table">
<thead className="table-head">
<tr >
<th>id</th>
<th>title</th>
<th>created_at</th>
<th>updated_at</th>
</tr>
</thead>
<tbody>
{dataAttribute.map(item => (
<tr key={item.id}>
<td>{item.id}</td>
<td>{item.title}</td>
<td>{item.created_at}</td>
<td>{item.updated_at}</td>
</tr>
))}
</tbody>
</table>
);
You need to pass value into the apiCategory function, otherwise it is undefined. If you want fetchData to fire every time the next page is selected then it needs to be a dependency in useEffect.
Home.js
useEffect(() => {
async function fetchData(currentPage) {
try {
const res = await apiCategory('api/categories', {
method:'GET'
}, currentPage);
console.log(res);
setValue({
listCategory:res.data
currentPage:res.page
});
} catch(e) {
console.error(e);
}
};
fetchData(value.currentPage);
},[value.currentPage]);
apCategory.js
export const apiCategory = async (url, args, valuePage) => {
...
// remove the local valuePage line
Also note that you are removing the listCategory property in value every time changePage is called. To preseve the previous values, you need to change it to this:
const changePage = (argPage) => {
setValue((prev) => {
return {
...prev,
currentPage: argPage
}
});
}
In your return you aren't referring to the data structure properly.
{
// iterate through each element in `value.ListCategory`
value.listCategory.map((item, index) => (
<button
key={'listCategory_'+item.id}
onClick={() => changePage(index + 1)}
>
{index + 1}
</button>
))
}