I have a react application for which I'm trying to show the average salary for each employee that I am getting from my data.json file. I am able to output all the salaries but unsure of how to get the average of all the salaries on the screen. I'm pretty new to React so any help would be greatly appreciated.
import React,{useState,useEffect} from 'react';
import './App.css';
function App() {
const [data,setData]=useState([]);
const getData=()=>{
fetch('data.json'
,{
headers : {
'Content-Type': 'application/json',
'Accept': 'application/json'
}
}
)
.then(function(response){
// console.log(response)
return response.json();
})
.then(function(myJson) {
setData(myJson)
});
}
useEffect(()=>{
getData()
},[])
const avg = () => {
return console.log(data);
}
return (
<div className="App">
{data && data.length>0 && data.map((item)=><p>{item.Salary}</p>)}
</div>
);
}
export default App;
You can use the useMemo hook for this. It could look sth like this:
const avg = useMemo(() => {
if (data.length === 0) {
return 0;
}
return data.reduce((sum, item) => sum + item.Salary, 0) / data.length;
}, [data])
The memo hook receives a dependency array. In this case, it is [data]. Whenever a value in the dependency array changes, the useMemo hook is recomputed and the value is accessible via the constant.
You don't want to duplicate all of that code every time you need a bit of JSON in your components. You can begin by writing a generic useAsync custom hook -
// hooks.js
import { useState, useEffect } from React
const useAsync = (runAsync, deps = []) => {
const [loading, setLoading] = useState(true)
const [error, setError] = useState(null)
const [result, setResult] = useState(null)
useEffect(_ => {
Promise.resolve(runAsync(...deps))
.then(setResult, setError)
.finally(_ => setLoading(false))
}, deps)
return { loading, error, result }
}
export { useAsync }
You're excited so you start using it right away -
// MyComponent.js
import { useAsync } from "./hooks.js"
const MyComponent = () => {
const { loading, error, result:items } =
useAsync(_ => {
axios.get("path/to/json")
.then(res => res.json())
}, ...)
// ...
}
export default MyComponent
But stop there. Write more useful hooks when you need them -
// hooks.js (continued)
const useAysnc = //...
const fetchJson = (url) =>
fetch(url, {
headers : {
'Content-Type': 'application/json',
'Accept': 'application/json'
}
})
.then(r => r.json())
const useJson = (url) =>
useAsync(fetchJson, [url])
export { useAsync, useJson }
Now it's dead-simple to fetch JSON in any component. Reusable generic functions reduces the chance you introduce bugs to your own code. And you get all the possible states: loading, error, or result -
// MyComponent.js
import { useJson } from "./hooks.js"
const MyComponent = () => {
const { loading, error, result:items } =
useJson("path/to/json")
if (loading)
return <p>Loading...</p>
if (error)
return <p>Error: {error.message}</p>
return <div><Items items={items} /></div> // example use of `items`
}
export default MyComponent
Caclculating average is a matter of using another generic function, sum -
// stats.js
const average = a =>
sum(a) / a.length
const sum = a =>
a.reduce((s, v) => s + v, 0)
export { average, sum }
Noticing a pattern? Isolate behaviour and write reusable functions. Import them where needed. Here's how we use average in your component -
// MyComponent.js
import { average } from "./stats.js"
import { useJson } from "./hooks.js"
const MyComponent = () => {
// useJson...
<div>Average: {average(items.map(v => v.Salary))}</div>
}
export default MyComponent
You need to find the sum of all salaries and then find the average of it.
let salaries=[2,3,8,1];
let sum=0;
for(let i=0;i<salaries.length;i++){
sum=sum+salaries[i];
}
average=sum/salaries.length;
console.log(average);
Related
I've created a simple useFetch custom hook which allows me to call any Url I want :
import React, { useState, useEffect } from 'react';
export default function useFetch(url) {
console.log(url)
const [data, setData] = useState([]);
useEffect(() => {
fetch(url)
.then((response) => {
if (response.ok) return response.json();
setData([]);
})
.then((data) => {
setData(data)})
.catch((err) => {
console.error(err);
setData([]);
});
}, [url]);
return { data} ;
}
In my Main component I'm loading a static list of items.( via useEffect with [] becuase it's static)
I currently do it via :
export function Courses() {
const [langs, setLangs] = useState([]);
useEffect(() => {
getData(config.url).then((f) => setLangs(f));
}, []);
...
Where getData is:
export function getData(uri) {
return fetch(uri).then(response =>
response.json()
);
}
The problem is that I can't (don't know how) I can use my useFetch here becuase it can't be inside useEffect , and that's why I've created the additional getData method.
ps -
In other "details" component I use useFetch perfectly fine :
export default function Details({ langId }) {
const { data: teachers } = useFetch(`${config.url}/${langId}`);
...
The problem is only in the main component where I don't want to fetch manually . I want to use my useFetch. How can I do that ?
I want that Courses will load the static list only once via useFetch
One possible option is to cache the fetch response (I didn't test the code)
import React, { useState, useEffect } from 'react';
const cache = {};
export default function useFetch(url, useCache=false) {
console.log(url)
const [data, setData] = useState([]);
useEffect(() => {
if (useCache && cache[url]) {
setData(cache[url]);
} else {
fetch(url)
.then((response) => {
if (response.ok) {
const responseData = await response.json();
if (useCache) cache[url] = responseData;
return responseData;
}
setData([]);
})
.then((data) => {
setData(data)})
.catch((err) => {
console.error(err);
setData([]);
});
}
}, [url]);
return { data} ;
}
You can use useRef if you want to keep the hooks as a pure function
There are two requests, a POST and a GET. The POST request should create data and after it has created that data, the GET request should fetch the newly created data and show it somewhere.
This are the hooks imported into the component:
const { mutate: postTrigger } = usePostTrigger();
const { refetch } = useGetTriggers();
And they are used inside an onSubmit method:
const onAddSubmit = async (data) => {
await postTrigger(data);
toggle(); // this one and the one bellow aren't important for this issue
reset(emptyInput); //
refetch();
};
Tried to add async / await in order to make it wait until the POST is finished but it doesn't.
Any suggestions?
I added here the code of those 2 hooks if it's useful:
POST hook:
import { useMutation } from 'react-query';
import { ICalculationEngine } from '../constants/types';
import calculationEngineAPI from '../services/calculation-engine-api';
export const usePostTrigger = () => {
const apiService = calculationEngineAPI<ICalculationEngine['TriggerDailyOpt1']>();
const mutation = useMutation((formData: ICalculationEngine['TriggerDailyOpt1']) =>
apiService.post('/trigger/DailyOpt1', formData)
);
return {
...mutation
};
};
export default usePostTrigger;
GET hook:
import { useMemo } from 'react';
import { useInfiniteQuery } from 'react-query';
import { ICalculationEngine } from '../constants/types';
import { calculationEngineAPI } from '../services/calculation-engine-api';
export const TAG_PAGE_SIZE = 20;
export interface PaginatedData<D> {
totalPages: number;
totalElements: number;
content: D[];
}
export const useGetTriggers = () => {
const query = 'getTriggers';
const apiService = calculationEngineAPI<PaginatedData<ICalculationEngine['Trigger']>>();
const fetchTriggers = (pageNumber: number) => {
const search = {
pageNumber: pageNumber.toString(),
pageSize: TAG_PAGE_SIZE.toString()
};
return apiService.get(`/trigger/paged/0/${search.pageSize}`);
};
const {
data: response,
isError,
isLoading,
isSuccess,
isFetching,
isFetchingNextPage,
fetchNextPage,
hasNextPage,
refetch,
...rest
} = useInfiniteQuery(query, ({ pageParam = 1 }) => fetchTriggers(pageParam), {
getNextPageParam: (lastPage, pages) => {
const totalPages = lastPage.data.totalPages || 1;
return totalPages === pages.length ? undefined : pages.length + 1;
}
});
const data = useMemo(
() => response?.pages.map((page) => page.data.content).flat() || [],
[response?.pages]
);
return {
data,
isError,
isLoading,
isSuccess,
isFetching,
isFetchingNextPage,
fetchNextPage,
hasNextPage,
refetch,
...rest
};
};
export default useGetTriggers;
You can use the onSuccess method of react-query (https://react-query.tanstack.com/reference/useMutation)
onSuccess: (data: TData, variables: TVariables, context?: TContext) => Promise | void
Optional
This function will fire when the mutation is successful and will be passed the mutation's result.
If a promise is returned, it will be awaited and resolved before proceeding
const { mutate, isLoading, error, isSuccess } = useMutation(
(formData: ICalculationEngine['TriggerDailyOpt1']) =>
apiService.post('/trigger/DailyOpt1', formData),
{
mutationKey: 'DailyOpt1',
onSuccess: (_, { variables }) => {
// Execute your query as you see fit.
apiService.get(...);
},
}
);
As a best practice thought I would suggest the POST request to return the updated data if possible to avoid this exact need.
when i use fetch to bring the list of notes and consol.log it nothing shows up. The url is not wrong i have carefully checked it. Here is the code:
import React, { useState, useEffect } from 'react'
const NotesListPage = () => {
let [notes, setNotes] = useState([])
useEffect(() => {
}, [])
let getNotes = async () => {
let response = await fetch('http://127.0.0.1:8000/api/notes/')
let data = await response.json()
console.log(data)
setNotes(data)
}
return (
<div>
</div>
)
}
export default NotesListPage
here is the api part:
#api_view(['GET'])
def getNotes(request):
notes = Note.objects.all()
serializer = NoteSerializer(notes, many=True)
return Response(serializer.data)
import React, { useState, useEffect } from 'react'
const NotesListPage = () => {
let [notes, setNotes] = useState([])
useEffect(() => {
getNotes();
}, [])
let getNotes = async () => {
let response = await fetch('http://127.0.0.1:8000/api/notes/')
let data = await response.json()
console.log(data)
setNotes(data)
}
return (
<div>
</div>
)
}
export default NotesListPage
You are not calling your function 'getNotes'
The way I would do it, it to fetch your data in the Effect hook and set it in your state hook there.
import React, { useState, useEffect } from 'react'
const NotesListPage = () => {
let [notes, setNotes] = useState([])
useEffect( async () => {
const response = await fetch('http://127.0.0.1:8000/api/notes/')
.then(response => response.json())
setNotes(response)
}, [])
console.log(notes)
return (
<div>
</div>
)
}
export default NotesListPage
*Edit
Cleaner would be to have the fetch in a seperate function doing the same thing and just calling that function in your effect hook (see other answer above*)
I am currently working on a chat application and for some reason every time I pass in my array of messages as a prop to another component it passes in a number to the component instead of the message object. I have tried a lot of different methods of passing it in regarding using multiple components etc but it seems to still be passing in the number of elements for some reason. Any help is appreciated... code is below
Component receiving the props
import React, { useEffect } from 'react'
import Message from '../../Message/Message'
function Messages({ messages }) {
useEffect(() => {
console.log(messages)
}, [messages])
return (
<div>
test
</div>
)
}
export default Messages
// Import React dependencies.
import React, { useEffect, useState, } from "react";
// Import React dependencies.
import io from 'socket.io-client'
import axios from 'axios'
import Messages from './Messages/Messages'
import uuid from 'react-uuid'
import { Redirect } from 'react-router-dom'
// Import the Slate components and React plugin.
const ENDPOINT = 'http://localhost:5000/'
export const socket = io.connect(ENDPOINT)
const LiveChatFunction = ({ group_id }) => {
// Add the initial value when setting up our state.
const [message, setValue] = useState("")
const [user, setUser] = useState("")
const [groupId, setGroup] = useState('')
const [messages, setMessages] = useState([])
const [toLogin, userAuth] = useState(false)
useEffect(() => {
setGroup(group_id)
axios.post('http://localhost:5000/api/users/refresh_token', null, { withCredentials: true }).then(data => {
if (!data.data.accessToken) {
userAuth(true)
}
})
axios.get('http://localhost:5000/api/users/userInfo', { withCredentials: true }).then(data => {
setUser(data.data.user)
})
socket.on(`message-${group_id}`, data => {
setMessages(messages.push(data))
});
axios.get(`http://localhost:5000/live/${group_id}`).then(x => {
console.log(x.data)
})
}, [group_id, messages])
function setClick() {
const data = {
messageId: uuid(),
user,
groupId,
message
}
socket.emit('message', data)
}
if (toLogin) {
return (
<Redirect to="/login" />
)
}
return (
<div>
<input placeholder="message" type="text" onChange={value => {
setValue(value.target.value)
socket.emit('typing-message', { username: user, time: new Date() })
}} />
<button onClick={setClick}>Submit</button>
<Messages messages={messages} />
</div>
)
}
export default LiveChatFunction;
I have added some comments of what I think you can change:
useEffect(() => {
const recieveFunction = (data) => {
//using callback so no dependency on messages
setMessages((messages) => messages.push(data));
};
async function init() {
//next line is pointless, this runs when group_id
// has changed so something must have set it
// setGroup(group_id);
await axios //not sure if this should be done before listening to socket
.post(
'http://localhost:5000/api/users/refresh_token',
null,
{ withCredentials: true }
)
.then((data) => {
if (!data.data.accessToken) {
userAuth(true);
}
});
await axios
.get('http://localhost:5000/api/users/userInfo', {
withCredentials: true,
})
.then((data) => {
setUser(data.data.user);
});
//start listening to socket after user info is set
socket.on(`message-${group_id}`, recieveFunction);
axios
.get(`http://localhost:5000/live/${group_id}`)
.then((x) => {
console.log(x.data);
});
}
init();
//returning cleanup function, guessing socket.off exists
return () =>
socket.off(`message-${group_id}`, recieveFunction);
}, [group_id]); //no messages dependencies
console.log('messages are now:',messages);
If messages is still not set correctly then can you log it
So I think I found your problem:
In your useEffect hook, you're setting messages to the wrong thing.
socket.on(`message-${group_id}`, data => {
setMessages(messages.push(data))
});
An example:
const m = [].push();
console.log(m);
// m === 0
const n = [].push({});
console.log(n);
// n === 1
As you can see this is the index.
So what you need is:
socket.on(`message-${group_id}`, data => {
messages.push(data);
setMessages(messages);
});
This will set messages to the array of messages.
TLDR; Basically I'm trying to figure out how to get data from my api into my React app on load so my app has it available right away. I'm totally fine with redoing or reworking any or all of my code below if need be. I just need to figure out how to do this. I think it needs to go into the context so that all my components have access to it.
I have created a ReactJS app with hard-coded dummy data and it works great. Now we need to convert it to pulling in real dynamic data from the database and am having trouble with understanding how promises work with state and useEffect. It gets so confusing. I am creating the contextValue object in my final .then call and I need that at the end of my file in the return. I tried saving it to a state variable, but that doesn't seem to be working. I am getting an error TypeError: _useContext is null in a different file.
Here is the entire context file with any irrelevant code redacted:
import PropTypes from 'prop-types';
export const ScheduleContext = createContext();
export const ScheduleProvider2 = (props) => {
const allChemicals = [...];
const allBOMs = [...];
const makeRandomStr = (length) => {...};
const getRandomChemicals = () => {};
const getRandomBOMs = () => {..};
const getRandomIntInclusive = (min, max) => {...};
const randomDate = (start, end, startHour, endHour) => {...};
const quotes = [...];
const getRandomComments = () => {..};
const getBatchNumbers = () => {...};
const [context, setContext] = useState(null);
const [orders, setOrders] = useState(null);
const [filteredOrders, setFilteredOrders] = useState(null);
const [pendingOrderIDs, setPendingOrderIDs] = useState(null);
const [activeOrder, setActiveOrder] = useState(0);
const [columns, setColumns] = useState([]);
const [showDetails, setShowDetails] = useState(false);
const [title, setTitle] = useState('Title');
const [lineType, setLineType] = useState('Type');
const chemicals = useState(allChemicals);
const BOMs = useState(allBOMs);
useEffect(() => {
const fetchPendingOrders = () => {
const ordersURL = 'http://localhost:3001/orders';
return fetch(ordersURL, {
cache: 'no-cache', // *default, no-cache, reload, force-cache, only-if-cached
headers: {
'Content-Type': 'application/json'
},
referrerPolicy: 'no-referrer' // no-referrer, *client
});
};
fetchPendingOrders()
.then((result) => {
return result.json();
})
.then((data) => {
const tempOrders = data.map((el, index) => {
return {
id: index,
.....
};
});
setOrders(tempOrders);
setFilteredOrders(tempOrders);
const pendingOrderIDVals = tempOrders.map(function(val) {
return val.id;
});
setPendingOrderIDs(pendingOrderIDVals);
const contextValue = {
orders,
setOrders,
filteredOrders,
setFilteredOrders,
pendingOrderIDs,
setPendingOrderIDs,
columns,
setColumns,
showDetails,
setShowDetails,
activeOrder,
setActiveOrder,
title,
setTitle,
lineType,
setLineType,
chemicals,
BOMs
};
setContext(contextValue);
console.log(contextValue);
})
.catch((e) => {
console.log(e);
});
}, []);
return (
<ScheduleContext.Provider value={context}>
{props.children}
</ScheduleContext.Provider>
);
};
That error TypeError: _useContext is null is happening in a functional component in a file that reads in the context file. These are the two relevant lines(This is the beginning of the SchedulePage.js where the error is happening):
import { ScheduleContext } from '../../schedule-context-new';
const Schedule = () => {
const {
showDetails,
orders,
setOrders,
activeOrder,
columns,
setColumns,
title,
pendingOrderIDs,
filteredOrders,
setTitle,
lineType,
setLineType
} = useContext(ScheduleContext);
....
I'm also using the ScheduleProvider in my app.js if that makes a difference:
import { ScheduleProvider2 } from './schedule-context-new';
import Schedule from './components/home/SchedulePage';
import './App.scss';
const App = () => {
return (
<ScheduleProvider2>
<div className={'App'}>
<Schedule />
</div>
</ScheduleProvider2>
);
};
Update:
Per the example link, I tried changing the end of my context file to this and now it is complaining about Error: ScheduleProvider2(...): Nothing was returned from render.
(async function() {
const context = await fetchPendingOrders();
return (
<ScheduleContext.Provider value={context}>
{props.children}
</ScheduleContext.Provider>
);
})();
};
Update 2 - I've figured out how to create a basic barebones replica of my app that actually produces the same error. Once again for some reason, the useEffect code wasn't getting called until I commented out useEffect, I don't know why.
https://codesandbox.io/s/tender-cookies-jzn5v
You should waite for API result
const Schedule = () => {
const {
orders,
setOrders,
filteredOrders,
setFilteredOrders,
pendingOrderIDs,
setPendingOrderIDs
} = useContext(ScheduleContext) ||[];
Or use useEffect
const data = useContext(ScheduleContext);
useEffect(() => {
// Do you calculation here when ScheduleContext is ready
}, [data])
codesandbox