Redux state for particular component in a list - reactjs

I'm learning react and redux. I'm creating a todo app but I'm facing a issue.
Let's say we have a list like
List.js
import React from 'react'
function List() {
return (
<div>
<ListItem>Text</ListItem>
<ListItem>Text</ListItem>
<ListItem>Text</ListItem>
<ListItem>Text</ListItem>
</div>
)
}
export default List
Then we have
ListItem.js
import React, { useState, useEffect } from "react";
import { useSelector } from "react-redux";
function ListItem() {
//This is some local state
const [isLoading, setLoading] = useState(false);
const loading = useSelector(state => state.loading);
// This is the loading flag which is coming from the reducer.. Which will be either true or false
useEffect(() => {
setLoading(true);
}, [loading]);
//Whenever the loading flag from the reducer changes, it changes the local state
return <div>{isLoading ? "loading" : "Hey, Im a list item"}</div>;
}
export default ListItem;
And let's say all listItem component have a click handler which dispatch an action to the change the loading flag in the reducer.
Now the issue I'm facing is as all the listItem are listening/subscribed to the same reducer, whenever the isLoading flag in the reducer changes, all the listItem start showing loading which is conditionally rendered from the return statement as shown above.
Expected Behaviour (or intended behaviour I must say)
I want to show loading on only that element that was clicked and whose local state was changed. But unfortunately my logic has a flaw that it all elements local states changes when the isLoading flag in the reducer changes

What you can do is provide key to each list item and assign loading to it in your reducer e.g
loading:{
List1: isLoading,
List2: notLoading
}

Related

React update useState() when variable value changes

How to update UseState() when variable 'matchUrl' changes. when navigating to another route the 'matchUrl' value changes. and the changes is reflected on console.log and on the 'h1' tag. but not the usestate(), it keeps the old value.
import React, {useState} from 'react'
import useInfinityScroll from './hooks/useInfinityScroll'
import names form './data/names'
function TableList({ match }) {
let matchUrl = match.params.name
const dataMatch = names.filter(name => name.occupation === matchUrl)
const [listNames, setListNames] = useState(dataMatch.slice(0, 10))
const [isFetching, setIsFetching] = useInfinityScroll(fetchMoreListItems) //fetches more items when scrolling to bottom of page
function fetchMoreListItems() {
setTimeout(() {
setListNames([...dataMatch.slice(0, 20)])
setIsFetching(false)
}, 2000)
}
return (
<>
<h1>{matchUrl}</h1>
<div>
{listNames.filter(name => name.occupation === matchUrl).map(listNames => <Table key={listNames.id} name={listNames} />)}
</div>
</>
)
}
export default TableList
useState hook takes initial value of the state as an argument, but it only assign the initial value to the state at the component mounting phase, not during re-rendering of the component if anything changes. Therefore, after initial rendering, state won't be changed even if the dataMatch variable changes.
Therefore, you need to have useEffect hook having dependency on dataMatch variable to keep track on its changes. Then you will be able to update the state when you've component re-rendering.
Use the following way to update the state.
useEffect(() => {
setListNames(dataMatch.slice(0, 10));
}, [dataMatch]);
For more information on useEffect hook, please refer https://reactjs.org/docs/hooks-effect.html
Hope this will solve your issue.
You haven't set the state for 'matchUrl'.Do the following modification to your code.
function TableList({ match }) {
let [matchUrl, setMatchUrl] = useState(match.params.name);
return (<><h1>{matchUrl}</h1></>);
}
Also when you change 'matchUrl' manually for any reason, you should use setMatchUrl('value') function to update the value
Here is the sample code. Hope this will help you 😇
import React, {useState, useEffect} from 'react'
function TableList({ match }) {
let matchUrl = match.params.name
useEffect(() => {
// YOUR_CODE_HERE WILL RUN WHEN the match.params.name is change
}, [match.params.name])
}
you can take a look also about documentation about useEffect

Adding an object to useState in react. Error "Too many re-renders"

As soon as I add an object to "setTitle" I get an error. Setting the object in "useState()" works.
import React, { useState } from "react";
export default function App() {
const [title, setTitle] = useState({});
setTitle({
"somthing": "dfgsf"
});
return <p>df</p>;
}
Too many re-renders. React limits the number of renders to prevent an
infinite loop.
Live: https://codesandbox.io/s/custom-hook-playground-bzt6s?file=/src/App.js
When component will mount for the first time, "setTitle" function will be called which will update the state.
When state updates, re-rendering happens, thus "setTitle" function will be called again, triggering an infinite loop
Solution:
Use "useffect" or some other function to update the state
You must use initialState when defining the state. You can then add an onClickin order to change the title state using setState
This is the right way:
import React, { useState } from "react";
export default function App() {
const [title, setTitle] = useState({"somthing": "dfgsf"});
function buttonHandler() {
setTitle({
"somthing": "dfgsf"
});
console.log(title)
}
return <button onClick={buttonHandler}>sdf</button>;
}
This is happening because state updates cause a re-render. Because you're updating the state unconditionally the component keeps re-rendering indefinitely.
This doesn't have anything to do with initial state.
useState takes an initialState as it's argument.
Try something like this?
export default function App() {
const [title, setTitle] = useState("My Initial Title");
return <p onClick={() => setTitle('New')}>{title}</p>;
}

