How to migrate from redux 'connect' to hooks 'useDispatch' and 'useSelector'? - reactjs

I'm not sure how to migrate from redux to hooks, as far as dispatching actions and retrieving state from store, as there are no official guides provided. Is this way correct?
import React, { useEffect } from 'react'
import { useSelector, useDispatch } from 'react-redux'
import { createSelector } from 'reselect'
import { actions, selectors } from 'data'
import TemplateComponent from './template'
const areaSelectors = selectors.components.areas
const modalActions = actions.modals
const selectAllAreas = createSelector(
areaSelectors.getAdminAreas,
areaSelectors.getGuestAreas,
(adminAreas, guestAreas) => adminAreas.concat(guestAreas)
)
const ContainerComponent = () => {
const dispatch = useDispatch()
const allAreas = useSelector(selectAllAreas)
const adminAreas = useSelector(areaSelectors.getAdminAreas)
useEffect(() => {
dispatch(
modalActions.showModal('AreaAddGuest')
)
}, [dispatch])
const passedProps = {
allAreas,
adminAreas
}
return <TemplateComponent {...passedProps} />
}
export default ContainerComponent

The selecting part seems correct,
the part with the dispatch doesn't.
I'm not sure what you're trying to do with the effect there, but the useDispatch is simple. It will give you a dispatch function, which you can then use to do dispatch(mdalActions.showModal('AreaAddGuest')) when you need that done.
The }, [dispatch]) part in the useEffect just tells useEffect to run whenever the dispatch function changes, which means probably never, but it also highly depend on the implementation. That seems almost certainly wrong.
If you can, post the code before adding the hooks, to see what the original was.
Also note that it's by no means necessary to rewrite code to use hooks. If the classes ever become obsoleted, it will most likely be far in the future.

Related

React Redux: state undefined

Playing around with react-redux and my state isCartVisible is showing undefined, I used simple functional components and I'm storing my stores in different files.
//main index.js file
import ReactDOM from 'react-dom/client';
import { Provider } from 'react-redux';
import './index.css';
import App from './App';
import store from './redux-store/store';
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(<Provider store={store}><App /></Provider>);
and
//App.js
import { useSelector } from "react-redux";
import Layout from "./components/Layout/Layout";
import Cart from "./components/Cart/Cart";
function App() {
const cartVisible = useSelector((state) => state.isCartVisible);
return (
<Layout>
{cartVisible && <Cart />}
</Layout>
);
}
and a component deep somewhere inside the app, by clicking the button I wanna toggle my <Cart> component
//CartButton.js
import { useDispatch } from "react-redux";
const CartButton = () => {
const dispatch = useDispatch();
const cartShowHandler = () => {
dispatch({ type: "cartToggle" });
};
return (
<button onClick={cartShowHandler}>
Click
</button>
);
};
and that's my store file, where I've created my store with reducer
import { createStore } from "redux";
const uiReducer = (state = { isCartVisible: true }, action) => {
if (action.type === "cartToggle") {
state.isCartVisible = !state.isCartVisible;
}
return state;
};
const uiStore = createStore(uiReducer);
export default uiStore;
You should never mutate the state. Your condition in reducer should look like this and it will work.
if (action.type === "cartToggle") {
return { ...state, isCartVisible: !state.isCartVisible};
}
As you have only one key in store in your example at the moment. You can do it this way also.
return { isCartVisible: !state.isCartVisible};
But it's always a good practice to return the whole state in your reducer's conditions.
Remember that redux do shallow comparison. Which means it checks if reference of an object is changed. In your case it wasnt changed.
Once, I wrote something about this topic in a blog post https://dev.to/machy44/shallow-comparison-in-redux-3a6
In Redux, you can only have one store. So it is very likely that your useSelector call actually tries to select data from another store than you are expecting it here.
You could validate that by using something like
const fullState = useSelector(state => state)
console.log(fullState)
That said, you are also writing a style of Redux here that is outdated by many years - in modern Redux you are not writing switch..case reducers or string action types. Also, createStore is deprecated at this point in favor of configureStore.
I would highly recommend you read about modern Redux and then follow the official Redux tutorial.
Whatever resources you have been following right now might have given you a very skewed view of how to use Redux.

