How to use React Redux store in component that modifies and renders the state - reactjs

In Text component, I want to get text_data from DB once and save it to the Redux store.
export default function Text() {
const [document, setDocument] = useState([]);
setDocument(useSelector(currentState))
useEffect(() => {
axios.get(`/api/texts/${docId}`).then((response) => {
dispatch(currentState(response.data));
})
}, [])
return (
<div className="Text">
{document.text && document.text.map((text, index) => (
<div onClick={() => {dispatch(changeColor(index))}}>
{text.word}
</div>
))}
</div>
)
}
Then I'd like to get the text_data from Redux store probably in the same component
setDocument(useSelector(currentState))
But it causes infinite rerender.
Moreover, I'd like to modify the text_data with clicks so that Text component will show text in different colors after click. For that, I'd like to modify Redux state and rerender Text component.
text_data has a structure {word: color, second_word: color, ...}
How to use Redux for that? Is it possible, is my thinking correct that the redux state should be the only one thing that should change?
EDIT: Code snippet added. I am working on this so my code snippet doesn't work.

I think you are not understand react-redux hooks correctly. These two lines does not make sense. I don't know what your currentState variable should be in your snippet. But the usage is definitely wrong.
setDocument(useSelector(currentState))
dispatch(currentState(response.data));
I don't know what your redux store looks like. In next snippets I will assume that it is something like this.
// redux store structure
{
texts: {document: {}, coloredWords: {}}
}
// fetched document will have structure something like this (array of words)
{text: []}
Your (texts) reducer should modified the redux store like this (written just schematically)
// ...
// storing fetched document
case 'SET_DOCUMENT': {
const document = action.payload
return {...state, document: document}
}
// storing color for word at particular index
case 'CHANGE_COLOR': {
const {index, color} = action.payload
return {...state, coloredWords: {...state.coloredWords, [index]: color}}
}
// ...
import { useDispatch, useSelector } from 'react-redux'
import { setDocument, changeColor } from 'path_to_file_with_action_creators'
export default function Text() {
const dispatch = useDispatch()
// get fetched document from your redux store (it will be an empty object in the first render, while fetch in the useEffect hook is called after the first render)
const document = useSelector(state => state.texts.document))
// get colors for document words (they were saved in redux store by your onClick handler, see bellow)
const coloredWords = useSelector(state => state.texts.coloredWords))
useEffect(() => {
// fetch document
axios.get(`/api/texts/${docId}`).then((response) => {
// store fetched document in your redux store
dispatch(setDocument(response.data));
})
}, [])
return (
<div className="Text">
{document && document.text && document.text.map((text, index) => (
<div
style={{color: coloredWords[index] ? coloredWords[index] : 'black' }}
onClick={() => {
// store color for word at particular index in redux store
dispatch(changeColor({index: index, color: 'red'}))
}}
>
{text.word}
</div>
))}
</div>
)
}

Related

Like Button with Local Storage in ReactJS

I developed a Simple React Application that read an external API and now I'm trying to develop a Like Button from each item. I read a lot about localStorage and persistence, but I don't know where I'm doing wrong. Could someone help me?
1-First, the component where I put item as props. This item bring me the name of each character
<LikeButtonTest items={item.name} />
2-Then, inside component:
import React, { useState, useEffect } from 'react';
import './style.css';
const LikeButtonTest = ({items}) => {
const [isLike, setIsLike] = useState(
JSON.parse(localStorage.getItem('data', items))
);
useEffect(() => {
localStorage.setItem('data', JSON.stringify(items));
}, [isLike]);
const toggleLike = () => {
setIsLike(!isLike);
}
return(
<div>
<button
onClick={toggleLike}
className={"bt-like like-button " + (isLike ? "liked" : "")
}>
</button>
</div>
);
};
export default LikeButtonTest;
My thoughts are:
First, I receive 'items' as props
Then, I create a localStorage called 'data' and set in a variable 'isLike'
So, I make a button where I add a class that checks if is liked or not and I created a toggle that changes the state
The problem is: I need to store the names in an array after click. For now, my app is generating this:
App item view
localStorage with name of character
You're approach is almost there. The ideal case here is to define your like function in the parent component of the like button and pass the function to the button. See the example below.
const ITEMS = ['item1', 'item2']
const WrapperComponent = () => {
const likes = JSON.parse(localStorage.getItem('likes'))
const handleLike = item => {
// you have the item name here, do whatever you want with it.
const existingLikes = likes
localStorage.setItem('likes', JSON.stringify(existingLikes.push(item)))
}
return (<>
{ITEMS.map(item => <ItemComponent item={item} onLike={handleLike} liked={likes.includes(item)} />)}
</>)
}
const ItemComponent = ({ item, onLike, liked }) => {
return (
<button
onClick={() => onLike(item)}
className={liked ? 'liked' : 'not-liked'}
}>
{item}
</button>
)
}
Hope that helps!
note: not tested, but pretty standard stuff

