useDispatch using lower version of react-redux - reactjs

I am encountering a function not found using useDispatch. Supposedly, I should be using react-redux, unfortunately, my version is 6.0.0 and what's needed is 7.0.0 and above. I'd like to explore useDispatch or related hook to dispatch an action function inside a function component. How do I do this given that I cannot upgrade my react-redux & still use function component?
My alternative is to use class component, but again, I'd like to see this work in function component. Let me know what other details needed.
Here's my code.
import React, { useDispatch } from 'react';
import {
Tooltip,
IconButton,
Icon,
} from '#material-ui/core';
import { logout } from './auth/store/actions';
const QuickLogout = () => {
const dispatch = useDispatch();
const handleClick = () => {
dispatch(logout());
};
return (
<Tooltip title="Logout" placement="bottom">
<IconButton className="w-64 h-64" onClick={handleClick}>
<Icon>exit_to_app</Icon>
</IconButton>
</Tooltip>
);
};
export default QuickLogout;
And here's the error when compiling.
TypeError: Object(...) is not a function
QuickLogout
webpack-internal:///.....onents/QuickLogout.js:18:75
Edit: I mentioned exploring useDispatch or related hooks given that I cannot upgrade. I felt that this is a gray statement as I really just wanted my code to work. That means, solution is not limited to useDispatch or other hooks. Hence, I chose the answer that did not use useDispatch or hooks because it was plain simple. Apologies for the vague statement. I'll take note on improving my writing skills.

React itself doesn't have useDispatch in it Hooks API, but it has useReducer to eliminate redux for a small project that redux is an overengineering process for them.
On the other hand, new versions of react-redux provide new handy hooks which you can make a similar version of those by yourself.
Here is a Tip
Hooks (React hooks) can only be used in functional component
HOC (Higher-order component) can be used anywhere
The connect function which react-redux provides is HOC so it can also be used with both class-base and functional components. It is also a good practice to separate your presentation UI and logic into representationals and containers, hence there will be no problem for your component to be functional and use all fancy things that could be used in traditional class-component and connect by react-redux.
you can also make your custom hook for handling such things in a re-usable manner with less code.
// setup.js
const store = createStore(/* put your reducer and enhancers here */)
export const { dispatch, getState } = store
// hooks.js
import { dispatch, getState } from '~setup.js'
export const useDispatch = () => dispatch
export const useSelector = selector => selector(getState())
// The above useSelector wont cause your component to re-render on data change
Then in your component
import { useDispatch } from '~hooks.js'
import { logout } from './auth/store/actions'
export default function QuickLogout() {
const dispatch = useDispatch()
const handleClick = () => {
dispatch(logout())
}
return (
<Tooltip>
<IconButton onClick={handleClick}>
<Icon>exit_to_app</Icon>
</IconButton>
</Tooltip>
)
}
If you want to know more about custom hooks see making my own hook.

If you can't upgrade your react-redux version to one with the useDispatch and useSelector React hooks you can still use the connect Higher Order Component to inject dispatch, or to even more simply wrap your action creators with a call to dispatch.
import { connect } from 'react-redux';
const QuickLogout = ({ logout }) => { // <-- destructure logout prop
return (
<Tooltip title="Logout" placement="bottom">
<IconButton
className="w-64 h-64"
onClick={logout} // <-- assign to click handler
>
<Icon>exit_to_app</Icon>
</IconButton>
</Tooltip>
);
};
const mapDispatchToProps = {
logout, // <-- inject logout action as prop already wrapped by dispatch!
};
const ConnectedQuickLogout = connect(
null, // <-- this is typically mapStateToProps, but we don't need it
mapDispatchToProps,
)(QuickLogout);
export default ConnectedQuickLogout;
If you prefer to keep the code closer to as-is then you can simply connect it to the redux store and a dispatch prop is still injected.
import { connect } from 'react-redux';
const QuickLogout = ({ dispatch }) => {
const handleClick = () => {
dispatch(logout());
};
return (
<Tooltip title="Logout" placement="bottom">
<IconButton className="w-64 h-64" onClick={handleClick}>
<Icon>exit_to_app</Icon>
</IconButton>
</Tooltip>
);
};
export default connect()(QuickLogout);

