React Loop over array within object - javascript

Consider the following JSON:
const JSON = [
{
"Name":"Ben Davies",
"JobsList":[
{
"Company":"Company1",
"Date":"01/01/01",
},
{
"Company":"Company2",
"Date":"01/01/01",
},
{
"Company":"Company3",
"Date":"01/01/01",
}
]
}
]
I want to pass the "Jobs" into a new component on the page, like so:
const [jobs] = useState(JSON);
return(
<CareerList
jobs={jobs}
/>
);
Now on the CareerList component, if I check the length of the jobs prop, I only get 1, I understand that this is because I'm not looping through the array, so how do I loop through the array? How do I make the length the desired 3 in this case.
const CareerList = (props) => {
const allJobs = props.jobs;
console.log(allJobs.length)
}
Thanks in advance.

If you only want the JobsList then just pass that to the useState as follows:
const JSON = [
{
"Name":"Ben Davies",
"JobsList":[
{
"Company":"Company1",
"Date":"01/01/01",
},
{
"Company":"Company2",
"Date":"01/01/01",
},
{
"Company":"Company3",
"Date":"01/01/01",
}
]
}
]
const JobsList = JSON[0].JobsList
const [jobs, setJobs] = useState(JobsList);
Although I would recommend that you do not rename JobsList to jobs. Just keep it the same name.

Related

When I import a recoil selectorFamily, it does not get data as expected

I'm having a recoil issue that I'm sure is pretty basic--but like, so basic they didn't bother explaining it in the docs.
In file 1, I have the following code:
export const questionState = atomFamily({
key: "question",
default: { question: "", answers: [] },
effects_UNSTABLE: (id) => [
({ onSet, setSelf }) => {
setSelf(cachedQuestionsAPI.getItem(id, "question"));
onSet((question) => {
updateServerItem(question, "question");
});
},
],
});
export const correctAnswersState = selectorFamily({
key: "correctAnswers",
default: [],
get:
(id) =>
({ get }) => {
const question = get(questionState(id));
return question.answers?.filter((answer) => answer.is_correct);
},
});
export default function Question({ id, questionType }) {
const question = useRecoilValue(questionState(id));
const correctAnswers = useRecoilValue(correctAnswersState(id));
console.log(correctAnswers);
return (
<div>
TEST
</div>
);
}
console.log(correctAnswers) here gives the expected result HOWEVER, when I import it to a sibling file, it yields an empty array. Here's that code:
import { atom, useRecoilState, useRecoilValue, selectorFamily } from "recoil";
import { correctAnswersState, questionState } from "../Question";
export default function FillInTheBlank({ onSet, question }) {
const correctAnswers = useRecoilValue(correctAnswersState(question.id));
console.log(correctAnswers);
return (<div>TEST</div>)
}
I'm just confused bc I thought that when I imported that correctAnswersState selectorFamily into my other file, it was essentially a pointer to the same stateful data. There's clearly some conceptual recoil thing I just don't get here.
Oddly enough, I have this code in that second file, which DOES work:
const correctAnswersStringsState = selectorFamily({
key: "correctAnswerStrings",
default: [],
get:
(id) =>
({ get }) => {
const question = get(questionState(id));
const correctAnswers = get(correctAnswersState(question.id));
const correctAnswerStrings = correctAnswers?.map(
(answer) => answer.answer
);
return correctAnswerStrings;
},
});
But it doesn't work if I take out the get(questionState(id)) call, even though get(correctAnswersState(question.id)) calls it too.

create a new array, removing duplicates and setting state with React