Can not render/display the fetched data from an api using, ReactJs, Axios, Redux

this is where all the things are happening
const Home = () => {
//FETCHING THE INFORAMATION
const dispatch = useDispatch();
useEffect(() => {
dispatch(fetchHeroes());
}, [Home]);
//PULLING THE DATA OR EXTRACTING IT FROM THE STATE
const { heroesInfo, heroName, heroType, attackType, mostPicked } =
useSelector((state) => state.HeroesInfoAll);
console.log(heroesInfo);
return (
<div>
<HeroList>
{heroesInfo.map((heroes) => {
<Heroes />;
})}
</HeroList>
</div>
);
};
I am also learning about Redux. I have use the reducer which has these arrays in which I want to pass the values accordingly for now though I am only passing the value to the "heroesInfo" array
const initState = {
heroesInfo: [],
heroName: [],
heroType: [],
attackType: [],
mostPicked: [],
};
const HInfoReducer = (state = initState, action) => {
switch (action.type) {
case "FETCH_HEROES":
return { ...state, heroesInfo: action.payload.heroesInfo };
default:
return { ...state };
}
};
export default HInfoReducer;
This is the Heroes component Which I want to render out for each data value present in state which you can see in the first code snippet
const Heroes = () => {
return (
<>
<h1>Hero Name</h1>
<div className="hero-image">
<img src="" alt="Hero Image" />
</div>
<h2>Hero Type</h2>
<h2></h2>
</>
);
};
export default Heroes;
I also console logged out some results to confirm the data was present in the state or not. Installed Redux tools to check it out as well here is the result
[this image shows that the data was extracted for sure after the FETCH_HEROES action ran][1]
[Here I console logged out the heroesInfo array which has the data which I want in it however on the left side my screen is completely blank. I expect it to render out my component for each element present inside of the array][2]
[1]: https://i.stack.imgur.com/nRqZr.png
[2]: https://i.stack.imgur.com/CZbHn.png
I hope I don't get banned this time, I really don't know how to ask questions but all I want to know is why is the component not being rendered out even though the data is present in their?
Please check HeroList component, if the component is returning proper data.
I rewrote the code and can get the data now but I have a new problem which I will be asking soon.

component state doesnt change even after replacing data

My image component displays images with a heart over it every time a user submits a search. The heart changes colors if the image is clicked, and should reset to white (default color) when user submits a new search. For some reason, the clicked-color persists even after a search. What am I not understanding about react states? This isn't simply something that changes on the next render. It just stays like that until I change it manually.
const Image = ({image, toggleFav, initialIcon, initialAlt}) => {
const [fav, setFav] = useState(false);
const [heartIcon, setHeartIcon] = useState(initialIcon)
const [heartAlt, setHeartAlt] = useState(initialAlt)
const handleClick = () => {
setFav(fav => !fav);
toggleFav(image.id, fav);
if (heartIcon == "whiteHeartIcon") {
setHeartIcon("redHeartIcon")
}
else {
setHeartIcon("whiteHeartIcon")
}
if (heartAlt == "white heart icon") {
setHeartAlt("red heart icon")
}
else {
setHeartAlt("white heart icon")
}
};
return (
<Grid item xs={4} key={image.id}>
<div className={`${fav ? "fav" : ""}`} onClick={handleClick}>
<div className="imgBox">
<img src={image.url} className="image"/>
<Heart icon={heartIcon} alt={heartAlt} className="heart"/>
</div>
</div>
</Grid>
);
}
This is the handle submit func for the component:
const searchAllImages = async (keyword) => {
const response = await searchImages(keyword);
const imageObjects = response.data.message.map((link, index) => {
let newImage = {
url: link,
id: link,
fav: false
};
return newImage;
});
dispatch({type: 'SET_IMAGES', payload: imageObjects});
};
I render the images through a redux store where it replaces the image state every time a new search is done. The state resides in Store.js where image is initially set to an empty list. The dispatch method comes from Reducer.js where the method is defined.
case "SET_IMAGES":
return {
...state,
images: action.payload
}
Have you tried setting the initial image to a different variable initially, then use a useEffect that checks the image variable for changes, and if it changes assign the value to the variable mentioned above. Anyways more like using useEffect or useCallback for actions.

