Redux state is updated but component state not updated - reactjs

how is it that my Redux state is updated, and can be log out in the pokelist.js file,
but my state variable is not set properly, is cardList is still an empty array, how do I
set the state properly? I log out the collection in the pokelist.js file, which logs out
an empty array first then an array containing the elements.
// reducer.js file
import { GET_LIMIT_NAMES } from '../actions/PokedexActions';
const initialState = {
collection: []
};
export default (state = initialState, action) => {
switch (action.type) {
case GET_LIMIT_NAMES:
return {
collection: action.data
};
default:
return state;
}
};
//===================================================================================================
// action.js file
import Pokemon from '../Pokemon';
export const GET_LIMIT_NAMES = "GET_LIMIT_NAMES";
export const getLimitNames = (limit = 100) => {
// redux-thunk
return async dispatch => {
try {
const allResponse = await fetch(`https://pokeapi.co/api/v2/pokemon/?limit=${limit}`);
const allUrlsData = await allResponse.json();
// console.log(allUrlsData.results);
const collection = [];
Promise.all(allUrlsData.results.map(urlData => {
var pokemon;
fetch(urlData.url).then(resp =>
resp.json()
).then(data => {
// console.log(data);
pokemon = new Pokemon(data);
// pokemon.log();
collection.push(pokemon)
}).catch(err => {
console.log(err);
})
return collection;
}))
// console.log(collection)
dispatch({
type: GET_LIMIT_NAMES,
data: collection
});
} catch (err) {
console.log(err);
}
};
};
//===================================================================================================
// I want to make a list of cards from the Redux state
// pokelist.js
import React, { useState, useEffect } from 'react';
import Card from 'react-bootstrap/Card';
import ListGroup from 'react-bootstrap/ListGroup';
import { useSelector } from 'react-redux';
const PokeList = () => {
const [cardList, setCardList] = useState();
const collection = useSelector(state => state.pokedex.collection);
useEffect(() => {
console.log(collection)
setCardList(collection.map(pokeData =>
<Card key={pokeData.id} style={{ width: '18rem' }}>
<Card.Img variant="top" src={pokeData.sprite + '/100px180'} />
<Card.Body>
<Card.Title>{pokeData.Name}</Card.Title>
<ListGroup className="list-group-flush">
<ListGroup.Item>{'Height: ' + pokeData.height}</ListGroup.Item>
<ListGroup.Item>{'Weight: ' + pokeData.weight}</ListGroup.Item>
</ListGroup>
</Card.Body>
</Card>))
}, [collection])
return (
<div>
{cardList}
</div>
)
}
export default PokeList;
//===================================================================================================
// search.js file where i render the component and call the dispatch function
import React, { useState, useEffect } from 'react';
import { Container, Row, Col, Image, Button } from 'react-bootstrap';
import { useDispatch } from 'react-redux';
import PokeList from './pokedex/PokeList';
import * as pokedexActions from './pokedex/actions/PokedexActions';
const Search = () => {
const dispatch = useDispatch();
useEffect(() => {
dispatch(pokedexActions.getLimitNames(5))
}, [dispatch])
return (
<div>
<Container>
<h2>Search</h2>
<PokeList />
</Container>
</div>
);
}
export default Search;

useState() hook is just a function call. It returns value and function pair. values is just a constant it doesn't have any property binding.
// Think of this line
const [cardList, setCardList] = useState([]);
// As these two lines
const cardList = [];
const setCardList = someFunction;
When you call setCardList your variable is not changed immediately, it doesn't have property binding. It just tells react to return new value on next render.
See my answer React Hooks state always one step behind.
In your case you can simply skip useState hook
const PokeList = () => {
const collection = useSelector(state => state.pokedex.collection);
const cardList = collection.map(
pokeData => (
<Card key={pokeData.id} style={{ width: '18rem' }}>
<Card.Img variant="top" src={pokeData.sprite + '/100px180'} />
<Card.Body>
<Card.Title>{pokeData.Name}</Card.Title>
<ListGroup className="list-group-flush">
<ListGroup.Item>{'Height: ' + pokeData.height}</ListGroup.Item>
<ListGroup.Item>{'Weight: ' + pokeData.weight}</ListGroup.Item>
</ListGroup>
</Card.Body>
</Card>)
);
return (
<div>
{cardList}
</div>
)
}

