Can't update browser contents using react and socket.io - reactjs

I'm trying to build a simple web app that displaying values in real-time.
So the code I was built is below.
import {useEffect, useState, VFC} from "react";
import {Heading} from "#chakra-ui/react";
import {useSocket} from "../../context/socketContext";
import {chartDataType} from "../../types/chartDataType";
type singleDataPair = {
dataName: string;
unixTime: number;
value: number;
}
//This function just makes a readable date and time string.
const current_time_readable = () => {
let date_obj = new Date();
return `${date_obj.getUTCFullYear()}-${('0' + (date_obj.getUTCMonth() + 1)).slice(-2)}-${('0' + date_obj.getUTCDate()).slice(-2)} ${('0' + date_obj.getUTCHours()).slice(-2)}:${('0' + date_obj.getUTCMinutes()).slice(-2)}:${('0' + date_obj.getUTCSeconds()).slice(-2)}`;
};
type Props = {
dataName: string;
};
export const ChartCard: VFC<Props> = (props) => {
const {dataName} = props;
const {socket} = useSocket();
let singleDataPair: Array<singleDataPair> = [];
const [chartData, setChartData] = useState<Array<singleDataPair>>([]);
//Update an array and setState function.
//This update is deleting its first element and add a value as its last element.
const arrayUpdate = (array: Array<singleDataPair>, value: chartDataType) => {
if (array.length > 1) {
let tempArray = array;
tempArray.shift();
tempArray.push({
dataName: dataName,
unixTime: value['end_time_unix'],
value: value[dataName],
});
setChartData(chartData => tempArray);
console.log('updated');
console.log(chartData);
}
}
//Get initial data and extract single pair of time and data.
socket.on('initial_data_to_browser', ((data) => {
if (data) {
data.forEach((obj: any) => {
singleDataPair.push({
value: obj[dataName],
unixTime: obj['end_time_unix'],
dataName: dataName,
});
});
}
setChartData(chartData => singleDataPair);
console.log('initialData:');
//This displays empty array.
console.log(chartData);
}));
//Get new data every 10 seconds and update the state using arrayUpdate function above.
socket.on('test_emit_10s', ((data) => {
if (singleDataPair) {
arrayUpdate(chartData, data);
}
}));
return (
<>
{
//Display the data on the browser
chartData ? (chartData.map((data: any) => (
<Heading as="h1"
key={data.unixTime}>{`${current_time_readable()} | ${data.unixTime} | ${data.value}`}</Heading>
))) : null
}
</>
)
}
The above code just executes get initial data the time getting initial_data_to_browser event at first then getting update data constantly the time getting test_emit_10s event.
But this code does not work properly.
I can not figure out the exact usage of setState of react in this case. This code has two problems below.
Problem1(regarding displaying the initial data)
The code above can display initial data the time getting the initial_data_to_browser event on the browser.
On the other hand, the result of the console.log inside the callback of this event is an empty array. Why?
Problem2(regarding updating the data and displaying it)
The time getting test_emit_10s event, it can not update the browser display.
On the other hand, the result of console.log inside the callback of this event can be updated. Why?
In the end, the result of console.log and the display of the browser does not correspond.
In my idea, the reason for this problem is my usage of setState.
But I can not come up with a solution for these problems.
Anyone who can help me?
Just in case, the socketContext provider code is below. This simply provides a socket object.
import { useContext, createContext } from "react";
import {io, Socket} from "socket.io-client";
type Context = {
socket: Socket,
}
const socket: Socket = io("http://***.***.***.***");
const SocketContext = createContext<Context>({
socket,
});
const SocketProvider = (props: any) => {
return (
<SocketContext.Provider value={{ socket }} {...props} />
);
}
const useSocket = () => useContext(SocketContext);
export { SocketProvider, useSocket }

Related

Get data from promise and use it in hook