Related

Cannot update a component (`TodoForm`) while rendering a different component (`TodoTask`). [SOLUTION] [React Redux To-Do App]

WHILE WRITING THIS POST I REALIZED WHAT THE SOLUTION WAS
Every time I dispatch a task to my store the following error occurs:
I have some idea of why it happens. It happens precisely when I try to get the to-do list using useSelector and then mapping through the list. However, the mapping is not the issue but rather returning a react component on the map function. It works just fine if I do not return a functional component and instead use HTML. So the issue, from my POV, is returning a react functional component while passing props to it on a map function.
Here's the code for my home component:
import Input from '../components/Input';
import TodoForm from '../components/TodoForm';
function Home() {
document.title = "MyTodo | Home"
return (
<div className="App">
<h1>MyTodo</h1>
<Input />
<TodoForm />
</div>
);
}
export default Home;
The input component where the action is being dispatched on key down:
import {useState} from 'react'
import { useDispatch } from 'react-redux';
import { todoActions } from '../store/todo';
const Input = () => {
const [inputText, setInputText] = useState("");
const dispatch = useDispatch();
const handleChange = (e) => setInputText(e.target.value)
const handleKeyPress = (event) => {
if (event.code === "Enter") {
// if the expression is false, that means the string has a length of 0 after stripping white spaces
const onlyWhiteSpaces = !inputText.replace(/\s/g, "").length;
!onlyWhiteSpaces &&
dispatch(
todoActions.addTask({ label: inputText, done: false })
);
setInputText("");
}
};
return (
<input
type="text"
onKeyDown={(e) => handleKeyPress(e)}
onChange={(e) => handleChange(e)}
value={inputText}
/>
);
}
export default Input
The TodoForm where I am using useSelector to get the todo list from the redux store and mapping thru it:
import { useSelector } from "react-redux";
import { v4 as uuidv4 } from "uuid";
import TodoTask from "./TodoTask";
const TodoForm = () => {
const tasks = useSelector((state) => state.todo.taskList);
const renderedListItems = tasks.map((task, index) => {
return (
<TodoTask
key={uuidv4()}
task={task}
targetIndex={index}
/>
);
});
return <div className="container">{renderedListItems}</div>;
};
export default TodoForm;
Finally the TodoTask component which is the child component being returned on the map function above:
import { useDispatch } from "react-redux";
import { todoActions } from "../store/todo";
const TodoTask = ({ task, targetIndex }) => {
const {text, done} = task;
console.log("Task: ", task);
const dispatch = useDispatch()
const removeTask = dispatch(todoActions.deleteTask(targetIndex))
return (
<div
className="alert alert-primary d-flex justify-content-between"
role="alert"
>
{text}
<button type="button" className="btn-close" onClick={()=>removeTask}></button>
</div>
);
};
export default TodoTask;
This is my first time facing this issue, and I know it has something to do with redux and how the useSelector hook forces a component to re-render. So the useSelector is re-rendering the TodoForm component, and since we are mapping and returning another component, that component is also being rendered simultaneously. At least, that is how I understand it. Let me know if I am wrong.
Things I have tried:
Wrapping the TodoTask in React.memo. Saw it somewhere as a possible solution to this kind of issue, but that did not work.
Passing shallowEqual as a second parameter on the TodoForm useSelector. This does prevent the page from going into an infinity loop, but the tasks show up empty but are being added to the redux store. However, with this method, the first warning stills shows up, and the console log in the TodoTask component does not execute.
Passing shallowEqual as a second parameter on the TodoForm useSelector. This does prevent the page from going into an infinity loop but the tasks show up empty but are being added to the redux store. However, with this method, the first warning stills shows up and the console log in the TodoTask component does not execute.
I realized what I was doing wrong while writing this part. The console log in the TodoTask component was working, but I had the browser console filtering for errors only. When I check the messages section, I saw everything working fine. Then when I checked the Task component, I noticed I was trying to read a property that did not exist and hence why the tasks had no text.
In other words, the solution was adding shallowEqual as second parameter of the useSelector hook in my TodoForm component that was the one mapping thru the todo tasks array. As I said, useSelector forces a component to re-render. shallowEquals checks if the existing state isn't the same as we already had and avoids unnecessary re-renders, which can lead my application to exceed the maximum update length.
Code fix [Solution]:
import { memo } from "react";
import { shallowEqual, useSelector } from "react-redux";
import { v4 as uuidv4 } from "uuid";
import TodoTask from "./TodoTask";
const TodoForm = () => {
// shallowEqual prevents unnecessary re-renders which can lead to an infinite loop
// it compares the current state with the previous one, if they are the same, it does not re-render the component
const tasks = useSelector((state) => state.todo.taskList, shallowEqual);
const renderedListItems = tasks.map((task, index) => {
return (
<TodoTask
key={uuidv4()}
task={task}
targetIndex={index}
/>
);
});
return <div className="container">{renderedListItems}</div>;
};
export default memo(TodoForm);
Honestly, I have been stuck on this since yesterday and I cannot believe I realize the solution just when I was about to ask for help. Hope this helps anyone else who faces a similar issue in the future.

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 call a action creator in a functional component with react-redux?

