Component wont re-render when changes happens to redux store - reactjs

I have problems with using Redux saga with react hooks. It clearly states that useSelector() will subscribe to the Redux store, and run the selector whenever an action is dispatched.
here is the component im trying to update:
import React from 'react';
import { useSelector} from "react-redux";
const InfoPage = () => {
const summoner = useSelector(state => state.summoner);
const loading = useSelector(state => state.loading);
console.log(loading,summoner) {*/Both undefined and only gets called once*/}
if(!summoner){
return <div>Loading...</div>
}
return(
<div>
{summoner.name}
</div>
)
}
export default InfoPage;
The component gets called via a history.push('/infopage') when I dispatch the action to fetch the "summoner", which again dispatches another action when successfully managing to fetch summoner. This should rerender my InfoPage if i understand this right.
My redux store has the values after the fetch, but the InfoPage won't rerender, heres the last state:
heres also my reducer

I found the problem... I've forgot that my saga reducer is called "saga" when exporting it. so to fix this problem i had to change:
const summoner = useSelector(state => state.summoner);
to
const summoner = useSelector(state => state.saga.summoner);

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.

I am not getting console logged my state change by one of the reducers

When I am dispatching an action of the loader, the reducer is changing the state when I see in redux logger but it is not console logged in the line next to when I have dispatched the action to change the state.
import React from 'react';
import { useEffect } from 'react';
import { useState } from 'react';
import {connect} from 'react-redux';
import {loader} from "../../actions/loaderAction";
import './Loader.css';
function LoaderPage(props) {
let [showloader,setshowloader] = useState(0);
const testfunc = ()=>{
props.loader(true);
console.log(props.Loader);
}
useEffect(()=>{
testfunc();
// eslint-disable-next-line
},[]);
return (
<>
{showloader
?
<div className="loader-comp">
<div className="animated yt-loader"></div>
<div className="mask"></div>
</div>
:
""
}
</>
)
}
const mapStateToProps= (state) => (
{
Loader:state.Loader
}
)
export default connect(mapStateToProps, {
loader
})(LoaderPage);
I know reducer returns new state asynchronously, so when I should use console.log for props.Loader
Any help or suggestion is valuable.
When you pass an empty array as second argument to useEffect means the code will only execute on mount. And your log doesn't reflect because state update is not sync.
You want log to be executed everytime on Loader changes. Remove your console.log from your function. Create another useEffect to log your Loader and pass Loader to array, which means your code will execute on Loader changes:
useEffect(()=>{
console.log(props.Loader);
},[props.Loader]);
You can use redux dev tools extension instead of console.log to view and manage your state. It is so simple to use and perfectly built
try using callback pattern
const callbackFunction = () => console.log("Some code");
const loader = (option = {}) => {
if(option.cb){
option.cb();
}
};
const option = {};
option.cb = callbackFunction;
loader(option);

How to call functions with API calls in an action using Redux-hooks useDispatch from component?

I'm doing a project using redux-hooks, I m new to redux-hooks. I am having actions.js file having different API call function. and to dispatch the action from a component using useDispatch(). do I have to import every function from actions.js to my different component to dispatch the action? or any methods are there? thanks in advance
Before react-redux incorporated hooks into their library it was typical to split the parts of redux into their own container file. There you would map the actions and state that you needed into props that you would pass into the component. For example containers and components would look something like this.
Container Example.js:
// Node Modules
import {connect} from 'react-redux';
// Actions
import {action1, action2} from '../actions';
// Components
import ExampleComponent from './ExampleComponent';
// Actions
const mapDispatchToProps = (dispatch) => ({
action1: () => dispatch(action1()),
action2: () => dispatch(action2()),
});
// State
const mapStateToProps = (state) => ({
state1: state.example.state1,
state2: state.example.state2,
});
export const Example = connect(mapStateToProps, mapDispatchToProps)(ExampleComponent);
Component ExampleComponent.jsx:
// Node Modules
import React from 'react';
const Example = (props) => (
<div>
<label>{props.state1}</label>
<label>{props.state1}</label>
<button onClick={props.action1}>Action 1</button>
<button onClick={props.action2}>Action 2</button>
</div>
);
export default Example;
Although you could write both the container and component elements together in one file, this is just an example of how Redux could be used within React. With the introduction of hooks into react-redux you can now access the Redux store through their provided hooks.
Component ExampleComponent.jsx:
// Node Modules
import React from 'react';
import {useDispatch, useSelector} from 'react-redux';
// Actions
import {action1, action2} from '../actions';
const Example = (props) => {
// Dispatch
const dispatch = useDispatch();
// Redux State
const state1 = useSelector((state) => state.example.state1);
const state2 = useSelector((state) => state.example.state2);
return (
<div>
<label>{state1}</label>
<label>{state1}</label>
<button onClick={() => dispatch(action1())}>Action 1</button>
<button onClick={() => dispatch(action2())}>Action 2</button>
</div>
);
};
export default Example;
With this method you can import your component directly from the ExampleComponent.jsx file instead of having to import it through a container. For your actions, you only need to import what you need to use for the component and wrap it with the dispatch hook that react-redux provides.

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

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.

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

Resources