Related

useEffect fails on page refresh

I am an infant programmer and I am trying to fetch an api and style the results using React. My page works fine on the initial load and subsequent saves on VScode,but when I actually refresh the page from the browser I get the error thats posted on imageenter image description here:
Here is my code: App.js
```import React, { useEffect, useState } from 'react';
import './App.css';
import Students from './components/Students';
import styled from 'styled-components';
function App() {
const [studentInfo, setStudentInfo] = useState({});
const [searchResult, setSearchResult] = useState({});
const [searchTerm, setSearchTerm] = useState('');
useEffect(() => {
getStudents();
}, []);
useEffect(() => {
getStudents();
console.log('useEffect');
}, [searchTerm]);
const getStudents = async () => {
const url = 'https://api.hatchways.io/assessment/students';
console.log(url);
fetch(url)
.then((res) => res.json())
.then((data) => {
console.log(data);
searchTerm != ''
? setStudentInfo(filterStudents(data.students))
: setStudentInfo(data.students);
});
};
const filterStudents = (studentsArray) => {
return studentsArray.filter((info) => {
return (
info.firstName.toLowerCase().includes(searchTerm) ||
info.lastName.toLowerCase().includes(searchTerm)
);
});
};
console.log(searchTerm);
return (
<div className="App">
<Students
studentInfo={studentInfo}
setSearchTerm={setSearchTerm}
searchTerm={searchTerm}
/>
</div>
);
}
export default App;```
here is my component Students.js:
```import React, { useState } from 'react';
import styled from 'styled-components';
import GradeDetails from './GradeDetails';
const Students = ({ studentInfo, searchTerm, setSearchTerm }) => {
console.log(typeof studentInfo);
console.log(studentInfo[0]);
const [isCollapsed, setIsCollapsed] = useState(false);
const handleDetails = () => {
setIsCollapsed(!isCollapsed);
};
const average = (arr) => {
let sum = 0;
arr.map((num) => {
sum = sum + parseInt(num);
});
return sum / arr.length.toFixed(3);
};
console.log(isCollapsed);
return (
<Container>
<Input
type="text"
value={searchTerm}
placeholder="Search by name"
onChange={(e) => setSearchTerm(e.target.value.toLowerCase())}
/>
{studentInfo?.map((student) => (
<Wrapper key={student.id}>
<ImageContainer>
<Image src={student.pic}></Image>
</ImageContainer>
<ContentContainer>
<Name>
{student.firstName} {student.lastName}{' '}
</Name>
<Email>Email: {student.email}</Email>
<Company>Company: {student.company}</Company>
<Skills>Skill: {student.skill}</Skills>
<Average>Average:{average(student.grades)}%</Average>
</ContentContainer>
<ButtonContainer>
<Button onClick={handleDetails}>+</Button>
</ButtonContainer>
{isCollapsed && <GradeDetails studentInfo={studentInfo} />}
</Wrapper>
))}
</Container>
);
};```
Every time I have the error, I comment out the codes in Students.js starting from studentInfo.map until the and save and then uncomment it and save and everything works fine again.
I am hoping someone can help me make this work every time so that I don't have to sit at the edge of my seat all the time. Thank you and I apologize for the long question.
You are using an empty object as the initial state for studentInfo (the value passed to useState hook will be used as the default value - docs):
const [studentInfo, setStudentInfo] = useState({});
.map is only supported on Arrays. So this is failing when the component is rendering before the useEffect has completed and updated the value of studentInfo from an object, to an array. Try swapping your initial state to be an array instead:
const [studentInfo, setStudentInfo] = useState([]);

value of state increment twice in reducer.js file

