Array data from Graphql is not populating a component in React - reactjs

I am trying to render multiple checkboxes that have two functions:
Show two states: checked and unchecked
Update a checked array with the checked checkboxes
Currently, I am successfully able to accomplish these two goals with dummy data:
const dummyPlayers = [
{ id: 1, name: 'Puppa' },
{ id: 2, name: 'Korvo' },
{ id: 3, name: 'Jesse' },
{ id: 4, name: 'Terry' },
{ id: 5, name: 'Gobblins' }
]
This is the shape of the array I want to populate the checkboxes with:
[
{
"id": "936d6050-00df-4bd4-bc54-6ce58ad0210c",
"name": "Travis",
"owner": "moralesfam",
"type": "Member",
"createdAt": "2021-09-24T20:08:02.292Z",
"updatedAt": "2021-09-24T20:08:02.292Z"
}...
]
However, when I start pulling data in from a database with Graphql, while I am able to render the checkboxes to the DOM, they are not interactive (don't show checked state) and don't log the checked checkboxes.
I bring in the data, an array of objects through a custom React hook, called useMembers and the data is stored in a members array. Console logging members prints out the array, but as soon as I swap the dummyPlayers for the members array, the two goals I stated earlier are unsuccessful.
// RecordGame.js
import React, { useState } from 'react'
import useLoadMembers from '../hooks/useLoadMembers'
import useUser from '../hooks/useUser'
function RecordGame() {
const dummyPlayers = [
{ id: 1, name: 'Puppa' },
{ id: 2, name: 'Korvo' },
{ id: 3, name: 'Jesse' },
{ id: 4, name: 'Terry' },
{ id: 5, name: 'Gobblins' },
]
const { members } = useLoadMembers(updateLoading)
const { user } = useUser()
const [checkedState, setCheckedState] = useState(
new Array(members.length).fill(false)
)
let playingPlayers = []
for (var index in checkedState) {
if (checkedState[index] === true) {
playingPlayers.push(dummyPlayers[index])
}
}
console.log(playingPlayers)
const handleOnChange = (position) => {
const updatedCheckedState = checkedState.map((player, index) =>
index === position ? !player : player
)
setCheckedState(updatedCheckedState)
}
// Rendered elements
const playerCheckboxes = dummyPlayers.map((player, index) => {
return (
<div key={index}>
<label htmlFor={player.name}>
<input
type="checkbox"
id={player.name}
name={player.name}
checked={checkedState[index]}
onChange={() => handleOnChange(index)}
/>
<span> {player.name}</span>
</label>
</div>
)
})
return (
<div>
<form>
{/* Game Players */}
<div>
<label htmlFor="players">
Who Played?
</label>
<div>{playerCheckboxes}</div>
</div>
</form>
</div>
)}
</Dashboard>
)
}
export default RecordGame
//useLoadMember.js
import { useState, useEffect } from 'react'
import { API, Auth } from 'aws-amplify'
import { listMembers } from '../graphql/queries'
const useLoadMembers = (updateLoading) => {
const [members, updateMembers] = useState([])
useEffect(() => {
fetchMembers()
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [])
const fetchMembers = async () => {
try {
let memberData = await API.graphql({
query: listMembers,
variables: { limit: 100 },
})
updateLoading(false)
let allMembers = memberData.data.listMembers.items
setFilteredMembers(allMembers)
} catch (err) {
console.error(err)
}
}
const setFilteredMembers = async (allMembers) => {
const { username } = await Auth.currentAuthenticatedUser()
const myMemberData = allMembers.filter((p) => p.owner === username)
updateMembers(myMemberData)
}
return { members }
}
export default useLoadMembers
In this first, picture, I used the dummyPlayers array and got the results I wanted.
However, in this second screenshot, I replaced the dummyData with the members array and did not get any results I wanted.
I'm just confused on why I am getting different results with the same array shape.

wherever you use members you will need to check that members is not undefined before it gets used. If the api calls are not complete, it will get set initially as undefined.
eg: members && members.map(() => ....)

Related

React nested items rerender not working as expected

im trying to develop component that will sort new items, depending if item parent is or is not present on the list already. Components can be nested in each other to unlimited depth. Parent have list of children, children have parentId. Now, it works as expected at the first render, but when new item appear on the list (its added by user, using form, up in the structure), it does in fact make its way to components list, but is not shown on the screen until page reload. I can see temporary list that is used to make all calculations have the item as expected in the nested structure. Then i set state list to value of temp, but its not working, and i dont know why. Im quite new to react stuff. In act of desperation i even tried to destructure root parent of the item, hoping it will force rerender, but that didnt worked too. Anybody could help with this?
http://jsfiddle.net/zkfj03um/13/
import React, { useState } from 'react';
function Component(props) {
const [component, setComponent] = useState(props.component);
return (
<div>
{component.id};
{component.name};
<ul>
{component.subcomps && component.subcomps.map((comp) =>
<li key={comp.id} style={{ textAlign: 'left' }}>
<Component component={comp}
id={comp.id}
name={comp.name}
parentId={comp.parentId}
subcomps={comp.subcomps}
/>
</li>)}
</ul>
</div>
);
}
function ComponentsList(props) {
const newComponents = props.newComponents;
const [filteredComponents, setFilteredComponents] = useState();
function deepSearch(collection, key, value, path=[]) {
for (const o of collection) {
for (const [k, v] of Object.entries(o)) {
if (k === key && v === value) {
return {path: path.concat(o), object: o};
}
if (Array.isArray(v)) {
const _o = deepSearch(v, key, value, path.concat(o));
if (_o) {
return _o;
}
}
}
}
}
async function filter() {
let temp = [];
await newComponents.forEach((comp) => {
//parent may be, or may not be on the list. Its not necesary
const parentTuple = deepSearch(filteredComponents, 'id', comp.parentId);
if (!parentTuple) {
//create parent substitute logic
} else {
const parent = parentTuple.object;
const root = parentTuple.path[0];
const mutReplies = [comm, ...parent.replies];
parent.replies = mutReplies;
temp = [{...root}, ...temp]
}
})
setFilteredComponents([...temp])
}
useEffect(() => {
setLoading(false);
}, [filteredComponents]);
useEffect(() => {
setLoading(true);
filter();
}, [newComponents]);
return (<>
{!loading && filteredComponents.map((component, index) =>
<li key={index}>
<Component component={component} />
</li>
)}
</>);
}
const items = [
{ id: 1, name: 'sample1', subcomps: [{ id: 5, name: 'subcomp1', parentId: 1, subcomps: [] }] },
{
id: 2, name: 'sample2', subcomps: [
{ id: 6, name: 'subcomp2', subcomps: [], parentId: 2 },
{ id: 7, name: 'subcomp3', subcomps: [], parentId: 2 }
]
},
]
ReactDOM.render(<ComponentsList newComponents={items} />, document.querySelector("#app"))

Replacing value in nested state with react functional components

I am trying to use update state in a react function component but it is not working. I tried following a tutorial on pluralsite and apply it to my own project. Ideally this code should be finding the product based on the ID number and replacing the total with a new value.
Unfortunately I am getting an error saying that 'productData.find' is not a function and I'm not sure where the code being used for that is. Are there any suggestions on how to solve this issue?
This is what the data looks like. In this example I am saving the first element of the array.
export let data = [
{
name: "Name",
description:
"",
products: [
{
id: 1,
name: "Name 1",
material: 1.05,
time: 25,
total: 0,
},
{
id: 2,
name: "Name 2",
material: 3,
time: 252,
total: 0,
},
],
},
...
];
function CompareCard({}) {
const index = 0;
const [productData, setProductData] = useState(data[index]);
function setTotalUpdate(id) {
const productPrevious = productData.find(function (rec) {
return rec.id === id;
});
const productUpdated = {
...productPrevious,
total: 1,
};
const productNew = productData.map(function (rec) {
return rec.id === id ? productUpdated : rec;
});
setProductData(productNew);
}
setTotalUpdate(1)
}
It's because productData is not an array so .find would not work. You want iterate over the products property in your data, so do productData.products.find(...)
When you do
const [productData, setProductData] = useState(data[index])
you don't pass an Array on your state but an Object (the first element of your data so an Object) and Object don't have find method.
Try
const [productData, setProductData] = useState([data[index]])
with [] on our useState to put your Object on array
///////////////////////////////// Edit /////////////
Ok, I try your code, and I propose you this.
import React, { useState } from "react";
const data = [
{
name: "Name",
description: "",
products: [
{
id: 1,
name: "Name 1",
material: 1.05,
time: 25,
total: 0,
},
{
id: 2,
name: "Name 2",
material: 3,
time: 252,
total: 0,
},
],
},
];
const CompareCard = () => {
// setState with the subArray products from data[0], I use '...' (spread operator) inside a Array or an Object to make a shalow copy
const [productsData, setProductsData] = useState([...data[0].products]);
const setTotalUpdate = (id) => {
// find the product to change inside products collection, that's ok
const productPrevious = productsData.find((rec) => {
return rec.id === id;
});
// create a new product to change one property's value, you can do something like 'productPrevious.total = 1', same same
const productUpdated = {
...productPrevious,
total: 1,
};
// create a new products collection to update state
const productNew = productsData.map((rec) => {
return rec.id === id ? productUpdated : rec;
});
setProductsData([...productNew]);
};
const setTotalUpdateSecond = (id) => {
// create a newState
const newState = productsData.map((product) => {
// condition if id === productId and do something
if (id === product.id) {
product.total = 1;
}
// both case, I return the product (change or not)
return product;
});
setProductsData([...newState]);
};
return (
<>
<button onClick={() => setTotalUpdate(1)}>Test old method on product 1</button>
<button onClick={() => setTotalUpdateSecond(2)}>Test second method on product 2</button>
{productsData.map((product) => {
return (
<>
<p>Product Id : {product.id} / Product Total : {product.total}</p>
</>
);
})}
</>
);
};
export default CompareCard;
Can you copy / past this, try and say me if it's what you want, if yes, I explain you where the confusion was. If not, explain me, what's the problem here and I modificate.

How can we index the keys from nested array of objects to our input set post data?

I'm trying to set course attribute from parameter to my input so that I can be able the insert the value on it but I cannot access the course attribute. ends_level and course becomes any instead of string means I haven't indexed properly the ends_level which the array of object course. I really need your eyes to see something that have missed or missed up. I hope I have explained it well. Thanks in advance y'all.
import { React, useState, useEffect } from 'react';
import Button from 'react-bootstrap/Button';
import { TextField } from '#material-ui/core';
import { useDispatch, useSelector } from 'react-redux';
import { updateProfile } from '../../../actions/profile';
const Profile = ({ data }) => {
const dispatch = useDispatch();
const currentId = data._id;
const [postData, setPostData] = useState(
{
profile: {
name: "",
},
skills: [
{
end: "",
ends_level: [
{
course: "",
level: ""
}
]
},
],
}
);
const profile = useSelector((state) => currentId ? state.profile.find((p) => p._id === currentId) : null);
useEffect(() => {
if(profile) setPostData(profile);
}, [profile])
const handleSubmit = (e) => {
e.preventDefault();
if(currentId) {
dispatch(updateProfile(currentId, postData));
}
}
return (
<form onSubmit={handleSubmit}>
<TextField
value={postData.profile.name}
onChange={(e) =>
setPostData(
{
...postData, profile:
{
...postData.profile, name: e.target.value
}
}
)
}
/>
<TextField
onChange={(e) =>
setPostData(
{
...postData, skills:
{
...postData.skills, ends_level:
{
...postData.skills.ends_level, course: e.target.value
}
}
}
)
}
/>
<Button variant="primary" size="md" type="submit" block >Save</Button>
</form>
);
}
export default Profile;
The problem seems to be you are using spread operators to spread objects into positions where you originally had arrays. Also when trying to sort out an answer, I feel like you aren't addressing a plurality problem with these arrays. Namely, you have a single target value which may apply to an array full of skills and an array full of ends_levels in each skill, so you have to decide how/what you want the new changed to value to apply to.
Here is a good start, with TypeScript enhancements, that lays out the schema and attempts to craft a new "post data" object which looks like the old one. You will have to fill out the commented section below to affect the array(s) with the new value as you see fit.
interface SkillType {
end: string,
ends_level: [
{
course: string,
level: string
}
]
}
interface DataType {
profile: {
name: string
},
skills: SkillType[]
}
let postData: DataType = {
profile: {
name: "",
},
skills: [
{
end: "",
ends_level: [
{
course: "",
level: ""
}
]
},
],
};
// Create a new holder with all the old skills
let newSkills : SkillType[] = [... postData.skills ];
// Will have multiple skills per post-data
// Each post-data will have multiple "ends_levels" in it
// Do you want to merge it so all of these blocks now have the new value e.target.value ??
// If so, manipulate newSkills
let newPostData: DataType = {
...postData,
skills: newSkills
}

I want to process setState at once

enter image description here
I want to count "indie" and "action" at the same time when the button is clicked. However, the only real application is "action". Please tell me how.
This is my solution to your problem
import React, { useState, useEffect } from "react";
const games = [
{ id: 1, genre: ["indie", "action"] },
{ id: 2, genre: ["indie"] },
{ id: 3, genre: ["action"] }
];
function ButtonComponent(props) {
const { genre, fn } = props;
return <button onClick={() => fn(genre)}>Click</button>;
}
function TestPage() {
const [genre, setGenre] = useState({ indie: 0, action: 0 });
const addGenrecount = (genres) => {
setGenre((previousState) => {
let { indie, action } = previousState;
genres.forEach((genre) => {
if (genre === "indie") indie = indie + 1;
if (genre === "action") action = action + 1;
});
return { indie, action };
});
};
useEffect(() => console.log("genre", genre), [genre]); // Logs to the console when genre change
return games.map((game) => {
const { id, genre } = game;
return <ButtonComponent key={id} genre={genre} fn={addGenrecount} />;
});
}
export default TestPage;
You may also go to codesandbox to test the demo
https://codesandbox.io/s/xenodochial-dirac-q01h4?file=/src/App.js:0-968
Just Friendly Tip:
If you need help regarding react I recommend to upload your code to codesandbox so that we can easily reproduce or solve the problem

Update item in state onClick ReactJS

So, I have class Comopnent :
state = {
tokens: [
{
name: "first",
value: 3
},
{
name: "second",
value: 2
},
{
name: "third",
value: 4
}
]
}
handleClick = (name, id) => {
const newState = this.state.tokens.map((token => {
console.log(token.name)
}))
}
render() {
const token = this.state.tokens;
const tokenList = token.map(t => {
return (
<div value={t.name} onClick={() => this.handleClick(t.name, t.value)}>
<img src=""/>
</div>
)
})
What i need to do - after click - to subtract 1 from value clicked token.
So - ex. after click on "First" token i want his value equal 2.
So far I've done just as much as the above.
I do not know how to go about it, i am new in ReactJS, so thanks for help in advance!
You'll have to find in your state in tokens array the object which has the same name as the argument passed in the onclick handler. Then you will have to change it's value - decrement it (value--) but you have to be aware that you can't mutate the state.
handleClick = name => () => {
const { tokens } = this.state;
const clickedToken = tokens.find(token => token.name === name);
clickedToken.value--;
const clickedTokenIndex = tokens.indexOf(clickedToken);
const newTokens = [
...tokens.slice(0, clickedTokenIndex),
clickedToken,
...tokens.slice(clickedTokenIndex + 1)
];
this.setState({ tokens: newTokens });
};
Codesandbox link: https://codesandbox.io/s/92yz34x97w
First, some things are wrong with your code.
1- You have an array of tokens, then you're mapping the list, but you don't have a key to index, this will cause weird behaviors, I improve your tokens list with keys now.
2.- You can handle the click and change the state of the tokens list, this will trigger a reload of the component.
state = {
tokens: [
{
name: "first",
value: 3,
id: 1
},
{
name: "second",
value: 2,
id: 2
},
{
name: "third",
value: 4,
id: 3
}
]
}
handleClick = (name, id) => {
const { tokens} = this.state;
const newState = tokens.map((token => {
if(token.id === id) {
token.value--;
}
return token;
}))
}
render() {
const token = this.state.tokens;
const tokenList = token.map(t => {
return (
<div key={t.key} value={t.name} onClick={() => this.handleClick(t.name, t.value, t.key)}>
<img src=""/>
</div>
)
})

Resources