How to use a react hook in an arrow function? - reactjs

I'm designing an application in react native for medical use case. The idea is to record some audio and to send it to a local server. Therefore, the ip adress is defined at runtime.
Previously, I used a global const as adress (code below).
const url = 'http://192.168.43.56:5000/';
const checkIsServerOnline = async (): Promise<string> =>
axios.get(url, {
timeout: 5000,
});
Now I would like to retrieve the url from a redux (persistant) store before using the function. I tried something like that, but it didn't worked:
function CheckIsServerOnline(): AxiosPromise {
const { ipAdress } = useSelector(
(state: RootState) => state.config
);
return axios.get(ipAdress, {
timeout: 5000,
});
}
The error message it returned:
Invalid hook call. Hooks can only be called inside of the body of a
function component. This could happen for one of the following
reasons:
You might have mismatching versions of React and the renderer (such as React DOM)
You might be breaking the Rules of Hooks
You might have more than one copy of React in the same app
Do you have any idea how I would be able to retrieve the value from the redux state ?
(Apologies for the begineer question)

React hooks can be used ONLY inside of React components.
Since you are using Redux I think you should set up your URL call in the store initialization. Here's a link to redux docs about initializing state: https://redux.js.org/recipes/structuring-reducers/initializing-state
Once you have it in the redux state you will be able to retrieve it wherever you want thanks to mapStateToProps from redux.

Related

react-router-dom and redux subapp: useNavigator causes redux reload

I'm using react-router-dom v6. I'm versed with redux and react but only just starting to "go deep" with react-router-dom.
I have nested routes. One of the "nests" has access to the redux <Provider> context.
Despite navigating within the "nest" of Routes with access to the redux context, all of the rendered components in the Router context are being re-rendered - and with it resetting redux.
Finally, there are several reasons for this design, but one of them is so that I can initialize the middleware for a given project (specified in the url).
const middleware = (projectId) => {
let initialized = false;
return (store) => (next) => (action) => {
if (!initialized) {
initialized = true;
next({ type: "SET_READY", projectId });
}
next(action);
};
};
Here is a link to the sandbox that highlights the issue.
Thank you #DrewReese for engaging me on this issue.
In the sandboxed description of the issue, I solve the issue by memoizing the function that instantiates the store. The middleware has a two-step initialization process: (1) set the project id (2) load the store. The store value in the second step depends on what is returned by the server (i.e., if null, use a new store).
// core-app.jsx
// Three versions of the store instantiation process
// 1. resets store on every new page within the project detail view (bad)
// const storeWithProject = store(projectId);
// 2. ##INIT instantiates using initialState where projectId = null (see reducer)
// const storeWithProject = useMemo(() => store(projectId), [projectId]);
// 3. The comprehensive solution
const storeWithProject = useMemo(
() => store(projectId, seedState(projectId)),
[projectId]
);
This all said, in the actual app, I wasn't able to get use the redux <Provider> within the <BrowserRouter> context. I was not able to prevent a re-render of the SubApp component with every route change (as described above); my reliance on useEffect to fetch data (e.g., list of projects) at various points in the app is beyond my current skill-set :-/ (prop and state changes in context of react-router is tough-stuff to manage).
The solution that I'm working on now is to "raise" the redux provider to minimize the number of parents that might trigger a re-render. A somewhat disappointing conclusion because I can't leverage the "natural" entry point for when to instantiate the redux store.

How to use StoreApi in zustand