Prevent `useSelector` from re-rendering component?

I have a table with thousands of rows.
import React from "react"
import { useSelector } from "react-redux";
import { useEffect } from "react";
import Source from "../components/TranslationTable/Source";
const SourceContainer = React.memo((props) => {
const suggestionArray = useSelector((state) => state.suggestions.suggestionArray );
const isFetching = useSelector((state) => state.suggestions.isFetching);
let source = props.source;
if (props.shouldHighlight && !isFetching) {
//I will have code here to highlight text at some point
}
return (
<Source source={source}>{console.log("rendered")}</Source>
)
}, (prevProps, nextProps) => {
return true;
});
export default SourceContainer;
The above component is located inside the first column of each row. When the row is selected, props.shouldHighlight becomes true.
The problem that I have is that every single SourceContainer component re-renders every time a row is focused. When a row gets focused, isFetching becomes true in the redux state, and suggestionArray will become equal to an array of suggestions based upon the data in the selected row.
If I remove the if statement and the suggestionArray variable, the component STILL re-renders. Meaning, the only thing required to re-produce this issue is having an unused useSelector variable (ifFetching).
Can anyone explain how I can use useSelector without re-rendering the component when the state value of that selector changes?
Using useSelector, you add a dependency to your Component and it is normal that it re-renders if the content of useSelector changes.
The trick here is to make a less varying content for your useSelector function. Or at least something that changes only if the component needs to re-render
For exemple, from what I understand of what you are trying to achieve, your component should only change if "props.shouldHighlight && !isFetching" changes value.
If that's the case, then what you want to do is this :
const isFetching = useSelector((state) => (!state.suggestions.isFetching && props.shouldHighlight));

why I'm not getting the actual state with redux?

I'm getting the state to populate my form but is not the actual state, apparently it is the initial state.
import React from "react";
import store from "../store";
const Invoice = () => {
console.log(store.getState().login);
return (
<div className="span7">
<h4 className="title">
<span className="text">
<strong>Datos</strong> de facturación
</span>
</h4>
...
However, my redux chrome DevTools is showing the actual state (the right one), why the difference?
Decide on 1 source of truth for your state - if you believe it should live in redux, then select the state from redux and DON'T combine it with a local useState:
import React from "react";
import { useSelector } from "react-redux";
const Invoice = () => {
const userAuth = useSelector(state => state.login);
console.log(userAuth);
}
If you want to log current state (from redux), I think you should use subscription (store.subscribe(...)) and put it into useEffect hook.
Something like this:
useEffect(() => {
const subscription = store.subscribe(() => {
console.log(store.getState().login);
});
return () => { subscription() } // clear out subscription when component unmounts
},[])
You are seeing difference between redux dev tools and your conslole.log because with store.getState() you are getting state at it was when your component was mounted. With subscribe you are getting notified any time some action get dispatched an state get changed.

How to fetch data by existing redux action with hooks?

I'm trying to understand React Hooks. What I want is to fetch data inside functional component by call redux action with useEffect hooks.
I know that I can pass props to state like
const [todoList] = useState(props.todoList)
But what is the best practice to fetch data by existing redux actions?
In React class component i call this method to fetch data in componentDidMount() and everythink works.
import React, { useEffect } from 'react'
import { connect } from 'react-redux'
import { ITodo } from './types'
import { getTodos } from '../actions/todoActions'
interface IProps {
todoList: Array<ITodo>
getTodos: typeof getTodos
}
const Todos = (props: IProps) => {
useEffect(() => {
props.getTodos()
}, [props.todoList])
return (
<div>
{props.todoList.map((_) => (<div key={_.Id}>{_.Name}</div>))}
</div>
)
}
const mapStateToProps = (state) => ({
todoList: state.todo.todoList
})
const mapDispatchToProps = {
getTodos
}
export default connect(mapStateToProps, mapDispatchToProps)(ProdRoute)
I expected to get list of todos with props and props.getTodos() should call once like in componentDidMount() method. But actualy I get data and getTodos() are called over and over again but should be called once on component mount
Take care that if you pass [props.todoList] to the useEffect you are erroneously forcing a constant refresh because:
useEffect does an instance comparison (===) to know if props.todoList is changed
after the the very first render the props.getTodos() dispatcher is called
when the props.todoList will be updated the component is re-rendered
the useEffect call will receive [props.todoList] as a value to check if it needs to re-run or not
props.todoList is changed (it was empty and now it's valorized) and props.getTodos() is so re-called
redux updates the todoList with the same values but mutating the array reference
the component is re-rendered and the useEffect will check if the [props.todoList] param is been updated... but IT IS BEEN UPDATED because the previous props.todoList is different from the actual props.todoList, even if the content is the same
So, if you need to call the props.getTodos() just once can
use [props.todoList.length] instead of [props.todoList] as the second parameter for the useEffect call
use an empty array [] as the second parameter for the useEffect call (see the docs)

Resources