How to change local state without changing global state - javascript

I am making a blog and I want to show a list of articles under each post so the user do not need to go back to the front page. But I do not want the article they are currently reading to be in the list.
I am using filter method and it's working, but only for a split second. As far as i understand it is happening because of the useContext. I do not want to change global state, only local.
const ArticleDetails = (props) => {
const {data} = useContext(ArticleContext); //data with all fetched articles from db
const article = props.location.state.article; //data for individual article
return (
<div>
//showing here data for individual post that i passed in props
{data.filter(item => {return item !== article})
.map(item => {return <ArticleList articleTitle={item.title} key={item.id} />})}
</div>
)
}

It can be that the useContext hooh has been completed some modification to the data you use in return after the render.
By filter or map you actually are not changing any global state or local state at all.
After that above mentioned update, your data items might be changing. Also your filter function needs to filter with a never changing id or something like that so your filter results stays consistent across renders or state updates.
data.filter(item => {return item.id !== article.id})
Also using equality for the objects like that will not work for your filter criteria. You use not equal criteria and yes after a render your item will not be equal to article in a subsequent render even they are same reference in one render. That kind of equality is checked by Object.is that would be a specific need. But I think you just need an id check.

Related

Conditional Rendering of Arrays in React: For vs Map

I'm new to React and building a calendar application. While playing around with state to try understand it better, I noticed that my 'remove booking' function required a state update for it to work, while my 'add booking' function worked perfectly without state.
Remove bookings: requires state to work
const [timeslots, setTimeslots] = useState(slots);
const removeBookings = (bookingid) => {
let newSlots = [...timeslots];
delete newSlots[bookingid].bookedWith;
setTimeslots(newSlots);
}
Add bookings: does not require state to work
const addBookings = (slotid, tutorName) => {
timeslots[slotid].bookedWith = tutorName;
}
I think that this is because of how my timeslot components are rendered. Each slot is rendered from an item of an array through .map(), as most tutorials online suggest is the best way to render components from an array.
timeslots.map(slot => {
if (!slot.bookedWith) {
return <EmptyTimeslot [...props / logic] />
} else {
return <BookedTimeslot [...props / logic]/>
}
})
So, with each EmptyTimeslot, the data for a BookedTimeslot is available as well. That's why state is not required for my add bookings function (emptyTimeslot -> bookedTimeslot). However, removing a booking (bookedTimeslot -> emptyTimeslot) requires a rerender of the slots, since the code cannot 'flow upwards'.
There are a lot of slots that have to be rendered each time. My question is therefore, instead of mapping each slot (with both and information present in each slot), would it be more efficient to use a for loop to only render the relevant slot, rather than the information for both slots? This I assume would require state to be used for both the add booking and remove booking function. Like this:
for (let i=0;i<timeslots.length;i++) {
if (!timeslot[i].bookedWith) {
return <EmptyTimeslot />
} else {
return <BookedTimeslot />
}
}
Hope that makes sense. Thank you for any help.
Your addBooking function is bad. Even if it seems to "work", you should not be mutating your state values. You should be using a state setter function to update them, which is what you are doing in removeBookings.
My question is therefore, instead of mapping each slot (with both and information present in each slot), would it be more efficient to use a for loop to only render the relevant slot, rather than the information for both slots?
Your map approach is not rendering both. For each slot, it uses an if statement to return one component or the other depending on whether the slot is booked. I'm not sure how the for loop you're proposing would even work here. It would just return before the first iteration completed.
This I assume would require state to be used for both the add booking and remove booking function.
You should be using setTimeslots for all timeslot state updates and you should not be mutating your state values. That is true no matter how you render them.

How to render and add similar item to the list of items from the api in React.js?

I have a component called ListItem which have 7 fields(components)ignore diagram it has only 4,all are react select component also it has save and delete icon for each ListItem. I am using context, where i have set lists=[] , On useEffect I have set the lists to array of objects that I got from api . I have a component Lists that renders this ListItem component by using map function. Now I want to add another list at the front of the first ListItem , I am able to do so and a record is made in the Database. I have implemented pagination and on pagination I am prepending all the list items that I have in state along with the items that's being returned from api since the product I add was inserted into Database so at the api call for the last page I also get the data that I added at the top, so I see same data twice. 1)When I clicked Add Product 2)As last ListItem since the record was added to database. What will be the correct approach to deal with such scenerio. Please ignore if I can't make sense because this is my first question.
It's hard to give specific advice without seeing your code, but I think I understand the problem. You need to keep the items that you are adding separate from the ones which you retrieved from the database. You also need to keep any UI state separate from the data.
In terms of user experience, I think it's good to show the item which they just added at the top even if it doesn't actually belong on that page. I would style it differently (probably a darker background color) to indicate that this is an added item rather than a true element of the list.
When they navigate to a different page of the list then the added item should not be prepended anymore. They would only see the added item in its actual position in the list, which may or may not be on that page.
Here is some psuedo-code for how I would handle this.
const List = () => {
// store the current page (or possibly store it in the context)
const [page, setPage] = useState(1);
// some hook which accesses API data via context
// should refresh the API fetch when the page changes
const items = useListPage(page);
// store items which were added while on this page
const [addedItems, setAddedItems] = useState([]);
// clear added items on page change
useEffect(() => {
setAddedItems([]);
}, [page]);
return (
<div>
<AddForm
// AddForm component would handle the POST request
// if successful, add that item to the List state via onSuccess callback
onSuccess={(object) => setAddedItems([object, ...addedItems])}
/>
{addedItems.map((item) => (
<ListItem
{...item}
key={item.id}
temporary={true} // some sort of flag to control styling
/>
))}
{items.map((item) => (
<ListItem
{...item}
key={item.id}
temporary={false}
/>
))}
<Pagination
page={page}
setPage={page}
/>
</div>
);
};