I'm learning react-redux. I am struggling a bit with the use of redux in functional components. How do I call an action from a functional component. I have seen some tutorials that use react hooks. Which makes sense to me. But these tutorials call action types and not functions that create action types.
My case:
Wrapping container: Im passing from a wrapping container component that manages all data the necassary props down to the LoadedNavbar function:
<LoadedNavbar isAuthenticated = {isAuthenticated} profile = {profile} />
Functional Component: A Navbar with a button to log out. The logout action should be called in the functional component.How do i make the action creator logout available in this function?
import React, { Component } from "react";
import { logout } from "../../../../actions/auth";
import { Link } from "react-router-dom";
export function LoadedNavbar (props) {
const {isAuthenticated, profile} = props
return (
<div>
<button onClick={this.props.logout} className="nav-link btn btn-info btn-sm text-light">Logout</button>
</div>
)
}
Action
// LOGOUT USER
export const logout = () => (dispatch, getState) => {
axios
.post(apiBase + "/auth/logout/", null, tokenConfig(getState))
.then(res => {
dispatch({
type: LOGOUT_SUCCESS
});
})
.catch((err) => {
dispatch(returnErrors(err.response.data, err.response.status));
});
};
Reducer
export default function(state = initialState, action) {
switch (action.type) {
case LOGOUT_SUCCESS:
default:
return state;
}
}
There are two ways to dispatch actions from functional components:
Using mapDispatachToProps function with connect higher order component, same as in class based components.
For details of how to use mapDispatchToProps along with connect to dispatch actions, see: React Redux - Connect: Dispatching Actions with mapDispatchToProps
Using useDispatch hook provided by react-redux.
If you want to use this hook, then you need to import it from the react-redux package. This hook returns a function that can be used to dispatch actions.
Example:
import React from "react";
import { logout } from "../../../../actions/auth";
import { useDispatch } from "react-redux";
function LoadedNavbar (props) {
...
const dispatch = useDispatch();
const handleClick = () => {
dispatch(logout());
}
return (
<div>
<button onClick={handleClick}>Logout</button>
</div>
)
}
For details of hooks provided by react-redux, see React Redux - Hooks

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 :)

Aliasing connected actions renders them invisible in an IDE

I want to connect / bind actions in the same file in which I am defining my component, and ideally would like this component to be functional. The problem is doing this means I must alias my action so as to avoid the eslint rule no-shadow. You can see this in the code block below. However, I also use an IDE and aliasing these actions renders them invisible to my IDE when trying to find all usages of said actions.
Is there a way I can connect these dispatched actions to my functional component while making said actions visible to my IDE for debugging?
import React from 'react';
import {connect} from 'react-redux';
import {actionOne, actionTwo} from '../../../../actions';
const ComponentOne = ({actionOneDispatch, actionTwoDispatch}) => {
const handleClick = () => {
actionOneDispatch();
actionTwoDispatch()
};
return (
<button onClick={handleClick}>Click Me</button>
);
};
const mapDispatchToProps = (dispatch) => ({
actionOneDispatch: () => {
dispatch(actionOne());
},
actionTwoDispatch: () => {
dispatch(actionTwo());
},
});
export default connect(null, mapDispatchToProps)(ComponentOne);

Resources