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

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!

Related

React - child not reverting to parent state when child state not saved

I fear that I've made a complete mess of my React structure.
I have a parent component/view that contains an object of attributes that are the key element of the content on the page. The ideal is to have a component that allows the user to update these attributes, and when they do so the content on the page updates. To avoid having the content update on every single attribute update/click, I want to implement a 'revert'/'save' button on the attribute update screen.
The problem I'm having is that I'm passing the state update function from the main parent to the child components, and the save is updating the parent and then the child switches out of 'edit mode' and displays the correct value, but when I click revert it doesn't update the parent (good) but it still maintains the local state of the child rather than rerendering to the 'true' parent state so the child implies it has been updated when it actually hasn't.
I'm a hobbyist React developer so I'm hoping that there is just something wrong with my setup that is easily rectifiable.
export const MainViewParent = () => {
const [playerAttributes, updatePlayerAttributes] = useState(basePlayerAttributes);
const revertPlayerAttributes = () => {
updatePlayerAttributes({...playerAttributes});
};
// Fill on first mount
useEffect(() => {
// Perform logic that uses attributes here to initialise page
}, []);
const adjustPlayerAttributes = (player, newAttributes) => {
// Update the particular attributes
playerAttributes[player] = newAttributes;
updatePlayerAttributes({...playerAttributes});
};
return (
<PlayerAttributes
playerAttributes={playerAttributes}
playerNames={["Player 1"]}
playerKeys={["player1"]}
updatePlayerAttributes={adjustPlayerAttributes} // Update the 'base' attributes, requires player key - uses function that then calls useState update function
revertPlayerAttributes={revertPlayerAttributes}
/>
);
}
// Child component one - renders another child component for each player
export const PlayerAttributes = ({
updatePlayerAttributes
}) => {
return (
<AnotherChildComponent />
);
};
// Child component 2
const AnotherChildComponent = ({ ...someThings, updatePlayerAttributes }) => {
const [editMode, updateEditMode] = useState(false); // Toggle for showing update graph elements
const [local, updateLocal] = useState({...playerAttributes}); // Store version of parent data to store changes prior to revert/save logic
// Just update the local state as the 'save' button hasn't been pressed
const updateAttributes = ( attributeGroup, attributeKey, attributeValue) => {
local[attributeGroup][attributeKey] = attributeValue;
updateLocal({...local});
};
const revertAttributes = () => {
// Need to update local back to the original state of playerAttributes
updateLocal(playerAttributes);
// revertPlayerAttributes();
updateEditMode(false); // Toggle edit mode back
};
const saveDetails = () => {
updatePlayerAttributes(playerKey, local);
updateEditMode(false);
};
return (
<FormElement
editMode={editMode}
// Should pass in a current value
playerAttributes={playerAttributes}
attributeOptions={[0.2, 0.3, 0.4, 0.5, 0.6]}
updateFunction={updateAttributes} // When you click update the local variable
/> // This handles conditional display of data depending on editMode above
);
}
// Child component 3...
const FormElement = ({ editMode, playerAttributes, attributeOptions, updateFunction, attributeGroup, attributeKey }) => {
if (editMode) {
return (
<div>
{attributeOptions.map(option =>
<div
key={option}
onClick={() => updateFunction(attributeGroup, attributeKey, option)}
>
{option)
</div>
)}
</div>
);
}
return (
<div>{//attribute of interest}</div>
);
};
I'm just a bit confused about as to why the revert button doesn't work here. My child displays and updates the information that is held in the parent but should be fully controlled through the passing of the function and variable defined in the useState call at the top of the component tree shouldn't it?
I've tried to cut down my awful code into something that can hopefully be debugged! I can't help but feel it is unnecessary complexity that is causing some issues here, or lifecycle things that I don't fully grasp. Any advice or solutions would be greatly appreciated!

How should I update individual items' className onClick in a list in a React functional component?

I'm new to React and I'm stuck trying to get this onClick function to work properly.
I have a component "Row" that contains a dynamic list of divs that it gets from a function and returns them:
export function Row({parentState, setParentState}) {
let divList = getDivList(parentState, setParentState);
return (
<div>
{divList}
</div>
)
}
Say parentState could just be:
[["Name", "info"],
["Name2", "info2"]]
The function returns a list of divs, each with their own className determined based on data in the parentState. Each one needs to be able to update its own info in parentState with an onClick function, which must in turn update the className so that the appearance of the div can change. My code so far seems to update the parentState properly (React Devtools shows the changes, at least when I navigate away from the component and then navigate back, for some reason), but won't update the className until a later event. Right now it looks like this:
export function getDivList(parentState, setParentState) {
//parentState is an array of two-element arrays
const divList = parentState.map((ele, i) => {
let divClass = "class" + ele[1];
return (
<div
key={ele, i}
className={divClass}
onClick={() => {
let newParentState =
JSON.parse(JSON.stringify(parentState);
newParentState[i][1] = "newInfo";
setParentState(newParentState);}}>
{ele[0]}
</div>
)
}
return divList;
}
I have tried to use useEffect, probably wrong, but no luck. How should I do this?
Since your Row component has parentState as a prop, I assume it is a direct child of this parent component that contains parentState. You are trying to access getDivList in Row component without passing it as a prop, it won't work if you write your code this way.
You could use the children prop provided by React that allow you to write a component with an opening and closing tag: <Component>...</Component>. Everything inside will be in the children. For your code it would looks like this :
import React from 'react';
import { render } from 'react-dom';
import './style.css';
const App = () => {
const [parentState, setParentState] = React.useState([
['I am a div', 'bg-red'],
['I am another div', 'bg-red'],
]);
React.useEffect(
() => console.log('render on ParentState changes'),
[parentState]
);
const getDivList = () => {
return parentState.map((ele, i) => {
return (
<div
key={(ele, i)}
className={ele[1]}
onClick={() => {
// Copy of your state with the spread operator (...)
let newParentState = [...parentState];
// We don't know the new value here, I just invented it for the example
newParentState[i][1] = [newParentState[i][1], 'bg-blue'];
setParentState(newParentState);
}}
>
{ele[0]}
</div>
);
});
};
return <Row>{getDivList()}</Row>;
};
const Row = ({ children }) => {
return <>{children}</>;
};
render(<App />, document.getElementById('root'));
And a bit of css for the example :
.bg-red {
background-color: darkred;
color: white;
}
.bg-blue {
background-color:aliceblue;
}
Here is a repro on StackBlitz so you can play with it.
I assumed the shape of the parentState, yu will have to adapt by your needs but it should be something like that.
Now, if your data needs to be shared across multiple components, I highly recommand using a context. Here is my answer to another post where you'll find a simple example on how to implement a context Api.

I want only one component state to be true between multiple components

I am calling components as folloews
{userAddresses.map((useraddress, index) => {
return (
<div key={index}>
<Address useraddress={useraddress} />
</div>
);
})}
Their state:
const [showEditAddress, setShowEditAddress] = useState(false);
and this is how I am handling their states
const switchEditAddress = () => {
if (showEditAddress === false) {
setShowEditAddress(true);
} else {
setShowEditAddress(false);
}
};
Well, it's better if you want to toggle between true and false to use the state inside useEffect hook in react.
useEffect will render the component every time and will get into your condition to set the state true or false.
In your case, you can try the following:
useEffect(() => { if (showEditAddress === false) {
setShowEditAddress(true);
} else {
setShowEditAddress(false);
} }, [showEditAddress])
By using useEffect you will be able to reset the boolean as your condition.
Also find the link below to react more about useEffect.
https://reactjs.org/docs/hooks-effect.html
It would be best in my opinion to keep your point of truth in the parent component and you need to figure out what the point of truth should be. If you only want one component to be editing at a time then I would just identify the address you want to edit in the parent component and go from there. It would be best if you gave each address a unique id but you can use the index as well. You could do something like the following:
UserAddress Component
const UserAddress = ({index, editIndex, setEditIndex, userAddress}) => {
return(
<div>
{userAddress}
<button onClick={() => setEditIndex(index)}>Edit</button>
{editIndex === index && <div style={{color: 'green'}}>Your editing {userAddress}</div>}
</div>
)
}
Parent Component
const UserAddresses = () => {
const addresses = ['120 n 10th st', '650 s 41 st', '4456 Birch ave']
const [editIndex, setEditIndex] = useState(null)
return userAddresses.map((userAddress, index) => <UserAddress key={index} index={index} editIndex={editIndex} setEditIndex={setEditIndex} userAddress={userAddress}/>;
}
Since you didn't post the actual components I can only give you example components but this should give you an idea of how to achieve what you want.

react, map component, unexpected result

I am building Weather App, my idea is to save city name in database/localhost, place cities in useState(right now it's hard coded), iterate using map in first child component and display in second child component.
The problem is that 2nd child component outputs only one element (event though console.log prints both)
BTW when I change code in my editor and save, then another 'li' element appears
main component
const App = () => {
const [cities, setCities] = useState(['London', 'Berlin']);
return (
<div>
<DisplayWeather displayWeather={cities}/>
</div>
)
}
export default App
first child component
const DisplayWeather = ({displayWeather}) => {
const [fetchData, setFetchData] = useState([]);
const apiKey = '4c97ef52cb86a6fa1cff027ac4a37671';
useEffect(() => {
displayWeather.map(async city=>{
const res =await fetch(`http://api.openweathermap.org/data/2.5/weather?q=${city}&units=metric&appid=${apiKey}`)
const data = await res.json();
setFetchData([...fetchData , data]);
})
}, [])
return (
<>
{fetchData.map(data=>(
<ul>
<Weather
data={data}/>
</ul>
))}
</>
)
}
export default DisplayWeather
second child component
const Weather = ({data}) => {
console.log(data) // it prints correctly both data
return (
<li>
{data.name} //display only one data
</li>
)
}
export default Weather
The Problem
The setFetchData hooks setter method is asynchronous by default, it doesn't give you the updated value of the state immediately after it is set.
When the weather result for the second city is returned and set to state, the current value fetchData at the time is still an empty array, so you're essentially spreading an empty array with the second weather result
Solution
Pass a callback to your setFetchData and get the current previous value of the state and then continue with your spread accordingly.
Like this 👇🏽
setFetchData((previousData) => [...previousData, data]);

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

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

Resources