React components rendering with wrong props when changing filter

I have a page in a react app that uses a radio form to filter different objects of data that are being passed to the page.
The problem I am encountering, is that when I change the filter, (click on a different option on the radio form), only some of the data in the resulting list changes. A simple example of what happens is as follows:
Option one is selected with all the old data
Option two is selected, but only some of the new data comes through
First, I use an axios request to get an array of objects that will be used for the data:
componentDidMount() {
axios.get("xxxxxxxxx")
.then(result => {
this.setState({
data: result.data
});
});
Then, I create an array that filters the data from axios by an attribute based on which radio option is selected in the form:
let filteredData = [];
filteredData = this.state.data.filter(thisData => thisData.attribute === "attribute1");
Finally, I map all of the filtered data in the render function:
filteredData.map(filteredItem => ( <MyComponent key={i++} itemInfo={filteredItem.info} /> ))
In the definition of MyComponent, I use props to access the filtered item's info and put it into the table like this:
<td>{this.props.itemInfo.thisDataPoint}</td>
I'd love to hear if anybody has any idea why some of the components data updates when the filter changes, but not all of it. It seems weird to me that some data changes but some does not.
I have tried converting the props into state so that the component re-renders on the change but that did not work.
Thanks again for any help I get :)
given that filteredData is correct, and based on your code the issue must be on key={i++}. using some kind of implementation index can often lead to rendering problems, react will have trouble to distinguish the components since it uses the key to track them.
you should provide some unique identifier to each component as key like key={filteredItem.id}. if you don't have it, you can generate it with some library like uuid.

Old props being used when rendering dynamically rendered list React/Redux

