Pushing Rect-Datepicker date State to Another React Component - reactjs

I am having a problem finding a way i can reliably store the state of the date from react-datepicker, and exporting that state to another component via URL parameter.
To give some context, I have two components. A Picker.JS component containing the react-datepicker calendar, and a separate Component called "DelayGuage", which dynamically draws a google charts guage using an API call. The only problem is, this API call has a dynamic parameter known as {date}, and this {date} parameter MUST come from the previously selected datepicker date.
Here is my DatePicker code Below. The issue I am having is that I keep getting
"Uncaught TypeError: event.target is undefined" when I attempt to call handleSubmit() after the calendar date is selected (onSelect).
import React, { useState } from "react";
import DatePicker from "react-datepicker";
import {Link, useNavigate} from "react-router-dom"
import "react-datepicker/dist/react-datepicker.css";
// CSS Modules, react-datepicker-cssmodules.css
// import 'react-datepicker/dist/react-datepicker-cssmodules.css';
const Picker = () => {
const navigate = useNavigate();
const [startDate, setStartDate] = useState(new Date());
const handleChange = (event) => {
setStartDate(event.target.value)
console.log(startDate)
}
const handleSubmit = (event) =>{
event.preventDefault();
navigate(`/guage/${startDate}`) //upon the call of handleSubmit, a redirect should call the guage component to be rendered, taking the startDate as a URL parameter
}
return (
<DatePicker selected={startDate} onChange={(date) => setStartDate(date)} onSelect={handleChange}/>
);
};
export default Picker
The second problem, is that I need to find a way to input the date selected from the Calendar with the API call to the backend to retrieve the data for my Guage. It only fetches a JSON for now, but the ability to fetch the appropriate JSON per date selected from the Picker.JS component has been giving me a headache.
Here is my DelayGuage.JS code below:
import React from "react";
import { Chart } from "react-google-charts";
import axios from "axios";
import { QueryClient, QueryClientProvider, useQuery } from 'react-query';
import { Link, useLocation, useParams } from "react-router-dom";
async function fetchPosts() {
const {data} = await axios.get(
"http://172.16.10.100:5004/reports/{date}/agencies"
);
const parsedData = data.agencies[0].kpis.map((r) => [
"Delay Index",
r.kpi_value * 100
]);
return [["Label", "Value"], ...parsedData];
}
So, to summarize, there are two issues in this project. The first being a way to store the selected date in the datepicker, and the second being a way to parse said selected date in the redirect guage/'${startDate}' URL to be used directly in the API call "http://172.16.10.100:5004/reports/{date}/agencies". This will return a JSON containing the object retrieved from a database (backend has been set up already).

Your onChange does the intended behavior of storing the value of the DatePicker input in your state as startDate. Passing the onSelect prop to the <DatePicker /> causes issues as the event parameter passed in to the onSelect is actually just the updated input value (you can see this if you console.log(event)). The <DatePicker /> component is just an input component, not an entire form. You should wrap the component in a form and pass the handleSubmit to an onSubmit prop of the form. There is an example in the React Router docs for useNavigate.
For the second part of your question, look into React Router's url parameters (guide, example). You can use the useParams() hook to get the startDate from the URL, and use that in your API call.

Related

Using React.Context with Nextjs13 server-side components