Invalid hook call error when showing/hiding component with React Redux

I'm attempting to create a React/Redux component that shows/hides an element when clicked.
I'm using this to trigger the function from another component:
import React from 'react'
//Some other code...
import { useSelector } from 'react-redux'
import onShowHelpClicked from '../help/AddHelpSelector'
<button onClick={onShowHelpClicked}>Help</button>
This this is AddHelpSelector:
import { useState } from 'react'
import { useDispatch } from 'react-redux'
import { helpVisible } from './HelpSlice'
export const AddHelp = () => {
const [isVisible, showHelp] = useState('')
const dispatch = useDispatch()
const onShowHelpClicked = () => {
dispatch(
helpVisible({
isVisible,
})
)
if (isVisible) {
showHelp(false)
} else {
showHelp(true)
}
}
return (
<section>
<h2 style={{ visibility: { isVisible } }}>Help section</h2>
</section>
)
}
export default AddHelp
Finally, this is HelpSlice
import { createSlice } from '#reduxjs/toolkit'
const initialState = [{ isVisible: false }]
const helpSlice = createSlice({
name: 'help',
initialState,
reducers: {
helpVisible(state, action) {
state.push(action.payload)
},
},
})
export const { helpVisible } = helpSlice.actions
export default helpSlice.reducer
I'm fairly certain I'm doing multiple things wrong, as this is my first attempt to do anything with Redux and I'm still struggling to wrap my mind around it after a week of learning.
But specifically, when clicking the help button I get this error.
"Uncaught Error: Invalid hook call. Hooks can only be called inside of the body of a function component.
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"
The linked documentation provides a way to test a component to see if React is importing properly, and it's not. But I'm not really sure what I'm doing wrong.
I think it may be that I'm importing React multiple times, but if I don't then I can't use "useState."
What's the correct way to do this? I'm open to corrections on both my code as well as naming conventions. I'm using boilerplate code as a template as I try to understand this better after getting through the documentation as well as Mosh's 6 hour course which I just finished.
You're importing the < AddHelpSelector /> component here import onShowHelpClicked from '../help/AddHelpSelector', and then you try to use it as a callback handler for the button's onClick, which doesn't really make sense. I assume you actually wanted to only import the onShowHelpClicked function declared inside the < AddHelpSelector /> component (which is not really a valid way of doing it). Since you want to control the visibility using redux state, you could just grab the flag from the redux store inside the < AddHelpSelector /> component using useSelector hook. To set it, you're gonna do that in the component where your button is. For that, you just need to dispatch an action(like you already did), with the updated flag. No need for the local useState. Also, using the flag you could just conditionally render the element.
const App = () => {
const dispatch = useDispatch();
const { isVisible } = useSelector((state) => ({ isVisible: state.isVisible }));
const handleClick = () => {
dispatch(
helpVisible({
!isVisible,
})
)
}
return (<button onClick={handleClick}>Help</button>);
}
export const AddHelp = () => {
const { isVisible } = useSelector((state) => ({ isVisible: state.isVisible }));
return (
<section>
{isVisible && <h2>Help section</h2>}
</section>
)
}
export default AddHelp

Invalid hook call. Hooks can only be used inside of a react function component... useEffect, redux

