Updating sibling component on an event in react js - javascript

I have a simple app which consists of a home component , an add component , Listing component. Home component has listing and add component. In add component i am adding some items to local storage and in listing component i am retrieving those items and showing as a list. Listing component doesn't have any props, it directly gets the data from local storage and returns ul list. I wanted to know, is there a way that when I save my data to local storage, at the same time my list should be updated that means there has to be some way by that if i click on save button the listing component should render with updated list. Can i trigger the render method of any component manually without using states.

this.forceUpdate() does work, but is in most cases a cheap fix which you want to avoid. You could have a the listings in two places: Local Storage for the actual saving, the state to trigger re-rendering when needed. Your app might look something like this:
constructor() {
super()
this.state = {
listings: []
}
}
componentWillMount() {
localStorage.getItem('listings')
this.setState({ listings })
}
saveListing(newListing) {
const listings = this.state.listings.concat([])
listings.push(newListing)
localStorage.setItem('listings', listings)
this.setState({ listings })
}
render() {
return(
<div>
<ul>
{
this.state.listings.map(listing => {
return(
<ListingComponent key={listing.id} listing={ ...listing } />
)
})
}
</ul>
<AddListingComponent addListing={(newListing) => this.saveListing(newListing)} />
</div>
)
}

There is a method called this.forceUpdate() to re-render , so just call this method after each element added to local storage
addItem(elm){
localStorage.yourArray.push(elm);
this.forceUpdate();
}

Related

React How to back to previous page with filters still in place

