React - changing key prop doesn't cause rerender of child component - javascript

I am using react-infinite-scroll to render a feed of data. Whenever the user changes their settings, I want to refresh the feed of data.
To do this, I have a key prop for the InfiniteScroll component. The idea being whenever the user changes their settings
this.state.renderCount is incremented by 1 in setState
React notices that the key prop of InfiniteScroll is different, and re-renders the component from scratch.
<Content style={{ padding: "0px 20px" }}>
<InfiniteScroll
key={this.state.renderCount}
style={{ width: "100%", height: "100%" }}
pageStart={0}
loadMore={this.loadPosts.bind(this)}
hasMore={true}
loader={<div>Loading....</div>}
useWindow={false}
>
{listItems}
</InfiniteScroll>
</Content>
The refresh code is in componentDidUpdate and is called if I determine that a refresh is needed:
this.setState({ renderCount: this.state.renderCount + 1 })
However, the component is not reset. Why is this?

key and ref are special keywords in react. See
Most props on a JSX element are passed on to the component, however,
there are two special props (ref and key) which are used by React, and
are thus not forwarded to the component.
For instance, attempting to access this.props.key from a component
(i.e., the render function or propTypes) is not defined. If you need
to access the same value within the child component, you should pass
it as a different prop (ex: <ListItemWrapper key={result.id}
id={result.id} />). While this may seem redundant, it’s important to
separate app logic from reconciling hints.
To pass they key prop to the child element use another name.
See also : Why is ref not a property in children components?

Turns out I was overthinking it.
With the way react-infinite-scroll works, I just have to clear the listItems array.
In my case, I just had to do:
this.setState({ templates: [] })
(listItems is generated by a map function that maps templates to jsx).

Related

React - Change state from external component

