SetState hook does not render the data after a timeout - reactjs

My data is not shown when the app launches. It only shows when I tried to inspect the page.
I am reading JSON data that make take some time to be available. So, I added a async/await.
How do I fix my code so it displays on load?
Here is a snippet of my code:
const WeatherWidget = ({ id, editMode }) => {
const [roles, setRoles] = useState();
const getGoalData = async () => {
return (
[
{
"username": "user1",
"goal": "$5,000,200"
},
{
"username": "user2",
"goal": "$5,000,200"
},
{
"username": "user3",
"goal": "$4,000,199"
},
]
);
}
useEffect(() => {
const setDataRole = async () => {
var json = await getGoalData();
setRoles(json)
}
setDataRole();
}, [])
return (
<Container>
<div>
ticker from widget config
</div>
<StyledUl>
<Ticker>
{({ index }) => (
<>
{roles && roles.map(({ username, goal }, i) => (
<>
{i === 0 ? null : ','}
<StyledSpanName>
<span className="name">{username}</span>
</StyledSpanName>
<StyledSpanGoal> <span className="goal">{goal}</span></StyledSpanGoal>
</>
))
}
</>
)}
</Ticker>
</StyledUl>
</Container>
);
};

You don't need to load anything. The data is right there in the code. Just put it in roles right from the start.
In fact, you don't even need to use useState, since you never mutate the roles state. It could (and probably should) be a constant.
const WeatherWidget = ({ id, editMode }) => {
const [roles, setRoles] = useState(
[
{
"username": "user1",
"goal": "$5,000,200"
},
{
"username": "user2",
"goal": "$5,000,200"
},
{
"username": "user3",
"goal": "$4,000,199"
},
]
);
return (
<Container>
<div>
ticker from widget config
</div>
<StyledUl>
<Ticker>
{({ index }) => (
<>
{roles && roles.map(({ username, goal }, i) => (
<>
{i === 0 ? null : ','}
<StyledSpanName>
<span className="name">{username}</span>
</StyledSpanName>
<StyledSpanGoal> <span className="goal">{goal}</span></StyledSpanGoal>
</>
))
}
</>
)}
</Ticker>
</StyledUl>
</Container>
);
};

Seems like you forgot async:
useEffect(() => {
const setDataRole = async () => { // here
var res = await getGoalData()
var data = await res.json()
setRoles(data)
}
setDataRole();
}, [])

Related

ReactJS print nested JSON inside a function using map((item, index))

I am deveoping a Kanban Board. I use ReactJS to call the backend for stages and tasks that are open in each stage. It is a very simple JSON that I get from the backend.
JSON
[
{
"open_tasks": [
{
"task_id": 37,
"task_title": "Develop frontend"
},
{
"task_id": 38,
"task_title": "Create app"
}
],
"stage_id": 6,
"stage_title": "Tasks"
},
{
"open_tasks": [],
"stage_id": 15,
"stage_title": "Blocked"
},
{
"open_tasks": [],
"stage_id": 18,
"stage_title": "Finished"
}
]
Now I want to use ReactJS to print the nested JSON, however I cannot use map inside a map.
import { useEffect, useState } from "react";
export function IndexKanbanBoard() {
const [stagesWithOpenTasks, setStagesWithOpenTasks] = useState(() => []);
// Load stages
const loadStagesWithOpenTasksForBoard = async (e) => {
let result = await fetch("https://localhost:5002/api/kanban_boards/get_stages_with_open_tasks_for_board", {
method: "GET",
headers: {
'Authorization': 'Bearer ' + 'bla bla'
}
});
let resultJson = await result.json();
if (result.status === 200) {
setStagesWithOpenTasks(resultJson.map(fetch_object => {
return fetch_object
}))
}
};
// On load
useEffect(() => {
loadStagesWithOpenTasksForBoard()
}, []);
return (
<div>
{stagesWithOpenTasks.map((item, index) => (
<div key={index}>
<h2>{item.stage_title}</h2>
<p>I WANT TO SHOW open_tasks HERE</p>
</div>
))}
</div>
);
}
export default IndexKanbanBoard;
How can i loop trough nested JSON in ReactJS?
Assigning the next array items to a variable will help please have a look
return (
<div>
{data.map((item, index) => {
const openTasks = item["open_tasks"];
return (
<div key={index}>
<h2>{item.stage_title}</h2>
{openTasks.map((item) => (
<p>{item.task_title}</p>
))}
<p></p>
</div>
);
})}
</div>
);