I encountered a problem with my react application related to hooks. Technologies being used: React, Redux, Apollo, ChakraUI.
Here is the React component that is troubling me:
import React, { useEffect } from "react";
import { Flex, Container, Heading, Text } from "#chakra-ui/react";
import { connect, useSelector, useDispatch } from "react-redux";
import { State } from "../state/store";
import { fetchRecipes } from "../state/recipe/actions";
interface RecipesListProps {}
const RecipesList: React.FC<RecipesListProps> = ({}) => {
const recipes = useSelector<State>(
(state) => state.recipe.recipes
) as State["recipe"]["recipes"];
const loading = useSelector<State>(
(state) => state.recipe.loading
) as State["recipe"]["loading"];
const dispatch = useDispatch();
useEffect(() => {
dispatch(fetchRecipes());
}, []);
if (loading) {
return <h1>Loading....</h1>;
}
return (
<Flex
m="auto"
mt="5rem"
w="50%"
direction="column"
justifyContent="center"
alignItems="center"
>
<Heading>Your Recipes</Heading>
<Flex mt="2rem" direction="column" w="100%" padding="0" gridGap="2rem">
{recipes &&
recipes.map((recipe) => (
<Container
key={recipe.id}
bg="orange.100"
borderRadius="0.2rem"
padding="1rem"
maxW="100%"
>
<Text fontSize="xl" fontWeight="bold">
{recipe.title}
</Text>
<Text>{recipe.description}</Text>
</Container>
))}
</Flex>
</Flex>
);
};
export default RecipesList;
Notice the use of the useEffect() hook. This is the error I am getting:
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
I am pretty sure I am disobeying rule number, 2 i.e. I am breaking the Rules of Hooks. As soon as I take the useEffect() call out of the component, it doesn't throw an error.
Could someone please give some guidance as to what I am doing wrong?
Thanks.
Edit:
The fetchRecipes function is a Redux thunk function that fetches recipes from a graphql server
Update:
I have been hacking away at a solution to this problem. I replaced the dispatch(fetchRecipes()) call with a console.log("hello world"), and it worked perfectly!
This is boggling my mind! Is this a problem with the fetchRecipes function?
Edit:
Here's the code for the fetchRecipes function:
export const fetchRecipes = () => {
return (dispatch: Dispatch) => {
dispatch(fetchRecipesPending());
const { data } = useRecipesQuery();
const errors = data?.recipes.errors;
const recipes = data?.recipes.recipes;
if (errors?.length) {
dispatch(fetchRecipesFailure(errors));
} else {
dispatch(fetchRecipesSuccess(recipes));
}
};
};
useRecipesQuery is a custom hook that was auto generated using the graphql-codegen library. It builds up on the useQuery hook from the #apollo/client library.
Your useEffect needs a little rewrite. You are dispatching the function fetchRecipes which in itself is a hook, but the thing dispatched should be a plain "action" (using Redux terminology here). So I guess we can fix that by breaking up your fetchRecipes fn.
A snippet of the component would now look like following:
const { data } = useRecipesQuery();
useEffect(() => {
if (!data) {
dispatch(fetchRecipesPending()) // I only assume you fetch on render
}
if (data?.recipes?.errors) {
dispatch(fetchRecipesFailure(data?.recipes.errors)
}
if (data?.recipes?.recipes) {
dispatch(fetchRecipesSuccess(data?.recipes?.recipes)))
}
}, [data]);
Now it should be fine AND more readable. Either way, as some has already suggested, I would think about using some more standardised way like using Redux w/ Thunks or Sagas, or, even better - I see you might be doing a GQL query - if so, just use a hook for it and handle the data with Apollo Client.

How to use React hooks + Redux

Is this the best way to connect Redux store with hooks?
Hi I am making a simple todo app using React hooks connected to Redux. The pattern I created works but I wonder if I'm doing it right, is there something wrong with this approach, is there a different pattern one should use?
App.jsx
const [initialTodos, updateTodos] = useState(store.getState());
const cleanup = store.subscribe(() => updateTodos(store.getState()));
useEffect(() => {
return () => cleanup();
});
dispatching happens in other components + the todo app works
Thank you for any input
react-redux package supports hooks since v7.1. Redux hooks can be used instead of the connect() function.
This is an example implementing a counter where the counter value is managed by Redux.
import React from 'react'
import { useDispatch, useSelector } from 'react-redux'
export const CounterComponent = () => {
const dispatch = useDispatch();
const counter = useSelector(state => state.counter);
return (
<div>
<span>{counter}</span>
<button onClick={() => dispatch({ type: 'INCREMENT_COUNTER' })}>
Increment counter
</button>
</div>
);
}
Source: https://react-redux.js.org/api/hooks
It turns out it is better too use 'react-redux' connect.
const mapStateToProps = state => ({state: state})
connect(mapStateToProps)(Component)
since connect automagically takes care of store.subscribe etc.
So drop hooks all together :)