Order is an array of Objects and there is a key 'count' inside each variable. When ADD_ITEM_IN_ORDER case is executed than the count of particular object should be increment by 1. But, in this case, when the particular item is already present in the array than the value of count of that item incrementing by 2, which should not happen(it should increment by 1).
reducer.js
export const initialState = {
Order: [],
};
const reducer = (state, action) => {
console.log(action);
switch (action.type) {
case "ADD_ITEM_IN_ORDER":
const tempOrder1 = [...state.Order];
const index1 = state.Order.findIndex((item) => item.id === action.item.id);
if (index1 >= 0) {
console.log("before",tempOrder1[index1].Count);
tempOrder1[index1].Count += 1;
return { ...state, Order: tempOrder1 };
}
else {
console.log("New item added");
return {
...state,
Order: [...state.Order, action.item]
};
}
default:
return state;
}
};
export default reducer;
action file
import React from 'react';
import VegIcon from '../Images/VegIcon.png';
import NonVegIcon from '../Images/NonVegIcon.png';
import { useStateValue } from '../StateProvider';
import { db } from '../firebase';
const CartMenu = (props) => {
const [{ Order }, dispatch] = useStateValue();
const add = () => {
dispatch({
type: "ADD_ITEM_IN_ORDER",
item: {
id: props.id,
menuCollectionName:props.menuCollectionName,
VegNonV: props.VegNonV,
Menu: props.Menu,
Price: props.Price,
Count: 1,
// RestuarantId: props.restuarantId
}
});
console.log(Order);
};
const remove = () => {
dispatch({
type: "REMOVE_ITEM_FROM_ORDER",
item: {
id: props.id, // id of a dish
restuarantId: props.restuarantId
}
});
};
return (
<div className='Menu_Display'>
<div className='Menu_Display_subsection1'>
{props.VegNonV === "Veg" ?
<img className="Menu_Veg_NonVeg" src={VegIcon} />
:
<img className="Menu_Veg_NonVeg" src={NonVegIcon} />
}
<div className='Menu_Name'>{props.Menu}</div>
</div>
<div className="Menu_Add_Button" >
<div className="Menu_minus" onClick={remove}>−</div>
<span>{props.Count}</span>
<div className="Menu_plus" onClick={add}>+</div>
</div>
<div className='Menu_Price'>
<span></span>
<span>₹{Math.round(props.Price * props.Count * 100) / 100}</span>
</div>
</div>
);
};
export default CartMenu;
StateProvider.js
//setup data layer
// we need this to track the basket data
import React,{createContext,useContext,useReducer} from 'react';
export const StateContext = createContext();
//Build Provider
export const StateProvider = ({reducer,initialState,children}) =>
(
<StateContext.Provider value = {useReducer(reducer,initialState)}>
{children}
</StateContext.Provider>
);
export const useStateValue = () => useContext(StateContext);
The context API broadcasts updates when it notices a change in the value. Since you are invoking useReducer within the value props, that returns an array (state value and dispatch function), it is this, that is likely causing double dispatch. I recommend you re-write your Provider logic.
import React,{createContext,useContext,useReducer} from 'react';
export const StateContext = createContext({ //Make sure to export this
Order:[], //Will be consuimg the state value from here
addItem:(arg)=>{} //This function will be hooked to a dispatch function below
});
//Build Provider
export const StateProvider = ({reducer,initialState,children}) =>
{
const [state,dispatchFn] = useReducer(reducer,initialState)
const addItemHandler = (item) => {
dispatchFn(item)
}
return(<StateContext.Provider value = {{Order:state.Order,addItem:addItemHandler}}>
{children}
</StateContext.Provider>)
};
You can then wrap your root component with the context provider component StateProvider so that all the components can access the state values.
In your index.js wrap the component like this:
import {StateProvider} from './path/to/provider'
ReactDOM.render(<StateProvider><App/></StateProvider>,doucment.getElementById("root"))
You can then use the context state and dispatch function from useContext hook by, passing the context variable.
In your action file:
import {useContext},React from 'react'; //Import use context
import {StateContext} from './path/to/state/context'
import VegIcon from '../Images/VegIcon.png';
import NonVegIcon from '../Images/NonVegIcon.png';
import { db } from '../firebase';
const CartMenu = (props) => {
const order_ctx = useContext(StateContext);
const add = () => {
order_ctx.addItem({
type: "ADD_ITEM_IN_ORDER",
item: {
id: props.id,
menuCollectionName:props.menuCollectionName,
VegNonV: props.VegNonV,
Menu: props.Menu,
Price: props.Price,
Count: 1,
// RestuarantId: props.restuarantId
}
});
console.log(Order);
};
const remove = () => {
order_ctx.rmItem({ //Note: rmItem method is not added to createContext, but this is just to demonstrate how state update methods can be invoked by using the context instance.
type: "REMOVE_ITEM_FROM_ORDER",
item: {
id: props.id, // id of a dish
restuarantId: props.restuarantId
}
});
};
return (
<div className='Menu_Display'>
<div className='Menu_Display_subsection1'>
{props.VegNonV === "Veg" ?
<img className="Menu_Veg_NonVeg" src={VegIcon} />
:
<img className="Menu_Veg_NonVeg" src={NonVegIcon} />
}
<div className='Menu_Name'>{props.Menu}</div>
</div>
<div className="Menu_Add_Button" >
<div className="Menu_minus" onClick={remove}>−</div>
<span>{props.Count}</span>
<div className="Menu_plus" onClick={add}>+</div>
</div>
<div className='Menu_Price'>
<span></span>
<span>₹{Math.round(props.Price * props.Count * 100) / 100}</span>
</div>
</div>
);
};
export default CartMenu;
And also since, your state contains only an array of orders, you can just return the updated array without having to override the previous state.
In your reducer:
case "ADD_ITEM_IN_ORDER":
const tempOrder1 = [...state.Order];
const index1 = state.Order.findIndex((item) => item.id === action.item.id);
if (index1 >= 0) {
console.log("before",tempOrder1[index1].Count);
tempOrder1[index1].Count += 1;
return { Order: [...tempOrder1] }; //Return updated array only
}
else {
console.log("New item added");
return {
Order: [...state.Order, action.item] //Return updated array only
};
}
default:
return state;
}

