React good practice from parent state to children props - javascript

So i've been reading a lot lately on react state and props.
My app wasn't that big, but now i'm facing a problem that seems to be commun for a lot of people, and i'm trying to find the best way to implement this.
My app is simple. A SearchBar on top, that display a list of contact. My search bar is a component and is updating a react-redux store with the results of the searchBar value (calling a backend with axios). Till here everything works great.
When the results array is populate (in redux store), my container rerender the results array. Like this:
class Suggestions extends Component {
render() {
console.log('before map: ', this.props.contacts);
const {
contacts,
...rest
} = this.props;
const options = contacts.map((contact, index) => (
<Contact
key={contact.id}
renderToaster={renderToasterFunction}
contact={contact}
/>
));
return <div>{options}</div>;
}
}
const mapStateToProps = (state, props) => ({
contacts: state.contact.results,
});
export default connect(mapStateToProps)(Suggestions);
The problem happen in my Contact component, My list is a lirs of sometimes 10 contacts that are display on the same page. So my problem is that each Contact component need to have it's own state (to add or edit info exemple: if you need to add a new phone number).
//contact component
state = {
contactState: ???
}
...
render(){
//exemple for simplicity
return <div>{this.state.contactState.name}</div>
}
I've founded on react website that it's not a good idea to copy props from parent in state of child. And in my case i've seen it, because if i do this
...
state = {
contactState: this.props.contact <--info from parent
}
first search is ok, but second search with an other letter, results list is not updated and i still see some results of first search.
so i've tried to change my contact component to this:
//contact component
state = {
contactState: ???
}
...
render(){
//exemple for simplicity
return <input value={this.props.contact.name} onChange={this.handleChange}/>
}
And this is working great in term of visual update, all my contact are update even if i do 3-4 searches. But my problem is that, now when i want to edit the name i need to store all my contactState somewhere before saving this and second problem, because my component display {this.props.contact.name} when i edit this, the user can't see the new value, because i can't edit props.
So is there a way to render state from props in a child everytime the parent state change. Or is there a way to 1) save the state when the user edit a contact and 2) display the new value he has written ?
What is the best way when dealing with .map() to have one state foreach children that can be re-renderer when the parent state change and rendering all children with their new state.
Thank you for your help.
Don't hesitate if you need more precisions.

I'm not sure to understand everything but if I get what you want to do:
A simple solution could be to dispatch an action on the onChange
The reducer which catch the action will update your redux store
The props will change and the View too.
But that's will make you dispatch A LOT of actions...
Other option :
Use a state in every Contact-Component which duplicates props
state = {...this.props.contact}
Modify the state on the change handler and use it as value too.
Save and dispatch the "final name" to update redux store and call the api at the same moment to update in on your server
Let me know if that's clear enough

Related

How to avoid sibling component rerender

I'd like to know if it's possible to avoid a sibling component rerendering when it's data hasn't changed.
I guess it's probably because of the way i have it structure so let me clarify what i have today.
I have a Smart component which has access to a state similar to this:
showcaseState = {
products: {
1: {
name: "Ball"
},
2: {
name: "Puzzle"
}
},
cart: [2]
}
And this Smart component renders two Dumb Components. The first one (ProductsList component) receives the list of products, and the second one (Cart component) gets a list that only contains the products that match the index(es) inside the cart property.
Whenever i click one product inside the ProductsList component, it fires an action that adds that product index to the cart, so the Cart component gets rerendered.
The thing is, the ProductsList is also getting rerendered, even though it's data didn't change at all. My guess is that it's happening because the Main Component rerenders. How do i avoid this? Should i make the ProductList component Smart, give it access to the products state and remove products from the Main component?
I'm a little bit lost regarding which is the best way to achieve this.
EDIT
The rendering of the Smart components is something like this
let { products, cart } = this.props.app
let cartProds = cart.map(prodId => { return products[prodId] })
<div>
<ProductsList prods={products} />
</div>
<div>
<Cart prods={cartProds} />
</div>
For anyone interested in this, I ended up using redux to manage the app state, and separating the things each component needed.