I have a MenuOptions component that I pass an options prop to. Options is a large array of objects. Each object has a nested array called 'services' inside services is an object with a key 'serviceType' which is the only value I want. I want to take all those values, push them into a new array and remove any duplicates if there are any, and then map through that new array and display each item in an 'option' html tag.
here is my createArray function:
const createNewArray = () => {
let optionsArr = []
let uniqueArr;
options.map((option) => {
option.services.map((service) => optionsArr.push(service.serviceType))
})
uniqueArr = [...new Set(optionsArr)];
return uniqueArr;
}
uniqArr seems to be holding what I want but now I want to set this to a piece of global state. Trying something like this does not work. array seems to still be set as null
const [array, setArray] = useState(null)
useEffect(() => {
setArray(createNewArray())
}, [])
any solutions? Thanks
1) You should add your array state initial value as an empty array:
const [array, setArray] = useState([]);
Live Demo
2) You can simplify the creating of a new array as:
const createNewArray = () => [
...new Set(options.flatMap((o) => o.services.map((obj) => obj.serviceType)))
];
3) set array state in useEffect as:
useEffect(() => {
setArray(createNewArray());
}, []);
From your description is this your data?
const options = [{
services: [
{
serviceType: 'serviceType',
}
],
},{
services: [
{
serviceType: 'serviceType',
}
],
},{
services: [
{
serviceType: 'serviceType',
}
],
},
{
services: [
{
serviceType: 'serviceType',
}
],
}]
here is the solution
const uniq = (a) => [...new Set(a)];
const createNewArray = (array) => {
const c = [...array]
const newArray = []
for (let i = 0; i < c.length; i++) {
const e = c[i];
for (let ii = 0; ii < e.length; ii++) {
const ee = e[ii].serviceType;
newArray.push(ee);
}
}
const toReturn = uniq(newArray)
return toReturn;
}
If you want unique options, just pass the options in and set them to the state after you massage the data.
const { useEffect, useMemo, useState } = React;
const unique = (arr) => [...new Set(arr)];
const uniqueOptions = (options) =>
unique(options.flatMap(({ services }) =>
services.map(({ serviceType }) => serviceType)));
const data = {
options: [
{ services: [{ serviceType: "Home" } , { serviceType: "About" }] },
{ services: [{ serviceType: "About" } , { serviceType: "Help" }] },
{ services: [{ serviceType: "Help" } , { serviceType: "Home" }] },
],
};
const MenuOptions = (props) => {
const { options } = props;
const [opts, setOpts] = useState([]);
useEffect(() => setOpts(uniqueOptions(options)), [options]);
return useMemo(
() => (
<select>
{opts.map((opt) => (
<option key={opt} value={opt}>
{opt}
</option>
))}
</select>
),
[opts]
);
};
const App = ({ title }) =>
useMemo(
() => (
<div>
<h1>Services</h1>
<form>
<MenuOptions options={data.options} />
</form>
</div>
),
[]
);
ReactDOM.render(<App />, document.getElementById("react-app"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.1/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.1/umd/react-dom.production.min.js"></script>
<div id="react-app"></div>

React + TS: How to call a method from outside of a React Functional Component

Im wondering how I can call a method from outside of a React Functional Component. I wrote the function GetUsedLockers() which gets all the used lockers and returns amount. Now I want to call this function from another another component (OrgLocker.tsx) and display the data from the getUsedLockers() function there.
OrgLockerTables.tsx
const OrgLockerTables: React.FC = () => {
const lockerCall = 'lockers';
const [lockerData, setLockerData] = useState({
id: 0,
guid: "",
is_currently_claimable: false
}[""]);
useEffect(() => {
componentConsole().then((res) => {
setLockerData(res);
})
// eslint-disable-next-line
}, []);
if (!lockerData) return (<div>Loading...</div>);
//function to get all used lockers
function getUsedLockers() {
let amount = 0;
for (let i = 0; i < lockerData.length; i++) {
if (!lockerData.is_currently_claimable) {
amount++;
}
}
console.log('log from getusedlockers, amount: ', amount)
return (amount)
}
// function to get JSON data from the API
function componentConsole(): Promise<any> {
return new Promise<any>((resolve, reject) => {
http.getRequest('/' + lockerCall).then((res) => {
let data = res.data.data;
console.log('data:', data);
resolve(res.data.data);
}).catch((error) => {
console.log(error);
reject();
});
})
}
}
OrgLocker.tsx
import OrgLockerTables from '../tables/orgLockerTables';
const OrgLockers: React.FC = () => {
let lockerTable = new OrgLockerTables();
return (
<div className="main-div-org">
<p>Used</p>
<p>{lockerTable.getUsedLockers()}</p>
</div>
);
}
export default OrgLockers;
When trying to make a call to OrgLockerTables and storing it in the lockerTable let it gives the following error:
Expected 1-2 arguments, but got 0.ts(2554)
Any help would be greatly appreciated!
I've restructured everything making it more understandable, I hope you don't mind according to what I think you want the comment above.
locker-model.ts - The type for the particular data being called back is found
export type Locker = {
id: number;
guid: string;
isCurrentlyClaimable: boolean;
}
locker-business.ts - Where all the business logic is carried out, from the call for data to the calculation based on it
import { Locker } from "./locker-models";
const lockerCall = 'lockers';
const mockedData: Locker[] = [{
id: 0,
guid: "sample",
isCurrentlyClaimable: false,
},
{
id: 1,
guid: "sample2",
isCurrentlyClaimable: true,
},
{
id: 2,
guid: "sample3",
isCurrentlyClaimable: true,
}]
// Mocked function from your backend (componentConsole where you use lockerCall variable)
export const getLockersData = (): Promise<Locker[]> => Promise.resolve(mockedData);
export const getAmount = (lockers: Locker[]): number => {
let amount = 0;
!!lockers ?
lockers.filter(({isCurrentlyClaimable}) => { if(isCurrentlyClaimable) amount++ })
: 0;
return amount;
};
index.tsx - Here are both components that make the call to get the data and render the result you're looking for
import React, { Component } from 'react';
import { Locker } from './locker-models';
import { getLockersData, getAmount } from './locker-business';
import './style.css';
type OrgLockersProps = {
amount: number;
}
const OrgLockers: React.FC<OrgLockersProps> = ({ amount }) => {
return (
<div className="main-div-org">
<p>Lockers used:</p>
<p>{amount}</p>
</div>
);
}
type OrgLockerTableProps = {};
const OrgLockerTable : React.FC<OrgLockerTableProps> = props => {
const [lockerData, setLockerData] = React.useState<Locker[]>([]);
React.useEffect(() => {
getLockersData().then(response => setLockerData(response));
}, []);
const amount = getAmount(lockerData);
return (
<div>
<OrgLockers amount={amount} />
</div>
);
};
You can see the example here
You can create new .js file like Helpers.js and define export function with parameter it like that
export function getUsedLockers(lockerData) {
let amount = 0;
//Check your loop it can be like that
for (let i = 0; i < lockerData.length; i++) {
if (!lockerData[i].is_currently_claimable) {
amount++;
}
}
console.log('log from getusedlockers, amount: ', amount)
return (amount)
}
Then import it where do you want to use.
import {getUsedLockers} from "../Helpers";
And use it like that:
const amount = getUsedLockers(data);

Appending fetched data

I'm trying to build a treeview component in react where data for the tree is fetched based on the nodes expanded by the user.
Problem
I want to replace the code inside handleChange with data from my server, so that I append the data i fetch to the tree state. How can I achieve this with react?
The data i get can look like this:
{
"children": [
{
"id": "2212",
"parentId": "3321",
"name": "R&D",
"address": "homestreet"
},
{
"id": "4212",
"parentId": "3321",
"name": "Testing",
"address": "homestreet"
}
]
}
My Code
import React, { useState } from "react";
import { makeStyles } from "#material-ui/core/styles";
import TreeView from "#material-ui/lab/TreeView";
import ExpandMoreIcon from "#material-ui/icons/ExpandMore";
import ChevronRightIcon from "#material-ui/icons/ChevronRight";
import TreeItem from "#material-ui/lab/TreeItem";
const useStyles = makeStyles({
root: {
height: 216,
flexGrow: 1,
maxWidth: 400
}
});
export default function FileSystemNavigator() {
const classes = useStyles();
const initialData = {
root: [
{
id: "1",
label: "Applications"
}
],
};
const [tree, setTree] = useState(initialData);
const handleChange = (event, nodeId) => {
setTimeout(() => {
const newTree = {
...tree,
[nodeId]: [
{
id: "2",
label: "Calendar"
},
{
id: "3",
label: "Settings"
},
{
id: "4",
label: "Music"
}
]
};
setTree(newTree);
}, 1000); // simulate xhr
};
const renderTree = children => {
return children.map(child => {
const childrenNodes =
tree[child.id] && tree[child.id].length > 0
? renderTree(tree[child.id])
: [<div />];
return (
<TreeItem key={child.id} nodeId={child.id} label={child.label}>
{childrenNodes}
</TreeItem>
);
});
};
return (
<TreeView
className={classes.root}
defaultCollapseIcon={<ExpandMoreIcon />}
defaultExpandIcon={<ChevronRightIcon />}
onNodeToggle={handleChange}
>
{renderTree(tree.root)}
</TreeView>
);
}
If I am understanding correctly, you want to replace your "fake" setTimeout implementation of an API call with a real call using fetch.
In this case, it's as simple as calling fetch inside of the handleChange handler and updating your state with new items that you get back as a result.
function FileSystemNavigator() {
const initialData = {...}
const [tree, setTree] = React.useState(initialData)
const handleChange = (event, nodeId) => {
const handleResult = (data) => {
const items = data.children.map(item => {
return { id: item.id, label: item.name }
})
setTree({
root: [...tree.root, ...items]
})
}
const handleError = (error) => {
// handle errors appropriately
console.error(error.message)
}
fetch("https://api.myjson.com/bins/1aqhsc")
.then(res => res.json())
.then(handleResult)
.catch(handleError)
}
// ...
return (...)
}
This should do the trick.
Note that I've used your sample API endpoint that you've provided in the comments, so you will have to change the handleResult callback inside of the handleChange handler to make sure you're parsing out your new data appropriately.
If you'd like to see a quick example, I created a CodeSandbox with a button that can be clicked to fetch more data and display it in a list:
Demo
Let me know if you have any questions.

Toggle item in an array react

I want to toggle a property of an object in an array. The array looks as follows. This is being used in a react component and When a user clicks on a button I want to toggle the winner.
const initialFixtures = [{
teams: {
home: 'Liverpool',
away: 'Manchester Utd'
},
winner: 'Liverpool'
},
{
teams: {
home: 'Chelsea',
away: 'Fulham'
},
winner: 'Fulham'
}, ,
{
teams: {
home: 'Arsenal',
away: 'Tottenham'
},
winner: 'Arsenal'
}
];
My react code looks something like this
function Parent = () => {
const [fixtures, setUpdateFixtures] = useState(initialFixtures)
const toggleWinner = (index) => {
const updatedFixtures = fixtures.map((fixture, i) => {
if (i === index) {
return {
...fixture,
winner: fixture.winner === home ? away : home,
};
} else {
return fixture;
}
})
setUpdateFixtures(updatedFixtures);
}
return <Fixtures fixtures={fixtures} toggleWinner={toggleWinner} />;
}
function Fixtures = ({ fixtures, toggleWinner }) => {
fixtures.map((fixture, index) => (
<div>
<p>{fixture.winner} </p>
<button onClick = {() => toggleWinner(index)}> Change Winner</button>
</div>
))
}
the code works but it feels like it is a bit too much. I am sure there is a better more succinct way of doing this. Can anyone advise? I do need to pass the fixtures in from the parent of the Fixture component for architectural reasons.
const updatedFixtures = [...fixtures];
const fixture = updatedFixtures[i];
updatedFixtures[i] = {
...fixture,
winner: fixture.winner === fixture.teams.home ? fixture.teams.away : fixture.teams.home,
};
You can slice the fixtures array into three parts:
from 0 to index: fixtures.slice(0, index). This part is moved to the new array intact.
The single item at index. This part/item is thrown away because of being changed and a new item is substituted.
The rest of the array: fixtures.slice(index + 1).
Next, put them into a new array:
const newFixtures = [
...fixtures.slice(0, index), // part 1
{/* new item at 'index' */}, // part 2
...fixtures.slice(index + 1) // part 3
];
To construct the new item:
Using spread operator:
const newFixture = {
...oldFixture,
winner: /* new value */
};
Using Object.assign:
const newFixture = Object.assign({}, oldFixture, {
winner: /* new value */
});
if you write your code in such a way - this will do the job.
const toggleWinner = index => {
const { winner, teams: { home, away } } = fixtures[index];
fixtures[index].winner = winner === home ? away : home;
setUpdateFixtures([...fixtures]);
};
Setting a new array of fixtures to state is completely enough to trigger render on Fixtures component.
I have made a working example for you.
You can use libraries like immer to update nested states easily.
const initialFixtures = [{
teams: {
home: 'Liverpool',
away: 'Manchester Utd'
},
winner: 'Liverpool'
},
{
teams: {
home: 'Chelsea',
away: 'Fulham'
},
winner: 'Fulham'
}, ,
{
teams: {
home: 'Arsenal',
away: 'Tottenham'
},
winner: 'Arsenal'
}
];
const newState = immer.default(initialFixtures, draft => {
draft[1].winner = "something";
});
console.log(newState);
<script src="https://cdn.jsdelivr.net/npm/immer#1.0.1/dist/immer.umd.js"></script>
If you are comfortable to use a class based approach, you can try something like this:
Create a class that holds property value for team.
Create a boolean property in this class, say isHomeWinner. This property will decide the winner.
Then create a getter property winner which will lookup this.isHomeWinner and will give necessary value.
This will enable you to have a clean toggle function: this.isHomeWinner = !this.isHomeWinner.
You can also write your toggleWinner as:
const toggleWinner = (index) => {
const newArr = initialFixtures.slice();
newArr[index].toggle();
return newArr;
};
This looks clean and declarative. Note, if immutability is necessary then only this is required. If you are comfortable with mutating values, just pass fixture.toggle to your react component. You may need to bind context, but that should work as well.
So it would look something like:
function Fixtures = ({ fixtures, toggleWinner }) => {
fixtures.map((fixture, index) => (
<div>
<p>{fixture.winner} </p>
<button onClick = {() => fixture.toggle() }> Change Winner</button>
// or
// <button onClick = { fixture.toggle.bind(fixture) }> Change Winner</button>
</div>
))
}
Following is a sample of class and its use:
class Fixtures {
constructor(home, away, isHomeWinner) {
this.team = {
home,
away
};
this.isHomeWinner = isHomeWinner === undefined ? true : isHomeWinner;
}
get winner() {
return this.isHomeWinner ? this.team.home : this.team.away;
}
toggle() {
this.isHomeWinner = !this.isHomeWinner
}
}
let initialFixtures = [
new Fixtures('Liverpool', 'Manchester Utd'),
new Fixtures('Chelsea', 'Fulham', false),
new Fixtures('Arsenal', 'Tottenham'),
];
const toggleWinner = (index) => {
const newArr = initialFixtures.slice();
newArr[index].toggle();
return newArr;
};
initialFixtures.forEach((fixture) => console.log(fixture.winner))
console.log('----------------')
initialFixtures = toggleWinner(1);
initialFixtures.forEach((fixture) => console.log(fixture.winner))
initialFixtures = toggleWinner(2);
console.log('----------------')
initialFixtures.forEach((fixture) => console.log(fixture.winner))
const toggleWinner = (index) => {
let updatedFixtures = [...fixtures].splice(index, 1, {...fixtures[index],
winner: fixtures[index].winner === fixtures[index].teams.home
? fixtures[index].teams.away : fixtures[index].teams.home})
setUpdateFixtures(updatedFixtures);
}

Categories