Correct item in array not always deleted. React context

I have a delete function in my Context that I'm passing ids to so I can delete components from the array however it doesn't always work correctly. If I add 3 note components to the board for example, it will always delete the last item on the board. If I add a to do list in between 2 notes, they'll delete correctly. There are 2 console logs and the deleted one shows the correct deleted item, and components shows the 2 that are left. Again, if there are 3 notes, it deletes the last item everytime, but if I do one note, one to do, then one note again, the correct item on the board is deleted.
import React, { createContext, useReducer, useState } from "react";
import ComponentReducer from "./ComponentReducer";
const NewComponentState: NewComponentsState = {
components: [],
addComponent: () => {},
deleteComponent: () => {},
};
export const NewComponentContext =
React.createContext<NewComponentsState>(NewComponentState);
export const NewComponentProvider: React.FC = ({ children }) => {
const [components, setComponents] = useState(NewComponentState.components);
const deleteComponent = (id: any) => {
for (let i = 0; i < components.length; i++) {
if(components[i].id === id) {
let deleted = components.splice(i, 1)
console.log(deleted)
setComponents([...components])
console.log(components)
}
}
}
const addComponent = (newComponent: any) => {
setComponents([...components, newComponent])
}
return (
<NewComponentContext.Provider
value={{ components, deleteComponent, addComponent }}
>
{children}
</NewComponentContext.Provider>
);
};
Board component
import React, { useContext } from "react";
import { NewComponentContext } from "../Context/NewComponentContext";
import NewComponentMenu from "./NewComponents/NewComponentMenu";
import Note from "./NewComponents/Note/Note";
import Photo from "./NewComponents/Photo/Photo";
import TodoList from "./NewComponents/Todo/TodoList";
const newComponents: any = {
1: TodoList,
2: Photo,
3: Note
}
const Board = () => {
const { components } = useContext(NewComponentContext);
const componentList = components.map((component, i) => {
const id: number = component.componentType
const NewComponent = newComponents[id]
for (const property in newComponents) {
const value = parseInt(property)
if (value == id) {
return (
<div key={i}>
<NewComponent id={component.id}/>
</div>
)
}
}
});
return (
<div className="flex space-x-10 mt-8">
<NewComponentMenu />
<div className="grid grid-cols-6 gap-8">{componentList}</div>
</div>
);
};
export default Board;