filter function doesnt rerender in react

I've building a page where it get a list of array of object from the server, and I have few checkbox as filter, user will check or uncheck the checkbox, I will have to filter the list, but I have problem rerender the list now. Below is my partial code.
filterItem () => {
// logic goes here
console.log(filteredItems)
}
renderItems (items) => {
return(
//map logic goes here
)
}
render(){
return(
{this.renderItems(this.props.item)}
)
}
how can I rerender renderItems function by passing the filteredItems as param to it? I tried this.renderItems(filteredItems) but did not see my list got updated.
The props or the state of your component does not change, so your component won't get re-rendered. You probably want to use state (See docs here) to store your data and change it accordingly so that your component will re-render.
You can set your items in state of component
constructor(){
super();
this.state = {
items: this.props.items
}
}
and render it with {this.renderItems(this.state.items)}. If i'm clear understand you, and when user click checkbox you filter and change your items array, will enough just setState this new array, and your component will rerender self.
https://facebook.github.io/react/docs/react-component.html
An update can be caused by changes to props or state.
You are not doing either

ReactJS - component to change what is rendered by another

I am creating a reactJS app that will show a list of mobile phones. I am currently calling an API, setting state to the response, and then displaying the contents. I am now looking to add sort order to this but am having difficulty.
When the user goes to /phones/Apple/iPhone, the routing will render the following component that calls the api, sets the state and passes the data to the results component...
export default class Phones extends Component {
constructor(props) {
super(props);
this.state = {
data: null,
make: this.props.params.make || null,
model: this.props.params.model || null
};
}
componentDidMount(){
const makeQuery = this.state.make ? this.state.make + '/' : '';
const modelQuery = this.state.model ? this.state.model : '';
const fetchUrl = '/api/phones/' + makeQuery + modelQuery;
fetch(fetchUrl, {
headers: {
Accept: 'application/json'
}
}).then(response => {
if (response.ok) {
response.json().then(json=> {
this.setState({data: json});
});
}
});
}
render() {
if (this.state.data) {
const currentUrl = this.props.location.pathname;
return (
<section className="phones">
<Filters
data={this.state.data}
currentUrl={currentUrl} />
<Results
data={this.state.data} />
</section>
)
}
}
}
The Results Component will map through the data and render the list of phones.
In the Filters Component I then have a dropdown that allows user to sort the results by price value, and this sets the state too.
export default class Filters extends Component {
constructor(props){
super(props);
this.state = {
value: 0
}
this.onChange = this.onChange.bind(this);
}
onChange(e){
this.setState({value: e})
}
render() {
return (
<div>
<p>Price</p>
<select onChange={this.onChange}>
<option value='asc'>low to high</option>
<option value='desc'>high to low</option>
</select>
</div>
);
}
}
The problem I'm having is that I do not know how I can apply this filter to the component which renders the results, and what is the best way to go about this?
I have started reading in redux, but am confused if the phones data and filters should be in a store as it is temporary and will change if they go to another page i.e /phones/Samsung/Galaxy/
Any help is appreciated
Typically in React, a parent container will handle state for all sub-components - data and props flow in one direction, down through the component tree.
Redux offers a way to give containers access to a method for updating state.
This page from the Redux docs provide instructions for how to integrate with React.
Redux provides a single object that contains the entire application state. Then an additional NPM module, react-redux, provides two functions for "connecting" a component to global Redux state: mapStateToProps and mapDispatchToProps.
Using this approach, you can have your Filters container set a toggle in global state:
state = {
phones: [],
sortBy: 'price',
sortOrder: 'asc',
}
Or similar. You can then use mapStateToProps to gain access to the sort state slices, and mapDispatchToProps to "dispatch" actions that update the state.
The Redux docs are excellent, and written with simplicity and beginners in mind.
So there are a couple of ways to do this. You mentioned you're just beginning to look into Redux, and I'd encourage you to keep on that path. It will help you much in the world of application state management. That said, if you are doing this with just React:
The parent component is Phones, so write a helper method in Phones to keep track of the filter (make sure you set a default filter state for when the component first constructs or mounts like you did with make and model):
setFilter(filter) {
this.setState({filter});
}
Pass the filter from the Phones component state to both the Filters and Results components. Also pass the setFilter class method into the child Filters component (All this is done in your Phones component's render method):
return (
<section className="phones">
<Filters
filter={this.state.filter}
onChange={this.setFilter}
data={this.state.data}
currentUrl={currentUrl} />
<Results
data={this.state.data}
filter={this.state.filter}/>
</section>
)
Change your Filter component so that its onChange event is the setFilter handler you passed into it, and the value prop in the <select> component is the filter we passed in (2).
I'll leave 3 to you. I think you can figure it out :). Note that you now have access to the filter value as a prop in your results class and you can sort in that component.
I have started reading in redux, but am confused if the phones data and filters should be in a store as it is temporary and will change if they go to another page i.e /phones/Samsung/Galaxy/
Redux is a great solution to this problem. By externalising the state, both components can easily access the necessary values.
Your concern about the data changing should not be so, as this is an important and common situation for redux stores. Even the todo list example from their documentation implements a filter component similarly to what you are describing.
Another benefit is that if you use the redux-thunk middleware (or whichever async middleware you want), you can also move your fetch call out of the component so it becomes a little bit simpler.
There are plenty of examples floating around for this kind of setup so I won't bother writing any out for you now, but I'll update this answer for any specific requests in the comments.
Alternatively, you can use callbacks to pass data up and down between components.
This image from this blog has is a great illustration of how this can be achieved

What's the correct way of accessing input fields in React-Redux?

So I have this application which uses Redux to store some data. Then I have the Search component, which I originally wanted to be a plain dumb component. However, there is an input field whose value I would like to access and pass on to do something with it. However, I am not sure how to manage the value of this input field, that is, how to access it & where to store the value. I want to use Redux and be consistent, so that's my main problem. I have found the following solution (which works), but this does not look like Redux to me anymore? Am I violating any Redux specific rules with this?
On the other hand, how would I do it with Redux? With a reducer & action etc. just for this one input field in one component? This seems like a bit too much, but please enlighten me!
class Search extends React.Component{
constructor(props) {
super(props);
this.state = {
username: ""
};
this.handleUsernameChange = this.handleUsernameChange.bind(this);
}
handleUsernameChange(evt) {
console.log("Helo" + evt.target.value);
this.setState({
username: evt.target.value
});
}
onSubmit(e) {
e.preventDefault();
console.log("Hello" + e);
/* Do something... */
}
render() {
// Return JSX via render()
return (
<div className="">
<h1>Youtube Link</h1>
<input className="form-control" onChange={this.handleUsernameChange}></input>
<button className="btn btn-large btn-positive" onClick={this.onSubmit}>Download</button>
</div>
);
}
}
// Export Search
export default Search
"I want to use Redux and be consistent, so that's my main problem."
That's a valid reason to use Redux for this use case. But it is also fine to use combination of Redux and local component state in your application. I think this is a perfect example on when local component state is a good solution. I wrote a blog post on this topic. If you want, you can take a look here: http://blog.jakoblind.no/2017/02/08/is-using-a-mix-of-redux-state-and-react-local-component-state-ok/
On the other hand, how would I do it with Redux? With a reducer & action etc. just for this one input field in one component? This seems like a bit too much, but please enlighten me!
Yes, this is how you would do it with Redux. If you need the the username value anywhere else in your app, then this is the approach you should take.
If you don't want to write your own actions and reducer and everything for it, you could look into redux-form to handle it for you, but it might be a bit overkill if there is not much other input required from the user.
If you only need it when you hit that button right below it, then what you have is fine as you can just raise your action with the value from the components state, e.g,
onSubmit(e) {
e.preventDefault();
console.log("Hello" + e);
/* Do something... */
this.props.downloadTheThing(this.state.username)
}
where downloadTheThing is mapped in your mapDispatchToProps function when connecting the component (assuming you are using react-redux).
It is definitely fine to use local state in Redux.
From the code snippet you shared, you don't even need to use a local state.
constructor(props) {
...
this.username = '';
...
}
onSubmit() {
console.log('Hello ' + this.username);
}
render() {
return (
...
<input type="text" onChange={e => this.username = e.target.value} />
...
);
}
<input className="form-control" onChange={this.handleUsernameChange}></input>
that is your input field. first thing todo when you work on input in react is to set the value and then apply onChange. with value we will assign the state of the input to the state of the component so we will get single source of truth.
since you want to work on redux store, I assume you already have your redux store, reducers and actions set in your application. reducers hold the state and you send message to reducers via dispatch(). You access to dispatch() on this.props.dispatch() once you define connect() (). By default, a connected component receives props.dispatch()
import React from "react"; //anywhere you are using jsx, u should import this
import { connect } from "react-redux";
import setInputAction from "relative path" //actions are the messages that you send to reducers. reducers change the state based on the actions.
//this allows you access to state in Redux-store via the props.
const mapStateToProps = state => {
return {
reduxState: state
};
};
export default connect(mapStateToProps)(Search);
this code is kinda configuration of your component to communicate with the redux-store.
Now let's focus on input field. we have to set the value:
<input
type="text"
value={this.props.reduxState.input}// i assume you have `input:""` in the state. u can name anything you want.
onChange={e => {
this.props.dispatch(setInputAction(e.target.value));//we are sending e.target.value to the reducer via dispatch().
}}
/>
setInputAction is just an object that sent to reducer. Reducers are already defined what to do under certain actions. So you just send the message and reducer will change the state accordingly.

React - updating state during render produces errors

I'm new to React and am trying to update the state of a parent component from the child everytime an onChange action happens. The onchange action comes from an input box that when letters are typed it updates the state of searchInputVal with the value of what has been typed. I have a parent <App/> component with the following properties and states here:
updateSampleFilteredState(filteredSamples) {
this.setState({
samples: filteredSamples
});
},
getInitialState () {
return {
samples:allSamples,
searchInputVal:""
}}
I pass the properties and states down to a child component here:
updateNewSampleState(filteredSamples){
return (
this.props.updateSampleFilteredState(filteredSamples)
)
}
render() {
const filteredSamples = this.props.samples.filter(sample => {
return sample.sampleFamily.toLowerCase().indexOf(this.props.searchInputVal.toLowerCase()) !== -1;
});
this.updateNewSampleState(filteredSamples);
return <div className="samples-container-inner-styling">
{
filteredSamples.map((sample) => {
return (...
Before I added the line this.updateNewSampleState(filteredSamples); the child component would render out the filtering just fine but obviously not update the state of sample with the new filtered state. When I the line this.updateNewSampleState(filteredSamples); to execute the function in the component to set the new state I get a list of re-occuring errors that eventually make my app crash. The errors say something about an anti pattern. I'm not sure how else to update the state?
You should't be updating the state from the render function, and you are facing the reason why that's a bad way to do things. Every time you call the setState the component re-renders, so if you call it inside the render function it will be called again and so on... You should ask yourself why are you calling that function there. I guess you could just do it in the onChange function you are using for the input.
As already mentioned by #César, setting the state in the renderer doesn't make sense, since setting the state triggers a rerender of the component, so you basically get something like an infinite render loop.
Given that you are computing filteredSamples only from the props, you could compute that state in the constructor:
The constructor is the right place to initialize state.
However, note the following when deriving state from props in the constructor:
It's okay to initialize state based on props if you know what you're doing. [...]
Beware of this pattern, as it effectively "forks" the props and can lead to bugs. Instead of syncing props to state, you often want to lift the state up.
If you "fork" props by using them for state, you might also want to implement componentWillReceiveProps(nextProps) to keep the state up-to-date with them. But lifting state up is often easier and less bug-prone.

Categories

Resources