How can I desctructure items out of an object that is returned from a React hook using Typescript? - reactjs

I have a component that needs to tap into the React Router query params, and I am using the use-react-router hook package to access them.
Here is what I am wanting to do:
import React from "react;
import useReactRouter from "use-react-router";
const Foo = () => {
const { id } = useReactRouter().match.params;
return (
<Bar id={id}/>
)
}
The issue is that this throws the following error in VS Code, and at compile time:
Property 'id' does not exist on type '{}'.ts(2339)
I have found that if I refactor my code like so:
const id = match.params["id"], I do not get the error, but I feel like this is not the correct approach for some reason. If someone could point me in the right direction, I would appreciate it.

I figured it out. The solution was to include angle brackets between the hook's name and the parenthesis, like so:
const { match } = useRouter<{ id: string }>();
const { id } = useRouter<{ id: string }>();
Or if you prefer nested destructuring:
const { match: { params: id } } = useRouter<{ id: string }>();

You can try to give default value to params
const { id } = useReactRouter().match.params || {id: ""};
It may be possible that params to be null at initial level

The code is insufficient.
However, at first glance,
// right way
const { history, location, match } = useReactRouter()
// in your case
const { match: { params : { id } } } = useReactRouter()
// or
const { match } = useReactRouter()
const { id } = match.params
now, try to console the value first.
Also, please try to pass the props to a functional component from it's container, since it's more logical.
From your comment below, i can only assume you solved it. Also, it's recommended to handle possible undefined values when you use it.
{ id && id }
However, the first step should've been consoling whether it has value in it,
console.log('value xyz', useReactRouter())

Related

Context provider not updating default value

I'm having some trouble modifying the default value given by a context. Below is a heavily simplified code, which still leads to the issue: As seen in the provider, I want greetWorld to become true, thus exhibiting "Hello world" instead of "..."
index.tsx:
import useSample, { SampleProvider } from './useSample'
const Example = () => {
const { greetWorld } = useSample()
return (
<SampleProvider>
{greetWorld ? 'Hello world' : '...'}
</SampleProvider>
)
}
useSample.tsx
import React from 'react'
type SampleContextType = {
greetWorld: boolean
}
const SampleContext = React.createContext<SampleContextType>({
greetWorld: false
})
export const SampleProvider = ({ children }: { children: React.ReactNode }) => {
const [greetWorld, setGreetWorld] = React.useState(true)
const value = React.useMemo(() => ({
greetWorld,
setGreetWorld
}), [greetWorld, setGreetWorld])
return <SampleContext.Provider value={value}>{ children }</SampleContext.Provider>
}
const useSample = () => {
const { greetWorld } = React.useContext(SampleContext)
return { greetWorld }
}
export default useSample
From my current understanding, greetWorld in index.tsx would get its value based on useSample's greetWorld, which in turn would be the greetWorld value given in SampleProvider, which is true. I've tried logging the greetWorld inside SampleProvider, and it shows true, so I'm assuming that SampleProvider is being reached properly, but I have no idea why things aren't being updated.
Regarding similar issues, this seemed rather similar, but in my simplified code there's no tag order to respect in the first place, so it can't be that, and this also seemed a little like my problem, but from what I can see, it seems like the consumer is the child already.
I get the feeling that the solution is rather obvious, as I'm unfamiliar with context hooks, but I wasn't able to find it. On a side note, since I'm also unfamiliar with memoization, I left it there, as it could be among the causes of the problem, but I also tried removing it and the problem persisted.

How do you access query arguments in getSelectors() when using createEntityAdapter with RTK Query