How to create infinite scroll in React and Redux?

import React, {useState, useEffect} from 'react';
import {connect} from 'react-redux';
import {
fetchRecipes
} from '../../store/actions';
import './BeerRecipes.css';
const BeerRecipes = ({recipesData, fetchRecipes}) => {
const [page, setPage] = useState(1);
const [recipes, setRecipes] = useState([]);
const [loading, setLoading] = useState(true);
useEffect(() => {
fetchRecipes();
}, [])
return (
<div className='beer_recipes_block'>
<div className='title_wrapper'>
<h2 className='title'>Beer recipes</h2>
</div>
<div className='beer_recipes'>
<ul className='beer_recipes_items'>
{
recipesData && recipesData.recipes && recipesData.recipes.map(recipe =>
<li className='beer_recipes_item' id={recipe.id}>{recipe.name}</li>
)
}
</ul>
</div>
</div>
);
};
const mapStateToProps = state => {
return {
recipesData: state.recipes
}
}
const mapDispatchToProps = dispatch => {
return {
fetchRecipes: () => dispatch(fetchRecipes())
}
}
export default connect(mapStateToProps, mapDispatchToProps)(BeerRecipes);
this is my component where I would like to create infinite scroll and below is my redux-action with axios:
import axios from "axios";
import * as actionTypes from "./actionTypes";
export const fetchRecipesRequest = () => {
return {
type: actionTypes.FETCH_RECIPES_REQUEST
}
}
export const fetchRecipesSuccess = recipes => {
return {
type: actionTypes.FETCH_RECIPES_SUCCESS,
payload: recipes
}
}
export const fetchRecipesFailure = error => {
return {
type: actionTypes.FETCH_RECIPES_FAILURE,
payload: error
}
}
export const fetchRecipes = (page) => {
return (dispatch) => {
dispatch(fetchRecipesRequest)
axios
.get('https://api.punkapi.com/v2/beers?page=1')
.then(response => {
const recipes = response.data;
dispatch(fetchRecipesSuccess(recipes));
})
.catch(error => {
const errorMsg = error.message;
dispatch(fetchRecipesFailure(errorMsg));
})
}
}
I want to create a scroll. I need, firstly, to display first 10 elements and then to add 5 elements with every loading. I have 25 elements altogether and when the list is done it should start from the first five again.
Assuming you already have everything ready to load your next page. You can probably simplify the entire process by using a package like react-in-viewport so you don't have to deal with all the scroll listeners.
then you use it like this way.
import handleViewport from 'react-in-viewport';
const Block = (props: { inViewport: boolean }) => {
const { inViewport, forwardedRef } = props;
const color = inViewport ? '#217ac0' : '#ff9800';
const text = inViewport ? 'In viewport' : 'Not in viewport';
return (
<div className="viewport-block" ref={forwardedRef}>
<h3>{ text }</h3>
<div style={{ width: '400px', height: '300px', background: color }} />
</div>
);
};
const ViewportBlock = handleViewport(Block, /** options: {}, config: {} **/);
const Component = (props) => (
<div>
<div style={{ height: '100vh' }}>
<h2>Scroll down to make component in viewport</h2>
</div>
<ViewportBlock
onEnterViewport={() => console.log('This is the bottom of the content, lets dispatch to load more post ')}
onLeaveViewport={() => console.log('We can choose not to use this.')} />
</div>
))
What happen here is, it creates a 'div' which is outside the viewport, once it comes into the view port ( it means user already scrolled to the bottom ), you can call a function to load more post.
To Note: Remember to add some kind of throttle to your fetch function.

Component not rerendering after axios Get (React)