My component is mutating its props when it shouldn't be

I have a component that grabs an array out of a prop from the parent and then sets it to a state. I then modify this array with the intent on sending a modified version of the prop back up to the parent.
I'm confused because as I modify the state in the app, I console log out the prop object and it's being modified simultaneously despite never being touched by the function.
Here's a simplified version of the code:
import React, { useEffect, useState } from 'react';
const ExampleComponent = ({ propObj }) => {
const [stateArr, setStateArr] = useState([{}]);
useEffect(() => {
setStateArr(propObj.arr);
}, [propObj]);
const handleStateArrChange = (e) => {
const updatedStateArr = [...stateArr];
updatedStateArr[e.target.dataset.index].keyValue = parseInt(e.target.value);
setStateArr(updatedStateArr);
}
console.log(stateArr, propObj.arr);
return (
<ul>
{stateArr.map((stateArrItem, index) => {
return (
<li key={`${stateArrItem._id}~${index}`}>
<label htmlFor={`${stateArrItem.name}~name`}>{stateArrItem.name}</label>
<input
name={`${stateArrItem.name}~name`}
id={`${stateArrItem._id}~input`}
type="number"
value={stateArrItem.keyValue}
data-index={index}
onChange={handleStateArrChange} />
</li>
)
})}
</ul>
);
};
export default ExampleComponent;
As far as I understand, propObj should never change based on this code. Somehow though, it's mirroring the component's stateArr updates. Feel like I've gone crazy.
propObj|stateArr in state is updated correctly and returns new array references, but you have neglected to also copy the elements you are updating. updatedStateArr[e.target.dataset.index].keyValue = parseInt(e.target.value); is a state mutation. Remember, each element is also a reference back to the original elements.
Use a functional state update and map the current state to the next state. When the index matches, also copy the element into a new object and update the property desired.
const handleStateArrChange = (e) => {
const { dataset: { index }, value } = e.target;
setStateArr(stateArr => stateArr.map((el, i) => index === i ? {
...el,
keyValue: value,
} : el));
}

React Redux - Single card(item) rerender in array of cards(items)

const mapStateToProps = (state) => ({posts: state.profile.posts});
return (this.props.posts.map((album,i)=>
<div key={i.toString()}>
<div onClick={this.onLikeClick.bind(this, album)}>{album.id}</div>
</div>)
onLikeClick(album) {
const likeDetail = {
user:userid,
post:album.id,
}
this.setState(
prevState => ({
count: !prevState.count
}))
this.props.addLike(likeDetail);
}
How can i update single card modified in multiple cards using react-redux. please help..thanks in advance.
1 - I am getting array list this.props.posts
2 - When i click to like single post how can i re-render single card without re-rendering whole card(array)
How can i update single card modified in multiple cards using react-redux. please help..thanks in advance.
1 - I am getting array list this.props.posts
2 - When i click to like single post how can i re-render single card without re-rendering whole card(array)
Based on code you provided
You need to make a separate Page Component like below
const album = {this.props};
return (
<div>
<div onClick={this.onLikeClick.bind(this, album)}>{album.id}</div>
</div>
)
onLikeClick(album) {
const likeDetail = {
user:userid,
post:album.id,
}
this.setState(
prevState => ({
count: !prevState.count
}))
this.props.addLike(likeDetail);
}
And you can call that from parent component
const mapStateToProps = (state) => ({posts: state.profile.posts});
return (this.props.posts.map((album,i)=>
<Post {...this.props} album={album} key={i}/>)
so whenever your child component State will be changed your parent component will not get affected and whole list won't re-rendered
Hope it helps!

Resources