I've been following along the REDUX essentials guide and I'm at part 8, combining RTK Query with the createEntityAdapter. I'm using the guide to implement it in a personal project where my getUni endpoint has an argument named country, as you can see from the code snippet below.
I'm wondering is there anyway to access the country argument value from the state in universityAdaptor.getSelector(state => ) at the bottom of the snippet, as the query key name keeps changing.
import {
createEntityAdapter,
createSelector,
nanoid
} from "#reduxjs/toolkit";
import {
apiSlice
} from "../api/apiSlice";
const universityAdapter = createEntityAdapter({})
const initialState = universityAdapter.getInitialState();
export const extendedApiSlice = apiSlice.injectEndpoints({
endpoints: builder => ({
getUni: builder.query({
query: country => ({
url: `http://universities.hipolabs.com/search?country=${country}`,
}),
transformResponse: responseData => {
let resConvert = responseData.slice()
.sort((a, b) => a.name.localeCompare(b.name))
.map(each => {
return { ...each,
id: nanoid()
}
});
return universityAdapter.setAll(initialState, resConvert)
}
})
})
});
export const {
useGetUniQuery
} = extendedApiSlice;
export const {
selectAll: getAllUniversity
} = universityAdapter.getSelectors(state => {
return Object.keys({ ...state.api.queries[<DYNAMIC_QUERY_NAME>]data }).length === 0
? initialState : { ...state.api.queries[<DYNAMIC_QUERY_NAME>]data }
})
UPDATE: I got it working with a turnery operator due to the multiple redux Actions created when RTK Query handles fetching. Wondering if this is best practice as I still haven't figured out how to access the country argument.
export const { selectAll: getAllUniversity } = universityAdapter
.getSelectors(state => {
return !Object.values(state.api.queries)[0]
? initialState : Object.values(state.api.queries)[0].status !== 'fulfilled'
? initialState : Object.values(state.api.queries)[0].data
})
I wrote that "Essentials" tutorial :)
I'm actually a bit confused what your question is - can you clarify what specifically you're trying to do?
That said, I'll try to offer some hopefully relevant info.
First, you don't need to manually call someEndpoint.select() most of the time - instead, call const { data } = useGetThingQuery("someArg"), and RTKQ will fetch and return it. You only need to call someEndpoint.select() if you're manually constructing a selector for use elsewhere.
Second, if you are manually trying to construct a selector, keep in mind that the point of someEndpoint.select() is to construct "a selector that gives you back the entire cache entry for that cache key". What you usually want from that cache entry is just the received value, which is stored as cacheEntry.data, and in this case that will contain the normalized { ids : [], entities: {} } lookup table you returned from transformResponse().
Notionally, you might be able to do something like this:
const selectNormalizedPokemonData = someApi.endpoints.getAllPokemon.select();
// These selectors expect the entity state as an arg,
// not the entire Redux root state:
// https://redux-toolkit.js.org/api/createEntityAdapter#selector-functions
const localizedPokemonSelectors = pokemonAdapter.getSelectors();
const selectPokemonEntryById = createSelector(
selectNormalizedPokemonData ,
(state, pokemonId) => pokemonId,
(pokemonData, pokemonId) => {
return localizedPokemonSelectors.selectById(pokemonData, pokemonId);
}
)
Some more info that may help see what's happening with the code in the Essentials tutorial, background - getLists endpoint takes 1 parameter, select in the service:
export const getListsResult = (state: RootState) => {
return state.tribeId ? extendedApi.endpoints.getLists.select(state.tribeId) : [];
};
And my selector in the slice:
export const selectAllLists = createSelector(getListsResult, (listsResult) => {
console.log('inside of selectAllLists selector = ', listsResult);
return listsResult.data;
// return useSelector(listsResult) ?? [];
});
Now this console logs listsResult as ƒ memoized() { function! Not something that can have .data property as tutorial suggests. Additionally return useSelector(listsResult) - makes it work, by executing the memoized function.
This is how far I got, but from what I understand, the code in the Essentials tutorial does not work as it is...
However going here https://codesandbox.io/s/distracted-chandrasekhar-r4mcn1?file=/src/features/users/usersSlice.js and adding same console log:
const selectUsersData = createSelector(selectUsersResult, (usersResult) => {
console.log("usersResult", usersResult);
return usersResult.data;
});
Shows it is not returning a memorised function, but an object with data on it instead.
Wonder if the difference happening because I have a parameter on my endpoint...
select returns a memoized curry function. Thus, call it with first with corresponding arg aka tribeId in your case and then with state. This will give you the result object back for corresponding chained selectors.
export const getListsResult = (state: RootState) => {
return state.tribeId ? extendedApi.endpoints.getLists.select(state.tribeId)(state) : [];
};
The intention of the getUni endpoint was to produce an array of university data. To implement the .getSelector function to retrieve that array, I looped over all query values, searching for a getUni query and ensuring they were fulfilled. The bottom turnery operator confirms the getUni endpoint was fired at least once otherwise, it returns the initialState value.
export const { selectAll: getAllUniversity } = universityAdapter
.getSelectors(state => {
let newObj = {};
for (const value of Object.values(state.api.queries)) {
if (value?.endpointName === 'getUni' && value?.status === 'fulfilled') {
newObj = value.data;
}
}
return !Object.values(newObj)[0] ? initialState : newObj;
})

Should I use useEffect in this situation?

I don't know if I am allowed to ask questions like these, but I have a dilemma where I don't know should I use useEffect in the situation I have here:
const handleUrlQuery = () => {
if (params.debouncedValue.length > 0) {
queryName = "name";
queryValue = params.debouncedValue;
return {
queryName,
queryValue,
};
} else if (params.debouncedValue === "") {
queryName = "page";
queryValue = params.page;
return {
queryName,
queryValue,
};
}
};
handleUrlQuery();
const url = `${process.env.REACT_APP_API_URL}?${queryName}=${queryValue}`;
const { data, error } = useFetch(url);
This function is used for changing the query part of the url, now it is supposed to change the queryName and queryValue based on the search value or in this case debounced search value. Now I am confused because I have a feeling that I need to use useEffect, but I am not sure, anyone has any advice on this?
If you really want to optimize this code, which unless its in a super heavy component, I don't see too much of a need you could use useMemo.
const url = useMemo(() => {
if (params.debouncedValue.length > 0) {
queryName = "name";
queryValue = params.debouncedValue;
} else if (params.debouncedValue === "") {
queryName = "page";
queryValue = params.page;
}
return `${process.env.REACT_APP_API_URL}?${queryName}=${queryValue}`;
}, [params.debouncedValue, params.page]);
// don't believe you have to add process.env.REACT_APP_API_URL as a dependency
const { data, error } = useFetch(url);
When you don't call the function handleUrlQuery inside a useEffect, it will be called on every re-render, even if params.debouncedValue didn't change.
Therefore, you need a useEffect if you have other state variables changing, and you only want to call handleUrlQuery when specifically params.debouncedValue changes.
Dummy Codesandbox example

Can I use ReactElement as useState argument?

I'm new in React and I wonder is using ReactElement as useState argument normal?
I try to do it and everything works fine. Is it anti-pattern or it's OK?
Unfortunately, I didn't find any information about it in documentation
const [infoBox, setInfobox] = useState<ReactElement|null>(null);
const catalogLoadedDataEmpty = useSelector(getCatalogLoadedDataEmptySelector);
const catalogHasErrors = useSelector(getCatalogHasErrorsSelector);
...
useEffect(() => {
let infoBoxTitle;
if (catalogLoadedDataEmpty) {
infoBoxTitle = t('pages.Brands.errors.noResults.title');
} else if (catalogHasErrors) {
infoBoxTitle = errorsByErrorCode[EErrorCodes.UNRECOGNIZED_ERROR](t);
} else {
setInfobox(null);
return;
}
setInfobox(<InfoBox
className={catalogInfoBoxClassname}
iconName={EInfoBoxIcon.error}
title={infoBoxTitle}
description={noResultsDescription}
/>);
}, [catalogLoadedDataEmpty, catalogHasErrors]);
You can, but it's easy to create bugs where you expect the page to update, but it doesn't, because you forgot to update the state. It's usually better to save data in state, and then use that data to render fresh elements on each render.
And in your case i'd go one step further: this shouldn't be a state variable at all. The values catalogLoadedDataEmpty and catalogHasErrors are enough to determine the desired output directly. You can thus remove the use effect, and in so doing get rid of the double-render that you currently have:
const catalogLoadedDataEmpty = useSelector(getCatalogLoadedDataEmptySelector);
const catalogHasErrors = useSelector(getCatalogHasErrorsSelector);
let infoBoxTitle;
if (catalogLoadedDataEmpty) {
infoBoxTitle = t('pages.Brands.errors.noResults.title');
} else if (catalogHasErrors) {
infoBoxTitle = errorsByErrorCode[EErrorCodes.UNRECOGNIZED_ERROR](t);
}
const infoBox = infoBoxTitle ? (
<InfoBox
className={catalogInfoBoxClassname}
iconName={EInfoBoxIcon.error}
title={infoBoxTitle}
description={noResultsDescription}
/>
) : null

which is the correct way while using props in functional component in react

which one is the correct way in react functional component and why?
Way 1:
export function renderElements(props) {
let { value, element } = props;
return (
<!--- Code----!>
)
}
renderElements.defaultProps = {
value: 0,
element: 'Hello'
}
Way 2
export function renderElements({
value = 0,
element = 'Hello'
}) {
return (
<!--- Code----!>
)
}
Can you please suggest?
There's no correct way, both ways can acceptable, depending on a case. The difference is that objects (<b>hi</b> is React element, which is an object) will be same with defaultProps. This may result in undesirable behaviour if prop values are mutated by a component:
export function renderElements(props) {
let { value, element } = props;
element.props.children = value; // affects all renderElements instances at once
return element;
}
renderElements.defaultProps = {
value: 0,
element: <b>hi</b>
}
This may be not a problem if objects are immutable (React.cloneElement in case of React element), which is preferable way to do things in React.
Both ways are correct. But for me, the second way is better because I am using it also code looks cool. You can initialize values in that case just to avoid code breaking and you are not receiving expected values
instead of using the function key wort, it's better to use es6 syntax for functional components.
`export const renderElements = ({
value = 0,
element = <b>hi</b>
}) => {
return (
<!--- Code----!>
)
}`
or if you want to return only a single element or object you can use shorter syntax like:
`export const renderElements = ({
value = 0,
element = <b>hi</b>
}) => <!--- Code----!>`

Resources