I'm trying to render List of items of my DB using React.Context.
All my request work pretty well.
when i console log my states first I get an empty array and then array with the data that I need but my component is not updating. I have to go to another page an then go back to this page to get the data. I don't really understand why... here are my files..
ArticlesContext.js :
import React, { useState, createContext, useEffect } from 'react';
import axios from 'axios'
export const ArticlesContext = createContext();
export function ArticlesProvider(props) {
const [articles, setArticles] = useState([]);
const [user, setUser] =useState(0)
async function getArticles () {
await axios.get(`/api/publicItem`)
.then(res => {
setArticles(res.data);
})
}
useEffect( () => {
getArticles()
}, [user])
console.log(articles);
return (
<ArticlesContext.Provider value={[articles, setArticles]}>
{props.children}
</ArticlesContext.Provider>
);
}
Inventaire.js :
import React, { useContext, useEffect, useState } from 'react';
import './Inventaire.css';
import { ArticlesContext } from '../../../context/ArticlesContext';
import DeleteAlert from './Delete/Delete';
import Modify from './Modify/Modify';
import Filter from './Filter/Filter';
import axios from 'axios'
import Crud from '../../Elements/Articles/Crud/Crud';
import List from './List/List';
export default function Inventaire() {
const [articles, setArticles] = useContext(ArticlesContext);
const [filter, setFilter] = useState(articles)
console.log(articles);
//list for Inputs
const cat = articles.map(a => a.category.toLowerCase());
const categoryFilter = ([...new Set(cat)]);
const gender = articles.map(a => a.gender.toLowerCase());
const genderFilter = ([...new Set(gender)]);
//Event Listenner
//Uncheck All checkboxes
function UncheckAll() {
const el = document.querySelectorAll("input.checkboxFilter");
console.log(el);
for (var i = 0; i < el.length; i++) {
var check = el[i];
if (!check.disabled) {
check.checked = false;
}
}
}
//SearchBar
const searchChange = (e) => {
e.preventDefault();
const stuff = articles.filter((i) => {
return i.name.toLowerCase().match(e.target.value.toLowerCase())
})
setFilter(stuff)
UncheckAll(true)
}
const Types = (e) => {
if (e.target.checked === true) {
const stuff = filter.filter((i) => {
return i.category.toLowerCase().match(e.target.value.toLowerCase())
})
setFilter(stuff)
console.log(articles);
} else if (e.target.checked === false) {
setFilter(articles)
}
}
const Gender = (e) => {
if (e.target.checked === true) {
const stuff = filter.filter((i) => {
console.log(i.category, e.target.value);
return i.gender.toLowerCase().match(e.target.value.toLowerCase())
})
setFilter(stuff)
} else if (e.target.checked === false) {
setFilter(articles)
}
}
return (
<div className="inventaireContainer">
<input type="text" placeholder="Recherche un Article" onChange={searchChange} />
<div className="inventaireMenu">
<Crud />
<Filter
filter={Types}
categorys={categoryFilter}
genre={genderFilter}
target={Gender}
/>
</div>
<List filter={filter} articles={articles}/>
</div>
)
}
List.js :
import React from 'react';
import DeleteAlert from '../Delete/Delete';
import Modify from '../Modify/Modify';
export default function List({ filter, articles }) {
return (
<div>
{filter.map((details, i) => {
return (
<div className="inventaireBlock" >
<div className="inventaireGrid">
<div className="inventaireItemImg">
<img src={details.image} alt="ItemImg" />
</div>
<h2>{details.name}</h2>
<h3>{details.category}</h3>
<h3>{details.gender}</h3>
<div>
<p>S :{details.sizes[0].s}</p>
<p>M :{details.sizes[0].m}</p>
<p>L :{details.sizes[0].l}</p>
<p>XL :{details.sizes[0].xl}</p>
</div>
<h2> Prix: {details.price}</h2>
<div className="modify">
<Modify details={details._id} />
</div>
<div className="delete" >
<DeleteAlert details={details._id} articles={articles} />
</div>
</div>
</div>
)
})}
</div>
)
}
Thanks for your time

Resources