I want to make hook to get data from Snapshot to display proposals. I use graphql-request library to get data. I want to get this data in component for example: const { data } = useSnapshotProposalsQuery(). How can i do this? For now i can only get const data = useSnapshotProposalsQuery() and when i am console.log(data) i get Promise{<opening>}. My code:
import { gql, request } from 'graphql-request';
export const useSnapshotProposals = gql`
query Proposals {
proposals(
first: 20
skip: 0
where: { space_in: ["example.eth"] }
orderBy: "created"
orderDirection: desc
) {
id
title
body
choices
start
end
snapshot
state
author
space {
id
name
}
}
}
`;
export const useSnapshotProposalsQuery = () => {
return request('https://hub.snapshot.org/graphql', useSnapshotProposals).then((data) => data);
};
You create a custom hook. and that hook returns a state. when sideeffect inside that hook happens, the state is updated and your outer component gets re-rendered. (react docs)
export const useSnapshotProposalsQuery = () => {
const [myData, setMyData] = useState(null);
useEffect(()=>{
request('https://hub.snapshot.org/graphql', useSnapshotProposals).then((data) => {setMyData(data)});
}, []); // run only one time
return myData;
};
in outer component:
function ABCcomponent () {
const myData = useSnapshotProposalsQuery(); // it will be null at first, but will be filled with data later.
return (
/*ui that uses myData */
)
}

Context API values are being reset too late in the useEffect of the hook