Can I replace context with hooks?

Is there a way with new react hooks API to replace a context data fetch?
If you need to load user profile and use it almost everywhere, first you create context and export it:
export const ProfileContext = React.createContext()
Then you import in top component, load data and use provider, like this:
import { ProfileContext } from 'src/shared/ProfileContext'
<ProfileContext.Provider
value={{ profile: profile, reloadProfile: reloadProfile }}
>
<Site />
</ProfileContext.Provider>
Then in some other components you import profile data like this:
import { ProfileContext } from 'src/shared/ProfileContext'
const context = useContext(profile);
But there is a way to export some function with hooks that will have state and share profile with any component that want to get data?
React provides a useContext hook to make use of Context, which has a signature like
const context = useContext(Context);
useContext accepts a context object (the value returned from
React.createContext) and returns the current context value, as given
by the nearest context provider for the given context.
When the provider updates, this Hook will trigger a rerender with the
latest context value.
You can make use of it in your component like
import { ProfileContext } from 'src/shared/ProfileContext'
const Site = () => {
const context = useContext(ProfileContext);
// make use of context values here
}
However if you want to make use of the same context in every component and don't want to import the ProfileContext everywhere you could simply write a custom hook like
import { ProfileContext } from 'src/shared/ProfileContext'
const useProfileContext = () => {
const context = useContext(ProfileContext);
return context;
}
and use it in the components like
const Site = () => {
const context = useProfileContext();
}
However as far a creating a hook which shares data among different component is concerned, Hooks have an instance of the data for them self and don'tshare it unless you make use of Context;
updated:
My previous answer was - You can use custom-hooks with useState for that purpose, but it was wrong because of this fact:
Do two components using the same Hook share state? No. Custom Hooks are a mechanism to reuse stateful logic (such as setting up a subscription and remembering the current value), but every time you use a custom Hook, all state and effects inside of it are fully isolated.
The right answer how to do it with useContext() provided #ShubhamKhatri
Now i use it like this.
Contexts.js - all context export from one place
export { ClickEventContextProvider,ClickEventContext} from '../contexts/ClickEventContext'
export { PopupContextProvider, PopupContext } from '../contexts/PopupContext'
export { ThemeContextProvider, ThemeContext } from '../contexts/ThemeContext'
export { ProfileContextProvider, ProfileContext } from '../contexts/ProfileContext'
export { WindowSizeContextProvider, WindowSizeContext } from '../contexts/WindowSizeContext'
ClickEventContext.js - one of context examples:
import React, { useState, useEffect } from 'react'
export const ClickEventContext = React.createContext(null)
export const ClickEventContextProvider = props => {
const [clickEvent, clickEventSet] = useState(false)
const handleClick = e => clickEventSet(e)
useEffect(() => {
window.addEventListener('click', handleClick)
return () => {
window.removeEventListener('click', handleClick)
}
}, [])
return (
<ClickEventContext.Provider value={{ clickEvent }}>
{props.children}
</ClickEventContext.Provider>
)
}
import and use:
import React, { useContext, useEffect } from 'react'
import { ClickEventContext } from 'shared/Contexts'
export function Modal({ show, children }) {
const { clickEvent } = useContext(ClickEventContext)
useEffect(() => {
console.log(clickEvent.target)
}, [clickEvent])
return <DivModal show={show}>{children}</DivModal>
}

Resources