Next13 was released a week ago, and I am trying to migrate a next12 app to a next13.
I want to use server-side components as much as possible, but I can't seem to use
import { createContext } from 'react';
in any server component.
I am getting this error:
Server Error
Error:
You're importing a component that needs createContext. It only works in a Client Component but none of its parents are marked with "use client", so they're Server Components by default.
,----
1 | import { createContext } from 'react';
: ^^^^^^^^^^^^^
`----
Maybe one of these should be marked as a client entry with "use client":
Is there an alternative here or do I have to resort to prop drilling to get server-side rendering?
It seems like I can use createServerContext
import { createServerContext } from 'react';
If you're using Typescript and React 18, you'll also need to add "types": ["react/next"] to your tsconfig.json compiler options, since this is a not-yet-stable function.
This is a new feature from React's SSR to recognize whether a component is client-side or server-side. In your case, createContext is only available on the client side.
If you only use this component for client-side, you can define 'use client'; on top of the component.
'use client';
import { createContext } from 'react';
You can check this Next.js document and this React RFC for the details
According to Next.js 13 beta documentation, you cannot use context in Server Components:
In Next.js 13, context is fully supported within Client Components, but it cannot be created or consumed directly within Server Components. This is because Server Components have no React state (since they're not interactive), and context is primarily used for rerendering interactive components deep in the tree after some React state has been updated
However, there are alternative ways to handle data in the new approach, depending on your case. F.e. if you fetched the data from the server in a parent component and then passed it down the tree through Context, you can now fetch the data directly in all the components that depend on this data. React 18 will dedupe (de-duplicate) the fetches, so there are no unnecessary requests.
There are more alternatives in the documentation.
I've made a tiny package to handle context in server components, works with latest next.js, it's called server-only-context:
https://www.npmjs.com/package/server-only-context
Usage:
import serverContext from 'server-only-context';
export const [getLocale, setLocale] = serverContext('en')
export const [getUserId, setUserId] = serverContext('')
import { setLocale, setUserId } from '#/context'
export default function UserPage({ params: { locale, userId } }) {
setLocale(locale)
setUserId(userId)
return <MyComponent/>
}
import { getLocale, getUserId } from '#/context'
export default function MyComponent() {
const locale = getLocale()
const userId = getUserId()
return (
<div>
Hello {userId}! Locale is {locale}.
</div>
)
}
This is the code for it, it's really simple:
import 'server-only'
import { cache } from 'react'
export default <T>(defaultValue: T): [() => T, (v: T) => void] => {
const getRef = cache(() => ({ current: defaultValue }))
const getValue = (): T => getRef().current
const setValue = (value: T) => {
getRef().current = value
}
return [getValue, setValue]
}

How to prevent useEffect in customHook being called with every import of custom hook?

I'm writing chat app using react js and socket.io library.
All the logic where I subscribe to events form server and emit some events is written in useEffect of custom hook.
Then I return all data I need from this custom hook and reuse it in components that I need. However, I realized that logic written in useEffect is called every time I import this custom hook to external component.
If I put all the logic outside of useEffect, it's called even more times than custom hook is imported.
How do I prevent it if it's possible at all?
If it's not possible, what solution could you please suggest? I don't want to use redux for this app, I thought to keep everything in this custom hook component and just reuse data from it where I need.
I can't share working example because it won't work without server part so here is a simple codesandbox example. You can see in console that it's rendered twice.
https://codesandbox.io/s/custom-hook-bfc5j?file=/src/useChat.js
It renders twice because you call useChat() two times in your app (one in App.js, other in Text.js) What you can do is to create a reference of useChat component in your App.js and pass is as a prop to Text.js like:
App.js
import React from "react";
import useChat from "./useChat";
import Text from "./Text";
import "./styles.css";
export default function App() {
const myUseChat = useChat();
const { printMessage } = myUseChat;
return (
<div className="App">
<button onClick={printMessage}>Print</button>
<Text myUseChat={myUseChat} />
</div>
);
}
Text.js
import React from "react";
import useChat from "./useChat";
import "./styles.css";
export default function Text(props) {
const { text } = props.myUseChat;
return <div className="App">{text}</div>;
}
If you want to set up some side effects once but also consume the resulting data in multiple places, one way is to use the context feature.
// ws/context.jsx, or similar
const WsContext = React.createContext(defaultValue);
export const WsProvider = props => {
const [value, setValue] = useState(someInitialValue);
useEffect(() => {
// do expensive things, call setValue with new results
});
return (
<WsContext.Provider value={value}>
{props.children}
</WsContext.Provider>
);
};
export const useCustomHook = () => {
const value = useContext(WsContext);
// perhaps do some other things specific to this hook usage
return value;
};
You can expect the hook to work in any component that is a descendant of <WsProvider> in React's rendered tree of elements.
If you use the hook in a non-descendant of the provider component, the value returned will be the defaultValue we initialized the context instance with.

Testing a react component using useSelector hook

I am having a lot of trouble trying to implement tests for a component using the useSelector hook from react redux. I've seen some questions already about this subject but I didn't manage to fix my problem using the suggested solutions to those questions.
My component is pretty big so I won't post it all but the part giving me trouble looks like this :
Total.tsx
import React from 'react';
import clsx from 'clsx';
import i18next from 'i18next';
import { useSelector } from 'react-redux';
import { Trans } from 'react-i18next';
import Box from '#material-ui/core/Box';
import CustomTooltip from '../CustomTooltip/CustomTooltip';
import SkeletonTotal from 'components/Skeletons/Total';
import { ApplicationHelper } from 'helpers';
import './Total.scss';
//Some interfaces here for types since this is in TypeScript
function Total(props: TotalProps) {
const { currency } = useSelector(
(state: { currencyReducer: any }) => state.currencyReducer
);
...
}
I first tried to test it like another component that doesn't use redux like so :
Total.test.js (first attempt)
import React from 'react';
import Total from './Total';
import { render } from '#testing-library/react';
test('test', () => {
const { container } = render(
<Total priceLoading={false} bookingPrice={bookingPrice} values={myFormValues} />
);
});
But I was getting an error saying I need a react-redux context value and to wrap my component in a Provider which led me to try this :
Total.test.js (attempt 2)
import React from 'react';
import { Provider } from 'react-redux'
import Total from './Total';
import { render } from '#testing-library/react';
test('test', () => {
const { container } = render(
<Provider>
<Total priceLoading={false} bookingPrice={bookingPrice} values={myFormValues} />
</Provider>
);
});
I am now getting a "Cannot read property 'getState' of undefined" error for the Provider component. I did try to mock a store to pass to my Provider as well as using jest to mock a return value like so
const spy = jest.spyOn(redux, 'useSelector')
spy.mockReturnValue({currency: 'cad'})
Unfortunately I was unsuccessful to make this work and could not find a working solution in the other questions that might relate to this. Any ideas how I could make this work? Thanks
The useSelector hook relies on the redux Context in order to access the state, so it must be inside of a Provider component in order to work. Your second attempt is on the right track, but you haven't set the store prop on the Provider, so the store is undefined and you get error "Cannot read property 'getState' of undefined".
Since you'll likely have many components that you'll want to test with redux context, the redux docs suggest creating your own version of the react testing library's render function which wraps the element in a provider before rendering it. This new render function adds two new optional options to the standard RTL options: initialState and store.
You can basically copy and paste that entire test-utils.js example from the docs, but I modified the return to include the created store so that we can dispatch to it directly (rather than just interacting with the component in ways that will dispatch an action).
return {
...rtlRender(ui, { wrapper: Wrapper, ...renderOptions }),
store
};
With typescript annotations.
Inside your component test file, you will use your test-utils to render the Total component. It's fine to return the container element but you don't actually need to because you can query matching elements on the global RTL screen object or on the bound queries for your base element. We are basically looking to see that the outputted HTML code matches the expectations. You could test the selector itself in isolation, but it seems like you are trying to test the component.
Your test might look something like this:
import React from "react";
import Total from "./Total";
import { render, screen } from "./test-utils";
// if you want events: import userEvent from "#testing-library/user-event";
test( 'gets currency from redux', () => {
// render with an initial currency
const { store, container, getByLabelText } = render(
// not sure where these props come from, presumable constants in the file
<Total priceLoading={false} bookingPrice={bookingPrice} values={myFormValues} />,
{ initialState: { currency: USD } }
);
// some sort of RTL matcher or document.querySelector
const currencyElement = getByLabelText(/currency/i); // uses regex for case-insensitivity
// some sort of check for value
expect(currencyElement?.innerText).toBe("USD");
// dispatch an action to change the currency
// might want to wrap in `act`?
store.dispatch(setCurrency("EUR"));
// check that the value changed
expect(currencyElement?.innerText).toBe("EUR");
});
Working example that I created based on a basic counter component.

Filter a list of items based on URL path

I have a component list of items in React. I want to filter these items based on URL path. For example if URL is something like below line I want items to be filtered by date attribute, with value 2010 :
http://localhost:3000/?filter='date'&value='2010'
As I'm so new in React I couldn't find any easy-to-understand answer, Is there any source I can learn about this question?
Thanks in advance
You should use useLocation hook to get the query params string from URL and then use query-string lib to parse the query params string to object. Like below:
import React, { useEffect } from 'react';
import { useLocation } from 'react-router-dom';
import queryString from 'query-string';
const App = () => {
const location = useLocation();
useEffect(() => {
console.log('query params object: ', queryString.parse(location.search));
}, [location])
}
export default App;
NOTE: Please be aware that the number of characters on a browser URL is limited, find the best solution that fits your need.
You simply can get data in url in this manner:
const [currentUrl, setCurrentUrl] = useState()
const currentUrl = window.location.href.split('/')

Pass parameters to another component

How to properly pass parameter from one component to another component. In my scenario, When I console log my pass parameter to another component they have empty string first before the actual string in the last. Which is supposed to be the actual string only.
Component 1:
import React, {useState, useEffect} from 'react'
import {dataComponent} from './include/childComponent'
export default function Parentcomponent(props) {
const [data, setdata] = useState('');
function functioncalhttp(){
//data from database
setdata(response.data);
}
useEffect(() => {
functioncalhttp();
},[]);
return (
<div>{<dataComponent callbackfunction={data}/>}</div>
)
}
Compnent 2:
import React, {useState, useEffect} from 'react'
export default function dataComponent(props) {
console.log(props.callbackfunction);
return (
<div></div>
)
}
Screenshot:
At the core of react is the idea of rendering components when either its state or received props change. Props are always passed to components when they render, so here in your case, whenever data changes, the child component will receive your new data state on the callbackfunction prop, and rerender. If you want to "wait" to render children until data is ready, here is a demo using a guard pattern to include children when conditions are met:
React also provides a way to further hint to react when a component should rerender with a memo HOC that provides more control over the received props and selectively letting react know that a rerender isn't necessary.

Resources