I have a FilterContext provider and a hook useFilter in filtersContext.js:
import React, { useState, useEffect, useCallback } from 'react'
const FiltersContext = React.createContext({})
function FiltersProvider({ children }) {
const [filters, setFilters] = useState({})
return (
<FiltersContext.Provider
value={{
filters,
setFilters,
}}
>
{children}
</FiltersContext.Provider>
)
}
function useFilters(setPage) {
const context = React.useContext(FiltersContext)
if (context === undefined) {
throw new Error('useFilters must be used within a FiltersProvider')
}
const {
filters,
setFilters
} = context
useEffect(() => {
return () => {
console.log('reset the filters to an empty object')
setFilters({})
}
}, [setFilters])
{... do some additional stuff with filters if needed... not relevant }
return {
...context,
filtersForQuery: {
...filters
}
}
}
export { FiltersProvider, useFilters }
The App.js utilises the Provider as:
import React from 'react'
import { FiltersProvider } from '../filtersContext'
const App = React.memo(
({ children }) => {
...
...
return (
...
<FiltersProvider>
<RightSide flex={1} flexDirection={'column'}>
<Box flex={1}>
{children}
</Box>
</RightSide>
</FiltersProvider>
...
)
}
)
export default App
that is said, everything within FiltersProvider becomes the context of filters.
Now comes the problem description: I have selected on one page (Page1) the filter, but when I have to switch to another page (Page2), I need to flush the filters. This is done in the useFilters hook in the unmount using return in useEffect.
The problem is in the new page (Page2), during the first render I'm still getting the old values of filters, and than the GraphQL request is sent just after that. Afterwards the unmount of the hook happens and the second render of the new page (Page2) happens with set to empty object filters.
If anyone had a similar problem and had solved it?
first Page1.js:
const Page1 = () => {
....
const { filtersForQuery } = useFilters()
const { loading, error, data } = useQuery(GET_THINGS, {
variables: {
filter: filtersForQuery
}
})
....
}
second Page2.js:
const Page2 = () => {
....
const { filtersForQuery } = useFilters()
console.log('page 2')
const { loading, error, data } = useQuery(GET_THINGS, {
variables: {
filter: filtersForQuery
}
})
....
}
Printout after clicking from page 1 to page 2:
1. filters {isActive: {id: true}}
2. filters {isActive: {id: true}}
3. page 2
4. reset the filters to an empty object
5. 2 reset the filters to an empty object
6. filters {}
7. page 2
As I mentioned in the comment it might be related to the cache which I would assume you are using something like GraphQL Apollo. It has an option to disable cache for queries:
fetchPolicy: "no-cache",
By the way you can also do that reset process within the Page Two component if you want to:
const PageTwo = () => {
const context = useFilters();
useEffect(() => {
context.setFilters({});
}, [context]);
For those in struggle:
import React, { useState, useEffect, useCallback, **useRef** } from 'react'
const FiltersContext = React.createContext({})
function FiltersProvider({ children }) {
const [filters, setFilters] = useState({})
return (
<FiltersContext.Provider
value={{
filters,
setFilters,
}}
>
{children}
</FiltersContext.Provider>
)
}
function useFilters(setPage) {
const isInitialRender = useRef(true)
const context = React.useContext(FiltersContext)
if (context === undefined) {
throw new Error('useFilters must be used within a FiltersProvider')
}
const {
filters,
setFilters
} = context
useEffect(() => {
**isInitialRender.current = false**
return () => {
console.log('reset the filters to an empty object')
setFilters({})
}
}, [setFilters])
{... do some additional stuff with filters if needed... not relevant }
return {
...context,
filtersForQuery: { // <---- here the filtersForQuery is another variable than just filters. This I have omitted in the question. I will modify it.
**...(isInitialRender.current ? {} : filters)**
}
}
}
export { FiltersProvider, useFilters }
What is done here: set the useRef bool varialbe and set it to true, as long as it is true return always an empty object, as the first render happens and/or the setFilters function updates, set the isInitialRender.current to false. such that we return updated (not empty) filter object with the hook.

React using fetch returns undefined until save

new to react so I am not quite sure what I am doing wrong here... I am trying to call data from an API, then use this data to populate a charts.js based component. When I cmd + s, the API data is called in the console, but if I refresh I get 'Undefined'.
I know I am missing some key understanding about the useEffect hook here, but i just cant figure it out? All I want is to be able to access the array data in my component, so I can push the required values to an array... ive commented out my attempt at the for loop too..
Any advice would be greatly appreciated! My not so functional code below:
import React, {useState, useEffect} from 'react'
import {Pie} from 'react-chartjs-2'
const Piegraph = () => {
const [chartData, setChartData] = useState();
const [apiValue, setApiValue] = useState();
useEffect(async() => {
const response = await fetch('https://api.spacexdata.com/v4/launches/past');
const data = await response.json();
const item = data.results;
setApiValue(item);
chart();
},[]);
const chart = () => {
console.log(apiValue);
const success = [];
const failure = [];
// for(var i = 0; i < apiValue.length; i++){
// if(apiValue[i].success === true){
// success.push("success");
// } else if (apiValue[i].success === false){
// failure.push("failure");
// }
// }
var chartSuccess = success.length;
var chartFail = failure.length;
setChartData({
labels: ['Success', 'Fail'],
datasets: [
{
label: 'Space X Launch Statistics',
data: [chartSuccess, chartFail],
backgroundColor: ['rgba(75,192,192,0.6)'],
borderWidth: 4
}
]
})
}
return (
<div className="chart_item" >
<Pie data={chartData} />
</div>
);
}
export default Piegraph;
There are a couple issues that need sorting out here. First, you can't pass an async function directly to the useEffect hook. Instead, define the async function inside the hook's callback and call it immediately.
Next, chartData is entirely derived from the apiCall, so you can make that derived rather than being its own state variable.
import React, { useState, useEffect } from "react";
import { Pie } from "react-chartjs-2";
const Piegraph = () => {
const [apiValue, setApiValue] = useState([]);
useEffect(() => {
async function loadData() {
const response = await fetch(
"https://api.spacexdata.com/v4/launches/past"
);
const data = await response.json();
const item = data.results;
setApiValue(item);
}
loadData();
}, []);
const success = apiValue.filter((v) => v.success);
const failure = apiValue.filter((v) => !v.success);
const chartSuccess = success.length;
const chartFail = failure.length;
const chartData = {
labels: ["Success", "Fail"],
datasets: [
{
label: "Space X Launch Statistics",
data: [chartSuccess, chartFail],
backgroundColor: ["rgba(75,192,192,0.6)"],
borderWidth: 4,
},
],
};
return (
<div className="chart_item">
<Pie data={chartData} />
</div>
);
};
export default Piegraph;
pull your chart algorithm outside or send item in. Like this
useEffect(async() => {
...
// everything is good here
chart(item)
})
you might wonder why I need to send it in. Because inside useEffect, your apiValue isn't updated to the new value yet.
And if you put the console.log outside of chart().
console.log(apiData)
const chart = () => {
}
you'll get the value to be latest :) amazing ?
A quick explanation is that, the Piegraph is called whenever a state is updated. But this update happens a bit late in the next cycle. So the value won't be latest within useEffect.

React js giving error Objects are not valid as a React child, I used hooks

I am sending data from Node JS to React JS in array object. In React JS when I am setting response data I am getting error "Objects are not valid as a React child (found: object with keys {eventName, eventDate, merchantid}). If you meant to render a collection of children, use an array instead."
I checked one of the Stackoverflow post useState Array of Objects. I am also setting value same way, but I am getting error.
Below data I am sending from Node JS.
[
{
eventName: 'Sankranti',
eventDate: 2021-01-21T00:00:00.000Z,
merchantid: 'tp012345'
},
{
eventName: 'Sankranti 1',
eventDate: 2021-01-26T00:00:00.000Z,
merchantid: 'tp012345'
}
]
Below screen shot I can see error and response data on the console.
Below my code, I am getting error at setEventList(eventList => [...eventList, response]). Based on comments I added below code.
import React, { useState, useEffect } from 'react';
import { connect } from 'react-redux';
import Carousel from 'react-bootstrap/Carousel'
import axios from 'axios';
import DashboardNavBar from './DashboardNavBar';
import Header from './Header';
const DashboardPage = (props) => {
const [eventList, setEventList] = useState([])
const [index, setIndex] = useState()
if (!props.profileData) {
useEffect(() => {
(async () => {
const eventsList = await axios.get(
"http://localhost:3000/api/dashboard"
);
console.log(eventsList.data)
const response = eventsList.data
setEventList(eventList => [...eventList, response])
if(!response){
setErrorMsg('Please create Event and then add User !!')
}
})();
}, []);
}
const eventListRender = eventList.length > 0 ?
eventList.map((item,index) => {
console.log('item name: ', item[index].eventName)
return <Carousel.Item>{item[index].eventName}</Carousel.Item>
}) :
<Carousel.Item>No upcoming events</Carousel.Item>
const handleSelect = (selectedIndex) => {
console.log(eventList)
console.log(selectedIndex)
setIndex(selectedIndex)
}
return (
<div>
<DashboardNavBar />
<Header />
<p >Welcome !!!!!!</p>
<Carousel
activeIndex={index}
onSelect={handleSelect}
>
{eventListRender}
</Carousel>
</div>
);
}
const mapStateToProps = (state) => ({
profileData: state.auth.profileData
})
export default connect(mapStateToProps) (DashboardPage);
After adding below code it always reading first occurrence
const eventListRender = eventList.length > 0 ?
eventList.map((item,index) => {
console.log('item name: ', item[index].eventName)
return <Carousel.Item>{item[index].eventName}</Carousel.Item>
}) :
<Carousel.Item>No upcoming events</Carousel.Item>
Please find the updated results
Issue
Ok, your codesandbox confirms what I suspected. In your sandbox you've directly placed that mock response in your state as a flat array
const [eventList, setEventList] = useState([
{
eventName: "Sankranti",
eventDate: "2021-01-21T00:00:00.000Z",
merchantid: "tp012345"
},
{
eventName: "Sankranti 1",
eventDate: "2021-01-26T00:00:00.000Z",
merchantid: "tp012345"
}
]);
This allows the render to work as you expected, simply mapping over this flat array of objects.
eventList.map((item, index) => {
return <Carousel.Item>{item.eventName}</Carousel.Item>;
})
But in your original code you are not updating your state to be a flat array. The response is an array, i.e. [object1, object2] and you append this array to the end of your state's eventList array.
setEventList(eventList => [...eventList, response])
This updates your state to something like this [[object1, object2]], so the mapping function you used only maps one element.
eventList.map((item, index) => {
return <Carousel.Item>{item[index].eventName}</Carousel.Item>;
})
The reason is because you used the array index of the outer (recall eventList is an array of length 1) to access into the inner nested array (array of length 2). In iterating the outer array the index only reaches value 0, so only the zeroth element of the inner array is rendered.
See a more accurate reproduction of your issue in this code:
const response = [
{
eventName: "Sankranti",
eventDate: "2021-01-21T00:00:00.000Z",
merchantid: "tp012345"
},
{
eventName: "Sankranti 1",
eventDate: "2021-01-26T00:00:00.000Z",
merchantid: "tp012345"
}
];
function App() {
const [eventList, setEventList] = useState([]);
useEffect(() => {
setEventList((eventList) => [...eventList, response]);
}, []);
const eventListRender =
eventList.length > 0 ? (
eventList.map((item, index) => {
return <Carousel.Item>{item[index].eventName}</Carousel.Item>;
})
) : (
<Carousel.Item>No upcoming events</Carousel.Item>
);
return (
<div className="App">
<h1>Hello CodeSandbox</h1>
<h2>Start editing to see some magic happen!</h2>
<Carousel>{eventListRender}</Carousel>
</div>
);
}
Solution
If the response data is also an array then it seems you should spread it into your eventList state array so it remains a nice, flat array.
Additionally, as pointed out by #Ashish, your useEffect hook usage is invalid and breaks the rules of hooks by being placed in a conditional block. The effect needs to be in the body of the component, so the condition should be tested in the effect callback. Refactor the anonymous async function to be a standard named function, and invoke in a conditional check within the effect callback.
useEffect(() => {
const getEvents = async () => {
const eventsList = await axios.get("http://localhost:3000/api/dashboard");
console.log(eventsList.data);
const response = eventsList.data;
setEventList((eventList) => [
...eventList, // <-- copy previous state
...response, // <-- spread array response
]);
if (!response) {
setErrorMsg("Please create Event and then add User !!");
}
};
if (!props.profileData) { // <-- check condition for fetching data
getEvents();
}
}, []);
const eventListRender =
eventList.length > 0 ? (
eventList.map((item, index) => {
return <Carousel.Item key={index}>{item.eventName}</Carousel.Item>;
})
) : (
<Carousel.Item>No upcoming events</Carousel.Item>
);
Demo with mocked axios data fetch.

Using document.cookie in Gatsby

I need to be able to set and access cookies in my Gatsby project, and I was able to get something solid setup using this tutorial. I'm building a hook that sets a cookie, and utilizing it throughout the site. This is what the helper looks like when it's all said and done.
use-cookie.ts
import { useState, useEffect } from 'react';
const getItem = (key) =>
document.cookie.split('; ').reduce((total, currentCookie) => {
const item = currentCookie.split('=');
const storedKey = item[0];
const storedValue = item[1];
return key === storedKey ? decodeURIComponent(storedValue) : total;
}, '');
const setItem = (key, value, numberOfDays) => {
const now = new Date();
// set the time to be now + numberOfDays
now.setTime(now.getTime() + numberOfDays * 60 * 60 * 24 * 1000);
document.cookie = `${key}=${value}; expires=${now.toUTCString()}; path=/`;
};
/**
*
* #param {String} key The key to store our data to
* #param {String} defaultValue The default value to return in case the cookie doesn't exist
*/
export const useCookie = (key, defaultValue) => {
const getCookie = () => getItem(key) || defaultValue;
const [cookie, setCookie] = useState(getCookie());
const updateCookie = (value, numberOfDays) => {
setCookie(value);
setItem(key, value, numberOfDays);
};
return [cookie, updateCookie];
};
I'm calling the hook into a component like so:
DealerList.tsx
import React, { ReactNode, useEffect } from 'react';
import { Container } from 'containers/container/Container';
import { Section } from 'containers/section/Section';
import { Link } from 'components/link/Link';
import s from './DealerList.scss';
import { useCookie } from 'hooks/use-cookie';
interface DealerListProps {
fetchedData: ReactNode;
}
let cookie;
useEffect(() => {
cookie = useCookie();
}, []);
export const DealerList = ({ fetchedData }: DealerListProps) => {
const dealerInfo = fetchedData;
if (!dealerInfo) return null;
const [cookie, updateCookie] = useCookie('one-day-location', 'sacramento-ca');
return (
<>
<Section>
<Container>
<div className={s.list}>
{dealerInfo.map((dealer: any) => (
<div className={s.dealer} key={dealer.id}>
<div className={s.dealer__info}>
<h3 className={s.name}>
{dealer.company.name}
</h3>
<span className={s.address}>{dealer.address.street}</span>
<span className={s.city}>{dealer.address.city} {dealer.address.zip}</span>
</div>
<div className={s.dealer__contact}>
<span className={s.email}>{dealer.email}</span>
<span className={s.phone}>{dealer.phone}</span>
</div>
<div className={s.dealer__select}>
<Link
to="/"
className={s.button}
onClick={() => {
updateCookie(dealer.phone, 10);
}}
>
Select Location
</Link>
</div>
</div>
))}
</div>
</Container>
</Section>
</>
);
};
It works well on gatsby develop and I'm able to access the value of the cookie and change the contact information that's displayed accordingly. However, when I try and build, or push to Netlify, I'm getting this error.
WebpackError: ReferenceError: document is not defined
I know this has something to do with document.cookie on lines 4 and 17, but I'm struggling trying to figure out how to fix it. Any suggestions? I'm imported useEffect, and from my research that has something to do with it, but what can I do to get it working properly?
Thanks in advance.
I did a bit more research, and I found this simple hook, replaced the code in use-cookie.ts with this, made a few modifications to it (included below), installed universal-cookie and it seems to work perfectly. Here's the new code:
use-cookie.ts
import { useState } from 'react';
import Cookies from 'universal-cookie';
export const useCookie = (key: string, value: string, options: any) => {
const cookies = new Cookies();
const [cookie, setCookie] = useState(() => {
if (cookies.get(key)) {
return cookies.get(key);
}
cookies.set(key, value, options);
});
const updateCookie = (value: string, options: any) => {
setCookie(value);
removeItem(value);
cookies.set(key, value, options);
};
const removeItem = (key: any) => {
cookies.remove(key);
};
return [cookie, updateCookie, removeItem];
};
If anyone has a better way to do this though, please let me know!
According to Gatsby's Debugging HTML Builds documentation:
Some of your code references “browser globals” like window or
document. If this is your problem you should see an error above like
“window is not defined”. To fix this, find the offending code and
either a) check before calling the code if window is defined so the
code doesn’t run while Gatsby is building (see code sample below) or
b) if the code is in the render function of a React.js component, move
that code into a componentDidMount lifecycle or into a useEffect hook,
which ensures the code doesn’t run unless it’s in the browser.
So, without breaking the rule of hooks, calling a hook inside another hook, causing a nested infinite loop. You need to ensure the document creation before calling it. Simply by adding a checking condition:
import { useState } from 'react';
const getItem = (key) => {
if (typeof document !== undefined) {
document.cookie.split(`; `).reduce((total, currentCookie) => {
const item = currentCookie.split(`=`);
const storedKey = item[0];
const storedValue = item[1];
return key === storedKey ? decodeURIComponent(storedValue) : total;
}, ``);
}
};
const setItem = (key, value, numberOfDays) => {
const now = new Date();
// set the time to be now + numberOfDays
now.setTime(now.getTime() + numberOfDays * 60 * 60 * 24 * 1000);
if (typeof document !== undefined) {
document.cookie = `${key}=${value}; expires=${now.toUTCString()}; path=/`;
}
};
/**
*
* #param {String} key The key to store our data to
* #param {String} defaultValue The default value to return in case the cookie doesn't exist
*/
export const useCookie = (key, defaultValue) => {
const getCookie = () => getItem(key) || defaultValue;
const [cookie, setCookie] = useState(getCookie());
const updateCookie = (value, numberOfDays) => {
setCookie(value);
setItem(key, value, numberOfDays);
};
return [cookie, updateCookie];
};
Since you might be calling useCookie custom hook in a component or page where the document doesn't exist yet, I would double-check by using the same condition or using a useEffect with empty dependencies ([], now it won't break the rule of hooks):
const Index = props => {
let cookie;
// both works
if (typeof document !== undefined) {
cookie = useCookie();
}
useEffect(() => {
cookie = useCookie();
}, []);
return <Layout>
<Seo title="Home" />
<h1>Hi people</h1>
</Layout>;
};

Resources