Having a hard time trying to figure this out.
I Have 2 Components
A filter component & A table view component
A enhanced view of number table view with more info and functionality
From component 2 i want to go back to component 1 but have the filters still in place?
How can i acheive this?
I've tried
this.props.history.push('/1');
It goes back but doesn't keep the filters?
export default class FilterComponent extends React.Component {
constructor(props) {
super(props)
this.state = {name:[]}
}
componentDidMount() {
fetch("https://jsonplaceholder.typicode.com/todos")
.then((response) => response.json())
.then(
(data) => data.map(e =>
this.setState({name:[...this.state.name, e.id]})
))
}
onTrigger = (event) => {
this.props.parentCallback(event.target.value);
event.preventDefault();
}
render() {
if(this.state.name.length < 1) {
return (
<div>
<p1> Loadings</p1>
</div>
)
} else {
return (
<div className="contactfForm">
<select onChange={this.onTrigger}>
<option> ID </option>
{this.state.name.map((result)=>(<option title={result}>{result}</option>))}
</select>
I guess you are rendering the filters twice, inside component1 and again inside component2. If you want to navigate and preserve the state, you have two react-based options, and a JS one:
MOVING FILTERS OUTSIDE ROUTER SWITCHER
If you render the filters outside the Switcher of the router, when changing route only the component1 and component2 will rerender, but not the filters.
STORING FILTERS STATE IN A GLOBAL STATE
If rendering the filters outside the Switcher of your Router is not feasible (Because maybe the filters are not rendered in every page of your app) you could save the filter state in a global state (Using for example, React Contexts, see https://reactjs.org/docs/context.html; or Redux).
If you use React Contexts, you could create the provider ouside the router, and inside the Filters component retrieve and modify its values. This way you will mantain the state between components.
SAVING FILTER STATE IN LOCAL STORAGE
If you want to preserve the state also when refreshing the page, you could store the filter state in the Local Storage of the Browser. Every time the Filters component is rendered, you would retrieve the value from the Local Storage, and every time the filters are modified, you would save it. That will also make that when changing the page, as the Local Storage is mantained, the filter state is mantained too.

React component retrieves props just once, goes undefined when refreshed

I'm creating a simple movie app with moviedb. I have successfully retrieved the most popular 20 movies and put them in the app state:
constructor(props) {
super(props);
this.state = {
movieInfo: [],
}
}
componentDidMount() {
this.getMovies();
}
getMovies = async () => {
await axios.get('https://api.themoviedb.org/3/movie/popular?api_key=94d4ad026c5009bdaf4aecb8989dfa07')
.then(res => this.setState({ movieInfo: res.data.results }))
}
I know the array was retrieved correctly because when I look at the React components in Chrome dev tools I see what I want:
Screen cap of App state
Then in the render part of the App I want to pass the first element in the array to a component called Movie, which will then display some info about the movie:
return (
<div>
<Movie movie={this.state.movieInfo[0]} />
</div>
);
}
I know the movie component is getting this info correctly because I see the object representing the first movie in the Movie component props:
Movie component props
My Movie function looks like this:
return (
<div>
<h1>{props.movie.original_title}</h1>
<p>{props.movie.overview}</p>
</div>
)
}
The first time I compile it this works and I see the info I want:
Rendered App with Movie component
But incredibly, when I refresh the page I see the error message
TypeError: Cannot read properties of undefined (reading 'original_title')
How is it possible that the App will correctly pass on the info to Movie and will display it correctly once, but as soon as I refresh the page somehow it's undefined?
Thanks in advance for the help,
JD
I assume that you don't get an error when you are developing and changing code. When you save your code Hot reloading only changes parts that changed.
This means that if your app loads data and populates this.state.movieInfo with an array from BE, and your save you code, it Hot reloads and you get new data. So, this.state.movieInfo[0] is always filled with data.
When you refresh your app, it just resets to an empty array as you put it there in the constructor.
Solution is to always check if there is that first element in array before rendering the Movie component:
return (
<div>
{this.state.movieInfo[0] ? <Movie movie={this.state.movieInfo[0]} /> : null}
</div>
);
You may need to also use componentDidUpdate() with componentDidMount():
componentDidMount() {
this.getMovies();
}
componentDidUpdate() {
this.getMovies();
}
when the page reloads init state is an empty array. you have to check the array has an item to render or not.
return (
<div>
{this.state.movieInfo && this.state.movieInfo.length>0 && (<Movie
movie={this.state.movieInfo[0]} />)}
</div>
);

React reuses components instead of creating new so that I have to rely on componentWillReceiveProps to load fresh data

I have a problem with ReactJS as when in parent component's state that stores child components (as thumbnails) the child components stored as array of components are constructed once and have componentDidMount called once. Once I 'reimport' data and create new child components based on new data from backend API (for example upon new sorting mode activated) the components do not call componentDidMount and i have to rely on componentWillReceiveProps method to import for example a link to the picture to be displayed on a thumbnail (it seems like react reuses components). If for example the the data in child components is being imported slowly it shows and old photo because remembers previous iteration done in own componentDidMount done after creation.
How can i force react to always create new child components from the scratch and thanks to that achieve having componentDidMount called to include data import from backend and avoid relying on componentWillReceiveProps call?
Here is the pseudocode where parent component ComponentManager imports person data from backend and creates thumbnails based on retrieved JSON. Thenafter it can upodate thumbnails after user changes sorting order:
class ComponentManager extends Component {
constructor(props) {
super(props);
this.state = {
personsThumbnails : undefined
}
}
componentDidMount() {
// Import person ids and create SinglePersonThumbnail(s) child components as the state personsThumbnails
importPersonsIds();
}
importPersonsIds(sortingMode) {
// Importing persons data from backend API and created thumbnails stored in personsThumbnails state
...
}
render() {
return(
<div>
<button onClick={()=>{this.importPersonsIds("SORT_BY_AGE")}}>Sort by age</button>
<button onClick={()=>{this.importPersonsIds("SORT_BY_NAME)}}>Sort by name</button>
<div>
{this.state.personsThumbnails}
</div>
</div>
);
}
}
class SinglePersonThumbnail extends Component {
constructor(props) {
super(props);
this.state = {
photoUrl : undefined,
personsName : undefined
}
}
componentDidMount() {
// Called when component is created
this.importDataAndPhotoForPerson(this.props.id);
}
componentWillReceiveProps(nextProps) {
// Called always when ComponentManager changes the order of thumbnails upon other sorting mode triggered
this.importDataAndPhotoForPerson(this.props.id);
}
importDataAndPhotoForPerson(id) {
// Imports name of the person and link to photo stored in state
}
render() {
return(
// Display image by link and person's name based on photoUrl and personsName states!
);
}
}
you can use componentDidUpdate() and can see the documentation from official site.
componentWillReceiveProps is outdated and not recommended. A better idea will be to store the data from backend in some context/redux. This will cause the child components to re-render with updated data.

What is the best approach in React Application for the case without using Redux?

I want to submit data via an api on a click of button in the parent Component, on collecting data from multiple sub components in different hierarchy.
I have a discussion on the case with one of my colleague and he suggest me to have a method in a parent comp. and share that (as a handler) to child(s) in prop and then to further to its child(s), keeping child comp. as stateless comp. Such that when user inputs the data it will store into a state variable (in parent comp.) via that handler method. For e.g:
class Parent extends Component {
constructor(props){
this.state.data = {}
}
updateHndler(key, value){
this.setState({
data : {...this.setState.data, key: value}
})
}
}
const ChildA = props => {
onComplete(e){
this.props.updateHndler('text', e.target.value)
}
return (<div>
...
..
<input .. onChange={()=>{onComplete(e)}}
</div>)
}
As each sub-component setting the value in parent state variable for each value it is getting update and its render() method keeps trigger, considering the rendering cost for all the sub-components on each change is the above approach is even good enough?
Thanks

React: update one item in a list without recreating all items

Let's say I have a list of 1000 items. And I rendering it with React, like this:
class Parent extends React.Component {
render() {
// this.state.list is a list of 1000 items
return <List list={this.state.list} />;
}
}
class List extends React.Component {
render() {
// here we're looping through this.props.list and creating 1000 new Items
var list = this.props.list.map(item => {
return <Item key={item.key} item={item} />;
});
return <div>{list}</div>;
}
}
class Item extends React.Component {
shouldComponentUpdate() {
// here I comparing old state/props with new
}
render() {
// some rendering here...
}
}
With a relatively long list map() takes about 10-20ms and I can notice a small lag in the interface.
Can I prevent recreation of 1000 React objects every time when I only need to update one?
You can do it by using any state management library, so that your Parent doesn't keep track of this.state.list => your List only re-renders when new Item is added. And the individual Item will re-render when they are updated.
Lets say you use redux.
Your code will become something like this:
// Parent.js
class Parent extends React.Component {
render() {
return <List />;
}
}
// List.js
class List extends React.Component {
render() {
var list = this.props.list.map(item => {
return <Item key={item.key} uniqueKey={item.key} />;
});
return <div>{list}</div>;
}
}
const mapStateToProps = (state) => ({
list: getList(state)
});
export default connect(mapStateToProps)(List);
// Item.js
class Item extends React.Component {
shouldComponentUpdate() {
}
render() {
}
}
const mapStateToProps = (state, ownProps) => ({
item: getItemByKey(ownProps.uniqueKey)
});
export default connect(mapStateToProps)(Item);
Of course, you have to implement the reducer and the two selectors getList and getItemByKey.
With this, you List re-render will be trigger if new elements added, or if you change item.key (which you shouldn't)
EDIT:
My inital suggestions only addressed possible efficiency improvements to rendered
lists and did not address the question about limiting the re-rendering
of components as a result of the list changing.
See #xiaofan2406's answer for a clean solution to the original question.
Libraries that help make rendering long lists more efficient and easy:
React Infinite
React-Virtualized
When you change your data, react default operation is to render all children components, and creat virtual dom to judge which component is need to be rerender.
So, if we can let react know there is only one component need to be rerender. It can save times.
You can use shouldComponentsUpdate in your list component.
If in this function return false, react will not create vitual dom to judge.
I assume your data like this [{name: 'name_1'}, {name: 'name_2'}]
class Item extends React.Component {
// you need judge if props and state have been changed, if not
// execute return false;
shouldComponentUpdate(nextProps, nextState) {
if (nextProps.name === this.props.name) return false;
return true;
}
render() {
return (
<li>{this.props.name}</li>
)
}
}
As react just render what have been changed component. So if you just change one item's data, others will not do render.
There are a few things you can do:
When you build, make sure you are setting NODE_ENV to production. e.g. NODE_ENV=production npm run build or similar. ReactJS performs a lot of safety checks when NODE_ENV is not set to production, such as PropType checks. Switching these off should give you a >2x performance improvement for React rendering, and is vital for your production build (though leave it off during development - those safety checks help prevent bugs!). You may find this is good enough for the number of items you need to support.
If the elements are in a scrollable panel, and you can only see a few of them, you can set things up only to render the visible subset of elements. This is easiest when the items have fixed height. The basic approach is to add firstRendered/lastRendered props to your List state (that's first inclusive and last exclusive of course). In List.render, render a filler blank div (or tr if applicable) of the correct height (firstRendered * itemHeight), then your rendered range of items [firstRendered, lastRendered), then another filler div with the remaining height ((totalItems - lastRendered) * itemHeight). Make sure you give your fillers and items fixed keys. You then just need to handle onScroll on the scrollable div, and work out what the correct range to render is (generally you want to render a decent overlap off the top and bottom, also you want to only trigger a setState to change the range when you get near to the edge of it). A crazier alternative is to render and implement your own scrollbar (which is what Facebook's own FixedDataTable does I think - https://facebook.github.io/fixed-data-table/). There are lots of examples of this general approach here https://react.rocks/tag/InfiniteScroll
Use a sideways loading approach using a state management library. For larger apps this is essential anyway. Rather than passing the Items' state down from the top, have each Item retrieve its own state, either from 'global' state (as in classical Flux), or via React context (as in modern Flux implementations, MobX, etc.). That way, when an item changes, only that item needs to re-render.
One way to avoid looping through the component list every render would be to do it outside of render function and save it to a variable.
class Item extends React.Component {
shouldComponentUpdate(nextProps, nextState) {
return this.props.item != nextProps.item;
}
render() {
return <li>{this.props.item}</li>;
}
}
class List extends React.Component {
constructor(props) {
super(props);
this.items = [];
this.update = this.update.bind(this);
}
componentWillMount() {
this.props.items.forEach((item, index) => { this.items[index] = <Item key={index} item={item} /> });
}
update(index) {
this.items[index] = <Item key={index} item={'item changed'} />
this.forceUpdate();
}
render() {
return <div>
<button onClick={() => { this.update(199); }}>Update</button>
<ul>{this.items}</ul>
</div>
}
}
There is a way to do this, but I don't think the React team would call it Kosher so to speak. Basically, instead of the parent having state, it has lists of refs. Your components have their own state. Your components are created once in the parent and stored in a parent's ref property which holds an array of all those components. So the components are never recreated on each rerender, and instead are persisted. You also would need a list of refs that attach to a function in each component to allow the parent to call individual components (in hooks you can use imperativehandle to do this).
Now, when the user does something that would cause the data to change for a specific component in that list, you would find that component's ref in the list of attached functions and call the function on it. The component could then update and rerender itself based off that function call from its parent, without other components being affected/recreated/rerendered.
I believe this is called imperative programming rather than declarative, and React doesn't like it. I personally have used this technique in my own projects for similar reasons to you, and it worked for me.

Categories

Resources