How to delete user by ID in react using react-confirm-alert

I'm trying to delete a user dependents by dependents id using react-confirm-alert dialog but the list refreshes, how do I stop this from happening?
import { confirmAlert } from 'react-confirm-alert';
import 'react-confirm-alert/src/react-confirm-alert.css';
//Api data sample
"Details": [
{
"name": "test test",
"gender" "M"
"dependents": [
{
"blood_group": "A+",
"date_of_birth": "1990-08-10",
"gender": "Female",
"genotype": "AS",
"id": "621f191dcd7fe69a6a3b7",
}
],
},
]
function App() {
const [formalDetails, setFormalDetails] = useState([]);
//get formal details API call
const handleDelete = (detail) => {
const params = JSON.stringify({
"principal enrid": detail.principals_enrid,
"dependent id": detail.id,
mine: true,
});
Axios({
method: "POST",
url: "api",
headers: {
"Content-Type": "application/json",
},
data: params,
})
.then((response) => {
console.log(response.status);
//below is where my proble lies
setFormalDetails((current) =>
current.filter((dep) => {
return dep?.dependents?.id !== detail?.dependents?.id;
})
);
})
.catch(function (error) {
console.log(error);
});
};
const submit = (user) => {
confirmAlert({
title: 'Confirm to delete Dependent',
message: `Are you sure you want to delete ${user?.name}?`,
buttons: [
{
label: 'Yes',
onClick: () => handleRemove(user)
},
{
label: 'No',
onClick: () => null
}
]
});
}
return (
<div className="app">
{formalDetails.length === 0 ? (<p>No Data</p>) : (
formalDetails?.map((record, idx) => {
return (
<div key={idx}>
<p >{record.name}</p>
<p >{record.gender}</p>
{
record?.dependents?.map((user, indx) => {
return (
<div key={indx}>
<p >{user.name}</P>
<button
onClick={() => submit(user)}
type="button">
Delete
</button
</div
)
}}
</div>
)
)}
</div>
);
}
export default App;
Please how do can I delete a dependent by ID without refreshing the list/page/window to keep the user scrolling down to take more action(s) even after performing a delete action.

Why element is not getting rendered and how can I fix it?

I am trying to render some dynamic data in an element using useEffect Hook, which is not working.
Below is abc.tsx which has byTestOne(globalThis.test) which is responsible to send data back to us using globalThis.test value
const abc = () => {
const [data, setData] = useState<any>([]);
// globalThis.test is something that changes and when it changes, byTestOne triggers and get the data based on globalThis.test
useEffect(() => {
async function fetchData() {
const data = byTestOne(globalThis.test).then((data: any) => {
return data
})
setData(await data)
}
fetchData();
}, [globalThis.test]);
return (
<ImageBackground
source={require("../assets/images/abc.png")}
style={styles.bg}>
<View style={styles.containerHome}>
<CardStack>
{
// Below Element is not getting rendered
data.map((item: any) => (
<Card key={item.id}>
<CardItemForSwiper
name={item.name}
description={item.description}
/>
</Card>
)
)
}
</CardStack>
</View>
</ImageBackground>
);
};
export default ABC;
byTestOne() looks like this:
let data = [{
name: "test1",
description: "desc",
test: "one"
},
{
name: "test2",
description: "desc",
test: "two"
}]
export const byTestOne = (test: string) => {
const dataA = new Promise((resolve) => {
const filData = data.filter((rawData) => {
return rawData.test == test
})
if (filData.length > 0) {
globalThis.test = test
}
return resolve(filData)
})
return dataA
}

Get data from API by map function

I'm running into a problem that I've been working on for days and unfortunately I can't figure it out by myself. I'm trying to create a View which shows some information from an API. But every time I map this item, I want to do another API call which checks the live price of that product.
So I have for example some JSON data what I get from an API.
{
"id": 1,
"name": "test product",
"productid": "73827duf"
},
{
"id": 2,
"name": "test product2",
"productid": "734437dde"
}
So I show this data with the following code inside my application:
{item.products.map((products) => {
return (
<View
key={products.id}
>
<Text
style={{
fontSize: FONTS.body3,
paddingLeft: 10,
}}
>
{products.name}
{getProductPriceJumbo(
products.productid
)}
</Text>
</View>
);
})}
So I want to run every time a function which fetches data from another API. I'm sending the productID because that's the only information I need to call this API. You can see this function down below:
function getProductPriceJumbo(id) {
fetch("https://---/test.php?id=" + id + "/", {
method: "GET",
})
.then((response) => response.json())
.then((data) => {
return data[0].price;
});
}
So this fetch returns a big list with information about the product from a third party API. I only want to return the price, that's the reason why I only return the price value and I want to print this out on the view above. I can't really figure out how to do this. I get undefined from the function every time I run it. Hope someone can help me with this.
Create a new Price Component to display the price
function Price({ id }) {
const [price, setPrice] = useState(0);
useEffect(() => {
function getProductPriceJumbo(id) {
fetch("https://---/test.php?id=" + id + "/", {
method: "GET"
})
.then((response) => response.json())
.then((data) => {
setPrice(data[0].price);
});
}
getProductPriceJumbo(id);
},[]);
return <Text>{price}</Text>;
}
And your .map will become
{
item.products.map((products) => {
return (
<View key={products.id}>
<Text
style={{
fontSize: FONTS.body3,
paddingLeft: 10
}}
>
{products.name}
<Price id={products.productid} />
</Text>
</View>
);
});
}
The reason you are getting undefined is because the window is rendering before the function finishes running. You will have define an asynchronous function before you return your view.
const [data, setData] = useState([])
const [loading, setLoading] = useState(true);
useEffect(() => {
const fetchData = async () =>{
setLoading(true);
try {
const {data: response} = await axios.get('API URL');
setData(response);
} catch (error) {
console.error(error.message);
}
setLoading(false);
}
fetchData();
}, []);
Then you can use data[0].price;
You'll probably want to make your individual product into its own component that handles the fetching, and setting the price to a state value that's local to that product view. Here's a full example of how you could do that:
import { useState, useEffect } from "react";
const Product = ({ product }) => {
const [price, setPrice] = useState("Price loading...");
useEffect(() => {
fetch("https://---/test.php?id=" + product.productid + "/", {
method: "GET"
})
.then((response) => response.json())
.then((data) => {
setPrice(data[0].price);
});
}, [product]);
return (
<View>
<Text
style={{
fontSize: FONTS.body3,
paddingLeft: 10
}}
>
{product.name}
{price}
</Text>
</View>
);
};
const App = () => {
const item = {
products: [
{
id: 1,
name: "test product",
productid: "73827duf"
},
{
id: 2,
name: "test product2",
productid: "734437dde"
}
]
};
return (
<div>
{item.products.map((product) => (
<Product key={product.id} product={product} />
))}
</div>
);
};
Alternatively, you could use Promise.all to get all of the price values before mapping your products:
import { useState, useEffect } from "react";
const App = () => {
const [item] = useState({
products: [
{
id: 1,
name: "test product",
productid: "73827duf"
},
{
id: 2,
name: "test product2",
productid: "734437dde"
}
]
});
const [products, setProducts] = useState([]);
useEffect(() => {
Promise.all(
item.products.map(async (product) => {
const response = await fetch(
`https://---/test.php?id=${product.productid}/`
);
const data = await response.json();
return {
...product,
price: data[0].price
};
})
).then((products) => setProducts(products));
}, [item]);
return (
<div>
{products.map((product) => {
return (
<View key={product.id}>
<Text
style={{
fontSize: FONTS.body3,
paddingLeft: 10
}}
>
{product.name}
{product.price}
</Text>
</View>
);
})}
</div>
);
};

ReactJS: Manage multiple checkbox inputs with useState

I have the following example component that uses multiple checkboxes for choosing what items to remove from a list of objects:
import React, { useState } from "react";
import "./styles.css";
const data = [
{
name: "test1",
result: "pass"
},
{
name: "test2",
result: "pass"
},
{
name: "test3",
result: "pass"
},
{
name: "test4",
result: "pass"
},
{
name: "test5",
result: "pass"
}
];
export default function App() {
const [allChecked, setAllChecked] = useState(false);
const [isChecked, setIsChecked] = useState({});
const [formData, setFormData] = useState(data);
const handleAllCheck = e => {
setAllChecked(e.target.checked);
};
const handleSingleCheck = e => {
setIsChecked({ ...isChecked, [e.target.name]: e.target.checked });
};
const onDelete = () => {
console.log(isChecked);
const newData = data.filter(
item => !Object.keys(isChecked).includes(item.name)
);
console.log(newData);
setFormData(newData);
};
return (
<div className="App">
<div>
<label>All</label>
<input
name="checkall"
type="checkbox"
checked={allChecked}
onChange={handleAllCheck}
/>
<label />
</div>
{formData.map((test, index) => (
<div key={index}>
<label>{test.name}</label>
<input
type="checkbox"
name={test.name}
checked={allChecked ? true : isChecked[test.name]}
onChange={handleSingleCheck}
/>
</div>
))}
<button onClick={() => onDelete()}>DELETE</button>
</div>
);
}
This is mostly working, except for check all. It seems onChange will not update while using useState. I need to be able to select all the objects or uncheck some to mark for deletion.
Any help is greatly appreciated.
CodeSandbox Example: https://codesandbox.io/s/modest-hodgkin-kryco
UPDATE:
Okay, after some help from Richard Matsen,
Here is a new solution without direct DOM manipulation:
import React, { useState, useEffect } from "react";
import "./styles.css";
const data = [
{
name: "test1",
result: "pass"
},
{
name: "test2",
result: "pass"
},
{
name: "test3",
result: "pass"
},
{
name: "test4",
result: "pass"
},
{
name: "test5",
result: "pass"
}
];
export default function App() {
const [allChecked, setAllChecked] = useState(false);
const [isChecked, setIsChecked] = useState();
const [loading, setLoading] = useState(true);
const [formData, setFormData] = useState(data);
const handleAllCheck = e => {
setAllChecked(e.target.checked);
};
const handleSingleCheck = e => {
setIsChecked({ ...isChecked, [e.target.name]: e.target.checked });
};
const onDelete = () => {
const itemList = Object.keys(isChecked).map((key:any) => {
if (isChecked[key] === true) {
return key
}
})
const result = formData.filter((item:any) => !itemList.includes(item.name))
console.log(result)
setFormData(result)
}
useEffect(() => {
if (!loading) {
setIsChecked(current => {
const nextIsChecked = {}
Object.keys(current).forEach(key => {
nextIsChecked[key] = allChecked;
})
return nextIsChecked;
});
}
}, [allChecked, loading]);
useEffect(() => {
const initialIsChecked = data.reduce((acc,d) => {
acc[d.name] = false;
return acc;
}, {})
setIsChecked(initialIsChecked)
setLoading(false)
}, [loading])
return (
<div className="App">
<div>
<label>All</label>
<input
name="checkall"
type="checkbox"
checked={allChecked}
onChange={handleAllCheck}
/>
<label />
</div>
{!loading ? formData.map((test, index) => (
<div key={index}>
<label>{test.name}</label>
<input
type="checkbox"
name={test.name}
checked={isChecked[test.name]}
onChange={handleSingleCheck}
/>
</div>
)): null}
<button onClick={() => onDelete()}>DELETE</button>
</div>
);
}
codesandbox of working solution:
https://codesandbox.io/s/happy-rubin-5zfv3
The basic problem is checked={allChecked ? true : isChecked[test.name]} stops the unchecking action from happening - once allChecked is true it does not matter what value isChecked[test.name] has, the expression is always going to be true.
You should rely only on isChecked for the value, and treat changing allChecked as a side-effect.
useEffect(() => {
setIsChecked(current => {
const nextIsChecked = {}
Object.keys(current).forEach(key => {
nextIsChecked[key] = allChecked;
})
return nextIsChecked;
});
}, [allChecked]);
...
{formData.map((test, index) => (
<div key={index}>
<label>{test.name}</label>
<input
type="checkbox"
name={test.name}
checked={isChecked[test.name]}
onChange={handleSingleCheck}
/>
</div>
))}
There's also this warning cropping up
Warning: A component is changing an uncontrolled input of type checkbox to be controlled. Input elements should not switch from uncontrolled to controlled (or vice versa). Decide between using a controlled or uncontrolled input element for the lifetime of the component.
So that's basically saying don't initialize isChecked to {}, because the input's checked property is initially undefined. Use this instead,
{
test1: false,
test2: false,
test3: false,
test4: false,
test5: false,
}
or this way
const data = { ... }
const initialIsChecked = data.reduce((acc,d) => {
acc[d.name] = false;
return acc;
}, {})
export default function App() {
const [allChecked, setAllChecked] = useState(false);
const [isChecked, setIsChecked] = useState(initialIsChecked);
...
The problem with your code was how you were handling allChecked. I have made some changes to your code and it works now.
const data = [
{
name: "test1",
result: "pass"
},
{
name: "test2",
result: "pass"
},
{
name: "test3",
result: "pass"
},
{
name: "test4",
result: "pass"
},
{
name: "test5",
result: "pass"
}
];
function App() {
const [allChecked, setAllChecked] = useState(false);
// using an array to store the checked items
const [isChecked, setIsChecked] = useState([]);
const [formData, setFormData] = useState(data);
const handleAllCheck = e => {
if (allChecked) {
setAllChecked(false);
return setIsChecked([]);
}
setAllChecked(true);
return setIsChecked(formData.map(data => data.name));
};
const handleSingleCheck = e => {
const {name} = e.target;
if (isChecked.includes(name)) {
setIsChecked(isChecked.filter(checked_name => checked_name !== name));
return setAllChecked(false);
}
isChecked.push(name);
setIsChecked([...isChecked]);
setAllChecked(isChecked.length === formData.length)
};
const onDelete = () => {
const data_copy = [...formData];
isChecked.forEach( (checkedItem) => {
let index = formData.findIndex(d => d.name === checkedItem)
delete data_copy[index]
}
)
setIsChecked([])
// filtering out the empty elements from the array
setFormData(data_copy.filter(item => item));
setAllChecked(isChecked.length && isChecked.length === data.length);
};
return (
<div className="App">
<form>
<label>All</label>
<input
name="checkall"
type="checkbox"
checked={allChecked}
onChange={handleAllCheck}
/>
{ formData.map((test, index) => (
<div
key={index}
>
<label>{test.name}</label>
<input
type="checkbox"
name={test.name}
checked={isChecked.includes(test.name)}
onChange={handleSingleCheck}
/>
</div>
))
}
<label />
</form>
<button onClick={onDelete}>DELETE</button>
</div>
);
}
I think you should merge allChecked and isChecked state vars, because they represent the same thing, but your denormalizing it by creating two different vars! I suggest to keep isChecked, and modify all its entries when you press the allChecked input. Then, you can use a derived var allChecked (defined in your component or by using useMemo hook) to know if all your checks are checked or not.
Well, after some time working I came up with:
import React, { useState } from "react";
import "./styles.css";
import { useFormInputs } from "./checkHooks";
const data = [
{
name: "test1",
result: "pass"
},
{
name: "test2",
result: "pass"
},
{
name: "test3",
result: "pass"
},
{
name: "test4",
result: "pass"
},
{
name: "test5",
result: "pass"
}
];
export default function App() {
const [fields, handleFieldChange] = useFormInputs({
checkedAll: false
});
const allcheck = () => {
const checkdata = document.querySelectorAll(".checkers").length;
const numChecks = Array.from(new Array(checkdata), (x, i) => i);
numChecks.map(item => {
console.log(item);
async function checkThem() {
let element = await document.getElementsByClassName("checkers")[item];
element.click();
}
return checkThem();
});
};
return (
<div className="App">
<div>
<label>All</label>
<input name="checkall" type="checkbox" onChange={allcheck} />
<label />
</div>
{data.map((test, index) => (
<div key={index}>
<label>{test.name}</label>
<input
className="checkers"
type="checkbox"
name={test.name}
onChange={handleFieldChange}
/>
</div>
))}
</div>
);
}
Relevent codesandbox: https://codesandbox.io/s/admiring-waterfall-0vupo
Any suggestions welcomed. Also, thanks for the help guys!

Resources