From the docs of zustand I found that together with set and get parameters they provide an additional parameter at StateCreator, called api.
Example bellow
import create, { StateCreator } from 'zustand'
import type { Store } from './store.types'
const globalStateCreator: StateCreator<Store> = (set, get, api) => ({
...mySlice(set, get, api),
})
What does it stands for? What are the best practices for using it?
If you're using React, you're probably using hooks to do something like this:
const color: PaletteColor = usePaintbrush((s) => s.color);
The hooks make zustand seem more self-contained than it really is. You have a lot of control over the state.
Hooks are limited to components, but you can use the api methods bound to the store from anywhere, imperatively.
// .ts
const color: PaletteColor = usePaintbrush.getState().color[shade];
// .tsx
const color = useRef<PaletteColor>(usePaintbrush.getState().color[shade]);
useEffect(() => usePaintbrush.subscribe((s) => (color.current = s.color[shade])), [shade])
Store actions are not required to be in the store either!
// at module level
const setColor(color: PaletteColor) => usePaintbrush.setState({ color })
You're unlikely to touch the api parameter unless you're creating a middleware.
Docs cover specific example usage with the persist middleware
The persist api enables you to do numbers of interactions with the persist middleware from inside or outside a React component.
references (updated #1033):
store without actions
persist middleware api
middleware that changes store type

In Next.js, how can I update React Context state with data from getServerSideProps?

I'm having a lot of trouble learning to properly load data into state in my todo app.
I have a next.js page component pages/index.tsx where I load data from my API via getServerSideProps and return it as a page prop called tasksData.
The tasksData is being returned properly and I can access them in my page component just fine via prop destructuring: const Home = ({ tasksData }: Home) => { }
I also have a React Context provider in _app.tsx called BoardProvider. This stores state for my task board, and employs useReducer() from the React Context API to update this state in context consumers, such as pages/index.tsx.
The challenge I am facing is how to make my UI's "source of truth" the state stored in my context provider (eg. const { { tasks }, dispatch } = useBoard();, rather than the page page props returned from my API (eg. the tasksData prop).
One approach I considered was to simply load the data in getServerSideProps and then set the state via a dispatched action in a useEffect hook:
useEffect(() => {
// On first render only, set the Context Provider state with data from my page props.
dispatch({ type: TaskAction.SET_TASKS, payload: tasksData });
});
However, this doesn't seem to be working because sometimes tasksData is undefined, presumably because Next.js has not yet made it available on page mount.
Another suggestion I heard was to fetch the data and pass it as pageProps to my Context Provider in _app.tsx. I believe this means using getInitialProps() in _app.tsx so that my provider's initial state is populated by my API. However, this disabled static optimization and other useful features.
Can anyone help me out with some pseudocode, documentation, or examples of how to use getServerSideProps in combination with React Context API?
Couple of points:
getServerSideProps should be invoked before the page is even rendered. So theoretically your tasksData is undefined is a bug! You can't have a server data to be unavailable unless you really really intend to have that happen in the first place.
Assuming getServerSideProps is always returning the right data, but you want to use your own data to override it. In your context, you can have this logic.
const Home = ({ tasksData }) => {
const value = { tasksData: {
// let me override it
}}
return (
<Context.Provider value={value}>
...
<Context.Provider>
)
}
If you have the context provided under a page, the above code is all you need. But if your context is provided in a root (parent of a page), you can still add the above code to re-provide the same context again with overridden value. Because this is how a context is designed, read https://javascript.plainenglish.io/react-context-is-a-global-variable-b4b049812028 for more info.

What is the correct way to use a hook while handling an event in React

My goal is to fetch data from an API using a custom hook after changing a Select option.
I have a onChange event on a Select-field that looks like this:
<Select options={customers.customers} onChange={updateMarkers} />
The function updateMarkers that is being called looks like this:
const updateMarkers = (selectedOption) => {
const warehouses = GQLQuery(`
{
warehouses(where: {customer: {id: "${selectedOption.value}"}}) {
name
address {
city
lat
lng
road
zipcode
housenumber
province {
name
}
country {
name
}
}
}
}
`)
console.log(warehouses)
}
Within this function I call a custom hook (GQLQuery) that fetches the data (which works if I am not calling it from an event) that looks like this:
import { gql, useQuery } from "#apollo/client";
function GQLQuery(query) {
const { loading, error, data } = useQuery(gql`${query}`);
if (loading) return <p>Loading...</p>;
if (error) return <p>Error :(</p>;
if (data) return data
}
The error I get after selecting an option is:
Uncaught Error: Invalid hook call. Hooks can only be called inside of the body of a function component. This could happen for one of the following reasons:
1. You might have mismatching versions of React and the renderer (such as React DOM)
2. You might be breaking the Rules of Hooks
3. You might have more than one copy of React in the same app
It is obvious that I am breaking the Rules of Hooks, because I just started using React and I am missing a lot of fundamentals, but I just can't understand what I am doing wrong here. Can someone point me to the documentation that I need to understand or point me towards the solution. Thanks!
Using react hooks useQuery() inside a function breaks the rules of hooks.
You need to use query(options): Promise method of apollo client to call API manually inside your event handler.
The Apollo client should be connected to React with the ApolloProvider component. See connect-your-client-to-react.
Then, you can use it like props.client.query({...}) in the event handler of the component.

Is it possible to create a React Hook factory function?

I have a React app that relies heavily on redux, react-redux, and redux-saga. I'm starting to experiment with React Hooks, and the useSelector and useDispatch hooks in react-redux, and I've run into an issue with how to essentially write a factory function to generate hooks for each of my redux nodes.
In general, I have a redux node for each API endpoint my app consumes. There are about 100 unique endpoints in this app, and so there are about 100 redux nodes. Each of those nodes then corresponds to one state-[node].js file, like state-users.js, etc. Those state files each encapsulate the endpoint they should call, trigger sagas to handle the HTTP lifecycle (start, fail, success, etc), and so on.
Over time, I've written code that abstracts much of the boilerplate away into utility functions, including functions that auto generate action creators, selectors, reducers, and the connect function. It's a bunch of code, and somewhat convoluted, but the gist looks something like this. First, I set up an array of objects describing actions that are possible for this redux node. A simplified version looks like this:
const messages = [
{ action: 'GET', creator: 'get', connect: true },
{ action: 'POST', creator: 'post', connect: true },
{ action: 'CLEAR', creator: 'clear', connect: true },
];
This describes that there will be three actions, get , post, and clear, and that they should be exposed in the connector. I have a set of common reducers (e.g. most get reducers are identical across nodes), so those are assumed here based on name.
Then I set up a list of selectors, like this:
const selectorKeys = ['data','pending','errors'];
...and then I have a factory function that I feed these arrays into, which looks something like this:
const { connector } = stateGenerators({
keyword: 'users', //redux node keyword
messages: messages,
selectorKeys: selectorKeys
})
This is a simplified version of how everything really works, but it's the crux of it. Again, all of the above code is abstracted into a state file, like state-users.js.
Then, in my class component, I just need to import the connector from state-users.js, like this:
import { connector } from 'state-users';
class Users extends Component {
componentDidMount() {
this.props.get();
}
componentWillUnmount() {
this.props.clear();
}
render () {
const { data } = this.props;
return (
<div>
{data.map()}
</div>
)
}
}
export connector()(Users)
This model does get clunky at times, but the nice part is that nearly all of the redux boilerplate is abstracted into common files, so my individual state files are, for the most part, really simple.
Now, to the question: is it possible to do something like this "factory function" approach with Hooks? So far in my experimenting I have not figured out how to do this. There are two fundamental issues:
First, you can't put hooks in loops, so I can't do this:
const selectors = {}
const reduxNodeKeyword = 'users';
['data','pending','errors'].map((selectorKey) => {
selectors[selectorKey] = useSelector((state) => state[keyword].selectorKey);
})
That code results in this error:
React hook "useSelector" cannot be called inside of a callback.
In practice, that means I can't just pass in an array of selector keys I'd like and then have it spit back my selectors.
Second, you can't put hooks inside conditionals. So since the first idea failed, I tried a different approach in my factory function, which looks something like this:
if (_.includes(stateSelectors, 'data')) {
result['data'] = useSelector((state) => state[keyword].data);
}
That results in this error:
React hook "useSelector" is called conditionally. React Hooks must be called in the exact same order in every component render
So that's also a bummer. But I think what I'm left with is that for each of my 100 Redux nodes, I would have to write a custom, and verbose, hook to more or less replicate connect.
I know, I know, hooks should encourage me to think differently, but I still need to fetch data from the server and provide it to components, and I need to follow that same basic pattern 100 times. Even if hooks makes my components more elegant (as I expect it would), the thought of writing 100 or so hooks out by hand, each with a fair amount of repeated data, rather than somehow auto-creating them using some sort of factory approach, gives me hives.
Help?
Thanks!
Not sure if this will be useful for others, but I found an approach that works for me. I can't put hooks in iterators, nor in if statements, but I do have common patterns all over the place. So the answer was to abstract those common patterns into generic hooks. For example, I have a hook called useCommonReduxListSelectors, which looks like this:
export const useReduxListCommonSelectors = (keyword: string) => {
return {
data: useSelector((state: any) => state[keyword].data),
errorMessage: useSelector((state: any) => state[keyword].errorMessage),
filters: useSelector((state: any) => state[keyword].filters),
pending: useSelector((state: any) => state[keyword].pending),
totalCount: useSelector((state: any) => state[keyword].totalCount)
};
};
I have lots of Redux state nodes that are responsible for handling lists returned from an API, and the data shape of most of those list endpoints is what you see above. So then the hook that a component would invoke uses useReduxListCommonSelectors, like this:
export const useReduxState = ({ id, defaultValues }) => {
const selectors = useReduxListCommonSelectors({
keyword:'users'
});
return {
...selectors,
};
};
And then obviously useReduxState can have any other data in there (actionCreators, custom selectors, etc) that is required by that node. So it allows me to abstract the most common patterns into reusable hooks, and also have the flexibility to add custom code to each useReduxState instance as needed.

Resources