I am rendering content that I created by mapping over an array which returns an array that displays the items individually. I am using an index position from my store to determine what card is being shown. This works great but when I implement an onClick event, which takes the current index Im using from the store, then pushes content to an array using the index from the store things start to break down. It returns the initial index value instead of the current index value. This is simply isolated to the onClick event as I am using the index value in the actual render method and it is behaving as intended. I'm assuming this behavior is happening because when I am mapping over the list when the component mounts, it is binding the props of the initial value and not passing the updated value? Here is the code thats in question
// This is the hook that creates the list of cards
const [cardDeck, setCardDeck] = useState([])
useEffect(()=>{
renderCardArray();
},[props.news])
// This is the function that calls the action creator
const addToList = () =>{
props.pushItemToReadingList(props.news[props.cardIndex]);
props.increaseCardIndex();
}
// This is the map that gets set to a state value, that I can individually show
const renderCardArray = () =>{
if(props.news.length > 1 && props.cardIndex < props.news.length){
setCardDeck(props.news.map((item,iter)=>{
return(
<div className="card" ref={drag} key={iter}>
<h3>{item.title}</h3>
<img src={item.urlToImage} alt='News' style={{maxWidth: '200px'}} />
<button onClick={props.increaseCardIndex}>X</button>
<button onClick={addToList}>{'<3'}</button>
</div>
)
}))`
// The 2ed button is returning an index of 0 every click, but the cards update with the proper index in the final return
// Here is the final return value of the component
return (
<div>
{cardDeck[props.cardIndex]}
</div>
)
}
// finally here is the connect and mapStateToProps
const mapStateToProps=(state)=>{
return {news: state.news, cardIndex: state.cardIndex}
}
export default connect(mapStateToProps,{
pushItemToReadingList,
increaseCardIndex
})(NewsCard);
If someone can help me understand why when I execute the addToList function it does not receive the updated index that the rest of the component is receiving? Also is there a better way to provide updated props to a mapped list?(I have temporarly solved this problem by switching around my action creator to not take an argument to push, it simply uses the store data internally but it doesn't help me understand why this behavior is happening) (also I am not planning on using local state and a redux store, I have split the two here simply to test this method of rendering and animating cards without having to write an additional action creator and reducer for my cardDeck)
It looks like you have a missing dependency props.cardIndex in your useEffect array, causing it to not update when props.cardIndex updates:
useEffect(()=>{
if(props.news.length > 1 && props.cardIndex < props.news.length){
renderCardArray();
}
// renderCardArray depends on `props.cardIndex`, so
// make sure to add it here
},[props.news, props.cardIndex])
It would be clearer if the contents of renderCardArray were inline in useEffect (unless you need to call that function somewhere else). You could also pass news and cardIndex into renderCardArray to make it clearer what the dependencies are.
renderCardArray(props.news, props.cardIndex);
I'm not sure if that will solve your problem, but here are a few suggestions that might make the code easier to reason about/debug:
Use a value for key that's more stable than an index. Indices change if elements are added to/removed from the array, and it can cause bugs. If item.title is unique for each item, you can use that instead.
Set data in state, and limit JSX to what you return from your component. It looks like you only need to render one card at a time, and may not even need to store anything in local state:
const item = props.news[props.cardIndex];
return (
<div className="card" ref={drag}>
<h3>{item.title}</h3>
<img src={item.urlToImage} alt='News' style={{maxWidth: '200px'}} />
<button onClick={props.increaseCardIndex}>X</button>
<button onClick={addToList}>{'<3'}</button>
</div>
)
If you do need to keep an array of cards in state, keep the data instead of the JSX, and map over the cardDeck data when you return:
const [cardDeck, setCardDeck] = useState([])
useEffect(()=>{
if(props.news.length > 1 && props.cardIndex < props.news.length){
setCardDeck(props.news);
}
},[props.news, props.cardIndex]);
...
return (
cardDeck.map(card => (
<div className="card" ref={drag} key={card.title}>
<h3>{card.title}</h3>
<img src={card.urlToImage} alt='News' style={{maxWidth: '200px'}} />
<button onClick={props.increaseCardIndex}>X</button>
<button onClick={addToList}>{'<3'}</button>
</div>
)
)
Actions should tell the reducer what, rather than how. They should describe what action happened, rather than how the reducer should update the state. This keeps all logic for the state in one place (the reducer), and makes a clean separation of concerns. Instead of pushItemToReadingList and increaseCardIndex, you could dispatch a single action addItemToReadingList and let the reducer be responsible for adding it to a stored list and increasing the index.

setState not triggering a re-render when data has been modified

Just started working with React, and it's been going relatively smooth up until this point.
I have a list, that contains X pre-defined items, which means that the list is always rendered with a given amount of rows.
This data is collected from a REST API, prior to the list rendering. The data contains variables relating to the list, as well as an array that contains each item within the list.
I chose the easy route, so I populate every component with a single JSON object named 'data', that contains everything necessary.
Rendering the list looks something like this:
<MyList data={data} />
Then, in the getInitialState of MyList:
dataInt: this.props.data.dataInt || 0,
dataStr: this.props.data.dataStr || '',
rows: this.props.data.rows || []
So I store my array of items (JSON) in the initial state of the list (parent), and when I choose to render the list, I first create all components (children) in an array, like this:
var toRender = [];
for(i = 0; i < this.state.rows.length; i++) {
toRender.push(<ItemRow data={this.state.rows[i]} />);
}
and then I render like this:
return (
<div className="item-container">
<table className="item-table">
{toRender}
</table>
</div>
);
The render-function of MyItem look something like this:
return (
<tr>
<td>{this.state.dataFromItem}</td>
</tr>
);
Now, let's say I want to modify the child data from within the parent, with some new data I just got from the API. Same structure, just the value of one field/column has changed:
i = indexOfItemToBeUpdated;
var newRows = this.state.rows;
newRows[i] = data; // New JSON object with same keys, different values.
this.setState({ rows: newRows }); // Doesn't trigger re-render
this.forceUpdate(); // Doesn't trigger re-render
What am I doing wrong?
At first I thought it was because I was wrapping the render function of MyItem in a , but since it renders perfectly fine on the initial render, I will assume that is an acceptable wrapper.
After some testing, it seems that the parent view is re-rendered, but not the children (which are based on the data that is updated within the parent).
JSfiddle: https://jsfiddle.net/zfenub6f/1/
I think the problem could be the your are only using getInitialState for the Row. You are setting state with the passed in props. If the children get new props, getInitialState won't get called again. I always refer back to https://facebook.github.io/react/docs/component-specs.html to see all the lifecyle events. Try using componentWillReceiveProps(object nextProps) to set the state again. If you don't actually need state in the child, remove the use of state and just use props and it probably will work.
If you're children are using State and state is not updating, what about props? does that render just fine? If the parent changes the state of the children and the children don't reflect that change those child components most likely need a unique key assigned to them which tells React that these items need to be updated when the state is updated on the parent. I ran into a similar issue where props inside the child updated to reflect the parent but the state of the child would not update. Once I added keys, the solution was resolved. For further reading on this check out this blog that initially hinted the problem to me.
http://blog.arkency.com/2014/10/react-dot-js-and-dynamic-children-why-the-keys-are-important/
There is also the official documentation from React that explains this situation.
http://facebook.github.io/react/docs/multiple-components.html#dynamic-children

Categories

Resources