I know that I will ask a question that brake some rules about the core/basic way to use React... but maybe with this example, someone helps me to solve the problem that I facing.
This is not the full code of my project, but show exactly the idea of my problem:
https://codesandbox.io/s/change-state-from-external-component-zi79e
The thing is I need to change a state from a child component from the parent component, but I don't want to run a render method in my parent or handle the state in the parent component.
Exists a way to achieve this? In my project, I have a parent that creates multiple generic children and it will be more difficult to handle this request.
And specifically, I need to change the state of one child (MyFirstChild), after another child (SecondChild) read the keystroke and run an API to get some values from my backend; after that, I need to send the change to "MyFirstChild" to change his state.
The parent component has ~50 child components and I blocked the re-render method (With the method shouldComponentUpdate)
The expected answer is: "It's not possible, or, you broke the good use of React"...
But, maybe using forwardRef or ref, or something else that I not see can help me to work around this...
To change the state of a child component from the parent without having to run a render method (in the parent): one possible solution would be to use Redux. With Redux from the parent you can dispatch an Action (execute an Action that changes the state of Redux).
And in the child component, you receive that part of the state that you change (it could be a string, object, etc) as a prop. So when it is changed from the parent, your child component will render again without having to run a render method in the parent.
https://react-redux.js.org/introduction/basic-tutorial
You could also use Context for the same purpose.
However, I saw your code example, I am not sure the exact reason of why you don't want to make a render in the parent, but the easiest solution for what you need would be to send the function that you want to execute in the parent as a prop and also the title.
If you want to change things from the parent, without re-rendering again with Redux, it would be something like this:
In the parent
const changeTitle = (newTitle) => {
this.props.setTitle(newTitle);
}
return (
<div className="App">
<ChildComponent />
</div>
);
const mapDispatchToProps = dispatch => ({
setTitle: newTitle => dispatch(setTitleACTION(newTitle)),
});
In the child
return (
<div>
<h1>
{this.props.title}
</h1>
<h2>Start editing to see some magic happen!</h2>
</div>
);
const mapStateToProps = ({ title }) => ({
title,
});
export default connect(mapStateToProps, null)(ChildComponent);
Again if you make this with Redux, you can get the "title" prop from the Redux store, and in the parent, you will change that variable (calling to a Redux action) without rendering the parent again.
If you want to fire the event from the child component, you can dispatch the action from the child component or you could call a function (that you receive from props from the parent) and call that function whenever you need.
Can't we use props here for passing data instead of trying to manipulate state from outside of component?
Based on #david paley explanation...
If the only way to achieve this is using Redux, I post my solution (It's the same example, but, implementing Redux)... hope that works for anyone else.
https://codesandbox.io/s/change-state-from-external-component-redux-rmzes?file=/src/App.js

Is there a way to pass a group of props in react?

The way I actually use works well but my props keep growing and I wondering if I can group them into one single state and pass it to the child, this is my code:
<Panel
uniqueIDs={uniqueIDs}
userID={userID}
loading={loading}
premium={premium}
percent={percent}
total={total}
until={until}
user={user}
done={done}
/>
After the render I define those variable like so :
let { loading, empty, total, uniqueIDs, slice, percent, until, ok,
user, premium, data, keys, done, userID } = this.state;
Can i just send this.state variable? I made a little bit of research I didn't find any solution to my issue, I know I can use third-party libraries to manage state but I am trying to keep it simple.
you can make an object for the props you are sending and can use spread operator
let props = {
uniqueIDs : uniqueIDs,
userID:userID,
loading:loading,
premium:premium
}
<Panel {...props} />
In panel component you can use this.props.uniqueIDs ans so on.
Yes, you definitely could just pass your entire state-variable into your child-component as multiple properties
<Child {...this.state}/>
This would be perfectly acceptable.
If your state is {id: 1, name: 2}
You would still be able to access the props in your child-component as
props.id or this.props.id
props.name or this.props.name
As a note you should be cognizant of component re-rendering. If you make many updates to the state in your parent-component this will also cause your Child component to re-render a lot as well, which could have performance issues.
To workaround this, make sure to employ methods like componentDidUpdate() for class-components and react-hooks for functional components. These can help control the flow of re-rendering.

Is there a better way to pass multiple props down to n children

Currently working a group project and I've been looking over the code the group wrote and I'm curious if there is a better way to pass props to n children components that change the state of the top-level App component.
currently we have something like this:
return (
<div>
<header className="navBar">
// NavBar is an example for the question
<NavBar
showSplash={this.state.showSplash}
displayAllSearchResults={this.displayAllSearchResults}
searchBarDisplay={this.state.showCard}
updateResults={this.updateResults}
selectResult={this.selectResult}
searchResults={this.state.searchResults}
showSuggestions={this.state.showSuggestions}
/>
</header>
<section className="App">
<article className="mainContent">{card}</article>
</section>
<div className="appBackground" />
</div>
);
All of these methods (which there are probably too many) are being passed into the NavBar component that itself is composed with a SearchBar component which also needs that giant block passed into it in order to change the state all the way back at the top-level App. This feels wrong, is there a solution or better approach to passing all these props down?
You can use the spread operator like this :
<NavBar {...this.state} />
It will pass you state properties as props to the navbar.
In you Navbar component, you can get the value of showSplash in state from props.showSplash
If you want to pass the data into a child component of Navbar, you can do something like this :
<ChildComponent {...props} /> // if Navbar is a functional component
<ChildComponent {...this.props} /> // if Navbar is a stateful component
Reference:
Spread in object literals
Here is a sample stackblitz for illustration.
If you want a data to be accessible by all the child components, then you have following options:
React Context
Redux or state management library like flux etc.
Or you could follow #Abdelkarim EL AMEL answer if that soughts out you issue for now.I would suggest take a look at the above mentioned options and if setting a global state which can be accessed anywhere in the app helps you to not send prop to each component explicitly. I would prefer to use a global state for variable which are common and needs to be accessed by all the child of the root app.
For example a button component to change the theme of the app. It changes the state of the root app and is accessible by every component.
I think this is actually entirely OK, and would recommend leaving it.
If you don't like seeing it itemized in the return of the render method you could keep them all in an object called navbarprops and just spread that in. I still prefer it as is though!

Pass component instance directly into prop or use a render callback?

What would be the idiomatic and/or more effective way of handling a situation whereby the parent component wants the child to render some custom JSX?
a render callback?
or passing a component instance directly into the prop?
Example:
// Option 1
// -
const rightChild = <View />
<NavigationBar rightChild={rightChild} />
// Option 2
// -
const renderRightChild = () => <View />
<NavigationBar renderRightChild={rightChild}>
(lambdas are used for simplicity, I am aware of the performance implications)
This:
const rightChild = <View />
// ...
<NavigationBar rightChild={rightChild} />
...isn't "passing JSX into the prop." It's passing a component instance. The JSX is processed by const rightChild = <View /> (turned into React.createElement call) and the result (a component instance) is stored in rightChild. That's normal, though it's usually as a child (ending up on the children prop) rather than a separate named prop. But either way, it's a prop, so...
As far as I know, that would be the standard way to do it.
Either way will work. The case for when you really want to use a render prop is when you will need the props passed from NavigationBar in order to render the component or fragment. The benefit to passing in a component is that you will not be passing in a new function to be rendered each time. From a performance standpoint it would be a bit easier to get it to not for a re-render.

How to transfer states among components?

I'm new to React JS, I want to create a ToDo list app. I have App.js which is the main page and so I have created MainBlock component for App.js page which holds left and right side of my layout and in right side property it loads Form component which has input and button and saves input values to an array in state and in left side it loads List component which has a property named allTasks which prints all tasks.
My problem is how can I transfer allTasks state from Form Component to App.js to pass it to List component property?
In App.js :
<MainBlock
left={
<List allTasks={['خرید از فروشگاه', 'تعویض دستگیره درب منزل']} />}
right={
<Form />
} />
You can accomplish this by storing the tasks as state in the App component, and have Form pass up the state through a callback prop. This concept is called "lifting state up". Here's a guide about it: https://reactjs.org/docs/lifting-state-up.html
<MainBlock
left={
<List allTasks={this.state.allTasks} />
}
right={
<Form onSubmit={allTasks => this.setState({ allTasks })} />
}
/>
Let tasks be a state of the MainBlock component. Pass this state down to the List component.
Let the Form component expose a callback property (maybe onTaskCreated) which is invoked when user complete creating a new task.
Let MainBlock intercept onTaskCreated callbacks and update it's internal state with the new task, which causes it to re-render and thus passing down the new task list to the List component.
You are trying to achieve two way binding here. Unfortunately, React is a library that doesn't support two way binding by default.
You can only pass in the values from Parent to Child to Sub-Child. However, the reverse is not true.
In this case, you are trying to load props from Form to App that is from Child to Parent which is not possible.
You should make use of state containers such as Redux that overcomes this limitation by making the state of one component available to the other component.
You can store all your tasks in the state of the MainBlock component and pass functions to update the state by your form. Also, pass the state (tasks) of MainBlock to your List component. Updating your tasks to MainBlock will automatically pass the tasks to List. Later you could use a state management library like MobX and Redux.
It can be confusing passing data between components. Libraries like redux were created to make this "easier", but it can be done with simple React. To pass state information to children, you pass it into their props. To send data the other direction, you pass (from parent to child) a handler function that is called from the child and can affect the state of the parent (or be passed to it's parent) as needed.
I wrote a sample pen to help people wrap their heads around this. Hopefully it helps.
Something like:
<Child1 handleAdd={this.handleAdd.bind(this)}
handleSubtract={this.handleSubtract.bind(this)}
/>
calls in the parent and and is accessed in the child as:
<Button onClick={this.onAdd.bind(this)}>
add
</Button>
<Button onClick={this.props.handleSubtract.bind(this)}>
subtract
</Button>
Then there is a function in the parent called handleAdd that can affect the parent state.

Categories

Resources