My aim is to get the Data from an API call on first rendering of the page, store it in a state variable for further use. During the call, a loader keeps spinning and when call is successful, the loader disappears and the data is rendered on the screen using the state variable in which the response is saved.
Currently, I successfully make the call and get the data but when I try to store the data on a state variable, it remains undefined. I know that it is due to the fact that I have not initiated it with anything when I declared it.
My concern being when I set it later to the response of the API call then why is it undefined? Also if it is undefined then the loader would be running infinitely but in reality it stops and certain part of data is shown.
I have looked at various solution on Stack overflow and tried the following:
Check the state variable is not empty before rendering
used useLayoutEffect instead of useEffect
put the call function inside useEffect
Nothing works and the loader is stuck on loading.
Thank You.
import React, { useState, useEffect } from "react";
import * as S from "./Service.js";
import { Loader } from "./Loader.js";
function NeedHelp() {
let rspnse;
const [loader, setLoader] = useState(true);
const [data, setData] = useState(); // not setting any value
async function fetchData() {
rspnse = await S.getData();
setData(rspnse);
console.log("data :\n",data);
}
useEffect(() => {
setLoader(true);
fetchData();
setLoader(false);
}, []);
return (
<div className=" pt-3 p-1 mx-1 w-full">
{loader || typeof data === "undefined" ? (
<Loader />
) : (
data && (
<div>
<div id="first row" className="border p-2">
<span className="px-4 px-4 flex border">
<input
className="w-1/5 text-xs rounded py-1 px-1.5 placeholder-gray placeholder-opacity-75 focus:ring-0.5 focus:ring-blue focus:border-blue"
type="text"
placeholder="Filter..."
></input>
<div id="search_clear" className=" mx-1">
<button
onClick=""
className="text-white rounded px-1.5 py-1 mx-0.5 bg-blue transition duration-500 ease-in-out transform hover:translate-x-1 hover:scale-110 hover:bg-blue hover:shadow-lg"
>
Clear
</button>
</div>
<div className="self-center">
<p className="text-sm text-bgray ml-5">
Number of View: {data.data.length}
</p>
</div>
</span>
</div>
<div id="table">
<table className="table_auto w-full">
<thead>
<tr className="">
<th className=" "> Name</th>
<th className=" "> ID</th>
<td className=" "></td>
<td className=" "></td>
</tr>
</thead>
<tbody className="border">
<tr>
<td className="border">1</td>
<td className="border">2</td>
<td className="border">3</td>
<td className="border">4</td>
</tr>
</tbody>
</table>
</div>
</div>
)
)}
</div>
);
}
export default NeedHelp;
No reason to store rsponse as you have, and I cleaned the code up in a couple of other ways that aren't super notable, except maybe that loading will always be shown if no data is set. This should without any hitches, if it doesn't further debugging of what res is needs to be done:
import React, { useState, useEffect } from "react";
import * as S from "./Service.js";
import { Loader } from "./Loader.js";
function NeedHelp() {
const [data, setData] = useState(); // not setting any value
useEffect(() => {
(async () => {
setData(await S.getData());
})();
}, []);
return (
<div className=" pt-3 p-1 mx-1 w-full">
{!data ? (
<Loader />
) : (
<div>
<div id="first row" className="border p-2">
<span className="px-4 px-4 flex border">
<input
className="w-1/5 text-xs rounded py-1 px-1.5 placeholder-gray placeholder-opacity-75 focus:ring-0.5 focus:ring-blue focus:border-blue"
type="text"
placeholder="Filter..."
></input>
<div id="search_clear" className=" mx-1">
<button
onClick=""
className="text-white rounded px-1.5 py-1 mx-0.5 bg-blue transition duration-500 ease-in-out transform hover:translate-x-1 hover:scale-110 hover:bg-blue hover:shadow-lg"
>
Clear
</button>
</div>
<div className="self-center">
<p className="text-sm text-bgray ml-5">
Number of View: {data.data.length}
</p>
</div>
</span>
</div>
<div id="table">
<table className="table_auto w-full">
<thead>
<tr className="">
<th className=" "> Name</th>
<th className=" "> ID</th>
<td className=" "></td>
<td className=" "></td>
</tr>
</thead>
<tbody className="border">
<tr>
<td className="border">1</td>
<td className="border">2</td>
<td className="border">3</td>
<td className="border">4</td>
</tr>
</tbody>
</table>
</div>
</div>
)}
</div>
);
}
export default NeedHelp;
modify your code as below
const [loader, setLoader] = useState(false);
async function fetchData() {
setLoader(true);
rspnse = await S.getData();
setData(rspnse);
console.log("data :\n",data);
setLoader(false);
}
useEffect(() => {
fetchData();
}, []);
Related
I want to ask for your help.
Earlier I got a Reactjs search filter tutorial on a web which I forgot where the source came from.
the code is like this =>
import React, { useEffect, useState } from "react";
import axios from "axios";
const Users = () => {
const [users, setUsers] = useState([]);
const [mainUsers, setMainUsers] = useState([]);
useEffect(() => {
axios
.get("https://jsonplaceholder.typicode.com/users")
.then((res) => {
setUsers(res.data);
setMainUsers(res.data);
})
.catch((err) => {
console.log(err);
});
}, []);
const handleSearch = (e) => {
setUsers(
mainUsers.filter((u) =>
u.name.toLowerCase().includes(e.target.value.toLowerCase())
)
);
console.log(e.target.value);
};
return (
<div className={`mt-5 p-4 container-fluid`}>
<div className="row my-2 mb-4 justify-content-between w-100 mx-0">
<div className="form-group col-10 col-md-6 col-lg-4">
<input
type="text"
className="form-control shadow"
placeholder="Search"
onChange={handleSearch}
/>
</div>
</div>
{users.length ? (
<table className="table">
<thead>
<tr>
<th scope="col">#</th>
<th scope="col">Name</th>
<th scope="col">User Name</th>
<th scope="col">Email</th>
<th scope="col">Action</th>
</tr>
</thead>
<tbody>
{users.map((u) => (
<tr key={u.id}>
<th scope="row">{u.id}</th>
<td>{u.name}</td>
<td>{u.username}</td>
<td>{u.email}</td>
<td>
<i className="fa fa-edit text-warning"></i>
<i className="fa fa-edit text-warning pointer"></i>
</td>
</tr>
))}
</tbody>
</table>
) : (
<h6 className="text-center text-info">wating ...</h6>
)}
</div>
);
};
export default Users;
then I have an endpoint that has params, like this =>
xxxx.com/api/dashboard/v1/getCountPoiCategoryProvinsi?provinsi=jakarta
The question is,
how to change the initial url (https://jsonplaceholder.typicode.com/users") to this url (xxxx.com/api/dashboard/v1/getCountPoiCategoryProvinsi?provinsi=jakarta) ?
I don't know how to change it Thank you, I really appreciate your help.
I am using Next and Tailwind/Daisy UI.
The code for the page below will fetch a JSON object from the API endpoint and render a top table of source systems and a lower table of domains attached. Clicking on a row in the top table filters the second table to the relevant domains. This all works fine. I also have a modal which is going to be used to create new source systems or edit existing ones. The [Edit] and [Create] buttons call the same function but the [Edit] button passes in the system ID and the [Create] button passes in -1 which is not a valid system id. The function call updates the SelectedSytemID store which is then used to filter the systems list for both the Domains table and the modal.
If when you load the page, you click on [Create] the modal opens and shows the placeholder (because the selectedSystemID is -1 and so not a valid system). If you click on an [Edit] button the modal opens and shows the system name (as it has found the correct system from the filter). If you now click on the [Create] button again, although the selectedSystemID is -1 and the filter function returns undefined, the modal input field is STILL showing the last filtered system name. I don't fully understand why and am looking for both an explanation of why the input value is not re-evaluated and how to fix it. I think I need either a useRef or useEffect but not sure where or how. Any help is much appreciated. I have replaced the API call with hard-coded JSON which is a cut down version of the response.
import { use, useEffect, useState } from "react";
import { listSourceSystems } from "./api/SourceSystems/index";
export default function Sourcesystem() {
const [systems, setSystems] = useState([]);
const [selectedSystemID, setSelectedSystemID] = useState(-1)
const [modalIsOpen, setModalisOpen] = useState(false)
async function fetchData() {
const listSourceSystems = [
{
"id": 1,
"systemName": "Order Management",
"domains": [
{
"id": 1,
"domainName": "Customer"
},
{
"id": 2,
"domainName": "Order"
},
]
},
{
"id": 2,
"systemName": "Warehouse Managment",
"domains": [
{
"id": 9,
"domainName": "Product"
}
]
}
]
// setSystems(await listSourceSystems());
setSystems(listSourceSystems)
console.log(systems)
}
useEffect(() => {
fetchData();
}, []);
function filterDomains(systemID) {
setSelectedSystemID(systemID)
}
function selectedSystem (){
const ss = systems.filter(s=> s.id === selectedSystemID)[0]
return ss
}
function openModal(systemID){
filterDomains(systemID)
setModalisOpen(true)
console.log("openModal")
}
function closeModal(){
setModalisOpen(false)
console.log("closeModal")
}
return (
<>
<div className="flex flex-col mx-10 mt-4">
<h1 className="text-3xl font-bold underline text-center">Source Systems</h1>
<div className="divider"></div>
<div className="grid h-50 card bg-base-300 rounded-box place-items-center">
<table className="table table-compact w-full">
<thead>
<tr>
<th className="font-bold px-5">Name</th>
<th>actions</th>
</tr>
</thead>
<tbody>
{systems && systems.map((system) => (
<tr
key={system.id}
className={`hover ${system.id === selectedSystemID? "active text-secondary font-bold": ""}`}
onClick={() => filterDomains(system.id)}
>
<td className="px-5">{system.systemName}</td>
<td>
<button
className="btn btn-primary btn-sm"
onClick={() => openModal(system.id)}
>
Edit
</button>
</td>
</tr>
))}
</tbody>
<tfoot>
<tr>
<td colSpan="4" className="text-center font-bold accent">Add a new Source System</td>
<td>
<button
className="btn btn-primary btn-wide btn-sm"
onClick={()=> openModal(-1)}
>
click here
</button>
</td>
</tr>
</tfoot>
</table>
</div>
<div className="divider mt-0 before:bg-secondary after:bg-secondary"></div>
<div>
<div className="grid h-20 card bg-primary-800 rounded-box place-items-center">
<table className="table table-compact w-full table-fixed table-zebra">
<thead>
<tr>
<th className="text-left px-5">Domain</th>
<th className="text-right px-5">Source System</th>
</tr>
</thead>
<tbody>
{
selectedSystem()?.domains.map(d => (
<tr key={d.id} className="hover">
<td className="px-5">{d.domainName}</td>
<td className="table-cell-2 text-right px-5">{systems.filter(s => s.id === selectedSystemID).systemName}</td>
</tr>
))
}
</tbody>
</table>
</div>
</div>
{/* !-- Modal --> */}
<input type="checkbox" id="source-system-modal" className=" modal-toggle" checked={modalIsOpen} />
<div className="modal">
<div className="modal-box">
<h3>Source System Maintenance</h3>
<form>
<input
type="text"
placeholder="System Name placeholder"
className="input input-bordered input-primary input-sm w-full"
value={selectedSystem()?.systemName }
>
</input>
</form>
<div className="modal-action">
<label htmlFor="source-system-modal" className="btn btn-info">Submit</label>
<label htmlFor="source-system-modal" className="btn btn-warning btn-outline" onClick={()=> closeModal()}>Cancel</label>
</div>
</div>
</div>
</div>
</>
)}
Once again, as soon as I post it on Stack Overflow, it leads me down a different query path. Turns out the reason is pretty simple. The value in the input must always return a string so I need to do a ternary check and actually return an empty string if the function returns undefined
value={selectedSystem()? selectedSystem().systemName : "" }
I'm trying to reference to an input value rendered by a map(), but quickly found out that document.getElementById is not the way to go. I think using useRef is difficult because of the mapping.
What is the most efficient and effective method for referencing a mapped input in a React application? Are there any specific techniques or tools that are particularly useful for this task?
import { Fragment, useContext } from "react";
import { RoutineContext } from "../../context/CurrentRoutineContext";
export default function Card() {
const { data: session, status } = useSession();
const { currentRoutine } = useContext(RoutineContext);
return (
<>
{currentRoutine.sections.map((item) => (
<div key={item.name} className="w-full rounded-md sm:max-w-md sm:shadow-lg">
{/* Header */}
<div className="px-4 py-5 border-b border-gray-200 shadow-md bg-gravel-400 sm:px-6">
<h3 className="text-xl font-medium leading-6 text-center text-slate-100">
{item.name}
</h3>
</div>
{/* Body */}
<div className="p-3">
{item.exercises.map((item, idx) => (
<div key={item.name} className="px-2 py-1.5">
<span className="block font-semibold text-gravel-700">{item.name}</span>
<table className="min-w-full">
<thead>
<tr key={item.name}>
//Table headers....
</tr>
</thead>
<tbody className="divide-y divide-gray-200">
<tr key={item.name} className="w-full py-1">
<td className="px-2 text-sm text-gray-500 whitespace-nowrap">
{/* I want this input's value */}
<input
type="number"
className="w-12 pl-1 text-base rounded-sm bg-gravel-100"
/>
</td>
<td>
<button
disabled={false}
onClick={() => {}}
type="button"
>
<CheckIcon className="w-5 h-5 p-1 text-center text-stone-800" />
</button>
</td>
</tr>
</tbody>
</table>
</div>
))}
</div>
</div>
))}
</>
);
}
You could have the ref itself be a map then use the callback form of the ref prop to populate it.
import { Fragment, useContext } from "react";
import { RoutineContext } from "../../context/CurrentRoutineContext";
export default function Card() {
const { data: session, status } = useSession();
const { currentRoutine } = useContext(RoutineContext);
const fields = useRef({})
return (
<>
{currentRoutine.sections.map((section) => (
<div key={section.name} className="w-full rounded-md sm:max-w-md sm:shadow-lg">
{/* Header */}
<div className="px-4 py-5 border-b border-gray-200 shadow-md bg-gravel-400 sm:px-6">
<h3 className="text-xl font-medium leading-6 text-center text-slate-100">
{section.name}
</h3>
</div>
{/* Body */}
<div className="p-3">
{item.exercises.map((exercise, idx) => (
<div key={exercise.name} className="px-2 py-1.5">
<span className="block font-semibold text-gravel-700">{exercise.name}</span>
<table className="min-w-full">
<thead>
<tr key={exercise.name}>
//Table headers....
</tr>
</thead>
<tbody className="divide-y divide-gray-200">
<tr key={exercise.name} className="w-full py-1">
<td className="px-2 text-sm text-gray-500 whitespace-nowrap">
{/* I want this input's value */}
<input
type="number"
className="w-12 pl-1 text-base rounded-sm bg-gravel-100"
ref={(el) => {
if (!fields.current[section.id]) fields.current[section.id] = {}
fields.current[section.id][exercise.id] = el
}
/>
</td>
<td>
<button
disabled={false}
onClick={() => {}}
type="button"
>
<CheckIcon className="w-5 h-5 p-1 text-center text-stone-800" />
</button>
</td>
</tr>
</tbody>
</table>
</div>
))}
</div>
</div>
))}
</>
);
}
Now fields.current will have a structure like:
{
"section_id_123": {
"exercise_id_123": // <-- HTML ELEMENT HERE
// Other exercises in this section
}
// Other sections
}
I made an assumption both section and exercise have an id property. You could probably use name if uniqueness is guaranteed.
However this is usually where you are at the point where you have reached the practical limits of "uncontrolled" forms. At this point you should look into changing your form to be "controlled". It would be much easier to manage.
You might consider using a form lib also.
I create a todo list system and I use a map to make a loop for all the items I have.
But items are stuck for me in the same row in the table.
This is a system I used to build in js vanila and now for practice I run it in react.
I would be happy for a solution.
In js vanila I would use insertAdjacentHTML
But I'm looking for the solution in react
demo for the app: https://v1-todolist.netlify.app
My Problem All items in one row.
I need not all items to be on the same line I need it to be dropped line like here
Example of how the item should look properly.
This system I built it in js vanila
if i add Div it not work
It does not sit well in the table and I also get a validateDOMNesting (...) error: cannot appear as a child of .
my code App.js
import { useState } from "react";
import "./App.css";
import Form from "./Form";
import Alert from "./Alert";
function App() {
const [nameTask, setNameTask] = useState("");
const [priority, setPriority] = useState("Low");
const [list, setList] = useState([]);
const [alert, setAlert] = useState({ show: false, type: "", msg: "" });
const handlerSubmit = function (e) {
e.preventDefault();
if (!nameTask) return showAlert(true, "danger", "you cannot input empty");
const newList = {
id: new Date().getTime().toString(),
title: nameTask,
priority: priority,
};
setList([...list, newList]);
};
const showAlert = function (show = false, type, msg) {
setAlert({ show, type, msg });
};
return (
<article className="vh-100 gradient-custom-2">
{alert.show && <Alert {...alert} showAlert={showAlert} />}
<Form
list={list}
setNameTask={setNameTask}
setPriority={setPriority}
handlerSubmit={handlerSubmit}
/>
</article>
);
}
export default App;
Form.js
import React from "react";
const Form = function ({ handlerSubmit, setNameTask, setPriority, list }) {
return (
<div className="container py-5 h-100">
<div className="row d-flex justify-content-center align-items-center h-100">
<div className="col-md-12 col-xl-10">
<div className="card mask-custom">
<div className="card-body p-4 text-white">
<div className="text-center pt-3 pb-2">
<img
src="https://mdbootstrap.com/img/Photos/new-templates/bootstrap-todo-list/check1.png"
alt="Check"
width="60"
/>
<h2 className="my-4">Task List</h2>
</div>
<form className="form-task" onSubmit={(e) => handlerSubmit(e)}>
<div className="col-auto">
<input
name="name-task"
type="text"
className="form-control task-input"
id="autoSizingInput"
placeholder="Add Task"
onChange={(e) => setNameTask(e.target.value)}
/>
</div>
<select
className="form-select"
aria-label="Default select example"
onChange={(e) => setPriority(e.target.value)}
>
<option value="Low">Low</option>
<option value="Normal">Normal</option>
<option value="High">High</option>
</select>
<button type="submit" className="btn btn-primary">
submit
</button>
</form>
<table className="table text-white mb-0">
<thead>
<tr>
<th scope="col">Task</th>
<th scope="col">Priority</th>
<th scope="col">Actions</th>
</tr>
</thead>
<tbody>
<tr className="fw-normal">
{list.map((item, index) => {
return (
<React.Fragment key={index}>
<th>
<span className="ms-2" data-name={`${item.title}`}>
{item.title}
</span>
</th>
<td className="align-middle priority-class">
<span className="badge ${value.color}">
{item.priority}
</span>
</td>
<td className="align-middle">
<h6 className="mb-0" data-id="${value.id}">
<a className="remove-link" href="#">
<span className="badge bg-gradient remove">
❌
</span>
</a>
<a className="complete-link" href="#">
<span className="badge bg-gradient complete">
✔
</span>
</a>
</h6>
</td>
</React.Fragment>
);
})}
</tr>
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
);
};
export default Form;
Check this CodeSandbox
I was able to solve your issu just by adding a flex propriety to the panel that contain your list and by changing React.fragment by a div.
However it would perhaps be better to swap the node with the class fw-normal to a div and change the React.fragment to the node tr.
Every time I display the cart items I want to update the total price and count of the items using the useState function inside the render method. But, immediately after the UI is rendered I get a react error mentioned above.
Is there a better way of doing what I'm trying to achieve without getting the error?
const Cart = () => {
const cartItems = useItems()
const firebase = useFirebase()
//Items count
//Total amount of the items
let [total, updateTotal] = useState(0)
let [count, updateCount] = useState(1)
//Method to add items to the cart
useEffect(() => {
if (!firebase) return
}, [firebase, cartItems])
return (
<Layout>
<SEO title="Cart" />
<Navbar count={count} />
<MDBContainer>
<MDBCol lg="12" className="">
<MDBTable responsive className="mt-5 z-depth-1">
<MDBTableHead>
<tr className="bg-light">
<th>
<div className="p-1 px-3 text-uppercase font-weight-bold">
Product
</div>
</th>
<th>
<div className="p-1 px-3 text-uppercase font-weight-bold">
Price
</div>
</th>
<th>
<div className="p-1 px-3 text-uppercase font-weight-bold">
Quantity
</div>
</th>
<th>
<div className="p-1 px-3 text-uppercase font-weight-bold">
Remove
</div>
</th>
</tr>
</MDBTableHead>
<MDBTableBody id="products-list">
{cartItems.map(product => {
updateTotal((total += product.price))
updateCount((count += 1))
return (
<tr>
<td class="px-3 font-weight-normal">
{product.name} <span class="d-none">{product.id}</span>{" "}
</td>
<td width="10%" class="text-center font-weight-normal">
{product.price}
<span>/kg </span>{" "}
</td>
<td width="10%" class="text-center font-weight-normal">
{product.count}
</td>
<td width="10%" class="text-center">
<div class="px-3 font-weight-normal">
{" "}
<button
class="bg-transparent border-0"
id="delete-button"
>
<i class="fa fa-trash-alt delete-icon"></i>
</button>
</div>
</td>
</tr>
)
})}
</MDBTableBody>
<MDBTableFoot>
<tr>
<td className="px-3 text-uppercase font-weight-bold">Total</td>
<td className="font-weight-bold px-5">₹{total}</td>
<td className="font-weight-bold pr-2 text-center">{count}</td>
</tr>
</MDBTableFoot>
</MDBTable>
</MDBCol>
</MDBContainer>
</Layout>
)
}
export default Cart
use blank [] instead of [firebase, cartItems]
The below code should work for you:
const cartItems = useItems();
// I assume this gives you cartItems.
let [total, updateTotal] = useState(() => {
if (cartItems) {
// item object {id: "2", name: "Cucumber", category: "vegetable", price: 50, // //count: 0}
return cartItems.reduce((acc, elem) => {
acc += elem.price * elem.count;
return acc;
}, 0);
}
return 0;
});
let [count, updateCount] = useState(cartItems.length);
React.useEffect(() => {
if (cartItems) {
updateCount(cartItems.length);
// item object {id: "2", name: "Cucumber", category: "vegetable", price: 50, // //count: 0}
const total = cartItems.reduce((acc, elem) => {
acc += elem.price * elem.count;
return acc;
}, 0);
updateTotal(total);
}
}, [cartItems]);
So, basically you need to initiate the state once you get some value from the useItems and then also need to update it when cartItems reference changes
Putting [firebase, cartItems] tells react to always re-render if any change is made to these two parametres. So that is why there are too many re-renders.