I'm new to React, and I made the mistake of thinking Redux is essential to writing any react app. I wrote all my components to be stateless, but I'm not really seeing the benefit:
It just feels really awkward when I have nested components catering to nested objects. At every component level, I have to pass in the all the previous indexes so the child component can update the correct value. Also, I can't even use the data passed into the FlatList because it won't be Redux aware; I have to use the data from the store.
Is this how stateless components are supposed to be written or am I doing something horribly wrong?
Example:
State structure
{
appetizers: [...],
desserts: [
...array of deserts,
{ fruits: [
... array of fruits,
{ name: 'apple',
calories: 100,
}]
}
]
}
Child class
class Child extends Component {
update() {
const props = this.props;
props.dispatch(actions.eat(props.food, props.dessertIndex, props.fruitIndex));
}
render(){
const props = this.props;
return (
<TouchableHighlight onPress={() => this.update()}>
<Text>{props.desserts[props.dessertIndex].fruits[props.fruitIndex]}</Text>
</TouchableHighlight>
)
}
}
const mapStateToProps = state => ({ desserts: state.desserts })
export default connect(mapStateToProps)(Child);
parent class
class Parent extends Component {
render(){
return (
<FlatList
...props
data={this.desserts[this.props.dessertIndex].fruits}
renderItem={({item, index}) => (<Child food={item}, dessertIndex={this.parent}, fruitIndex={index}>)} />
)
}
}
const mapStateToProps = state => ({ desserts: state.desserts })
export default connect(mapStateToProps)(Parent);
grandparent class
class GrandParent extends Components {
render() {
return (
{ this.desserts.map((item, index) => {
return <Child data={item} dessertIndex={index} />
}) }
)
}
}
const mapStateToProps = state => ({ desserts: state.desserts })
export default connect(mapStateToProps)(GrandParent)
A few thoughts:
First, Redux is not essential to building a React app. However, there are very good reasons to use Redux with React - I talked about a few of them in a post on why using Redux and React together is a good idea.
Second, I've seen a number of people say that you "should keep absolutely all your state in Redux, and only use functional components". I completely disagree with that. Per the Redux FAQ entry on using Redux state vs React component state, it's up to you as a developer to decide what state lives where. In addition, per this Dan Abramov tweet, it's totally fine to use class components if you need state or lifecycle methods.
Third, it looks like your current Redux state structure is storing very nested data. You can do that, but it's not the recommended approach. Instead, you are encouraged to keep your state flattened and normalized as much as possible, which simplifies lookups like the ones you're dealing with. See the Redux docs section on Structuring Reducers - Normalizing State Shape for examples.
Fourth, the best pattern for good Redux performance and simpler props management is to have many components connected to the Redux store, especially for things like list items. You can pass in item IDs and other values as props to the connected component, and use those values in its mapState function to extract only the item that one specific component instance needs. I've got examples of this in my post Practical Redux, Part 6: Connected Lists, Forms, and Performance, and more articles on the topic in the Redux Performance section of my React/Redux links list.
Hopefully that points you more in the right direction. If you've got more questions, please come drop by the Reactiflux chat channels on Discord and ask! The invite link is at https://www.reactiflux.com .
Related
I'm currently developing an app that uses React in some parts of its UI to implement some complex feature that are very interactive. Some of these parts of the app, that are in fact React components, have become very complex in size.
This has led me to organize the code by dividing them in multiple subcomponents (and those in their own subcomponents, and so forth). The main problem is that some of the grandchildren of the main component might have the need to modify the state of another grandchildren that they're not related to. To do so, I end up having to have the state in the common parent and passing a lot of event handlers (methods of the parent component) to their children, and those children would need to pass it to their grandchildren.
As you can imagine, this is becoming some kind of a mess.
// MyComponent.js
class MyComponent extends React.Component {
constructor(props) {
super(props);
this.state = {
list: [1, 2, 3, 4],
selected: '',
}
this.add = this.add.bind(this)
this.handleChange = this.handleChange.bind(this)
}
add() {
const newNumber = this.state.list[this.state.list.length - 1] + 1,
list = [...this.state.list, newNumber]
this.setState({list})
}
handleChange({target}) {
this.setState({
selected: target.value,
})
}
render() {
return (
<>
<List items={this.state.list} selected={this.state.selected} />
<Button onClick={this.add} />
<input type="text" value={this.state.selected} onChange={this.handleChange} />
</>
)
}
}
// Button.js
class Button extends React.Component {
render() {
return (
<button onClick={this.props.onClick}>Click me!</button>
);
}
}
// List.js
class List extends React.Component {
constructor(props) {
super(props)
this.refs = props.items.map(_ => React.createRef())
}
render() {
return (
<ul>
{this.props.items.map((item, key) =>
(<li ref={this.ref[key]} key={key}>{item}</li>)
)}
</ul>
);
}
}
In the previous dummy code you can see how I need to define the add() method in the MyCompoent component so that an action that happens in the Button component can modify what is being shown in List. Even tho this might seem like the obvious way to do it, my component has a big component tree, and a lot of methods, and most of then are lost in the tree, passing from parent to child until it reaches the component that should be expected.
I have done some research on the internet and it turns out this is a very common problem. In most sites, using Redux or other state management library is recommended. However, all the tutorials and guides I've seen that implement Redux with React seem to assume you're only using React to build your app, in Single Page Application sort of way. This is not my case.
Is there any way to share the state of a component to avoid this kind of problem? Is there, maybe, a way to use Redux multiple times for multiple components in the same app, where one store saves only the state for MyComponent and can be accessed by either List or any of its possible children?
Redux doesn't require your entire site to be in React. It implements a higher-level component that you can use with any React components even if they are embedded in another site.
You can look at React hooks to solve similar problems. Specifically, check out useContext() and useState().
You've used a lifting state up pattern in react in your example.
It's quite common you good approach but when you app is growing you need to pass all bunch of props throu the tree of components. It's difficult to maintain.
In this case you need to check out redux with separated store or useContext() hook.
Note: I'm using React Native but this applies to Reactjs also. I only use stateless functional components by design. I'm wanting to learn and use functional programming as much as possible. Does this essentially mean I can never use connect() from react-redux? Should I be adding more container components to my app design?
Here is an example of my components:
export const Height = (props, { store }) => {
const state = store.getState()
return (
<View style={formStyles.container}>
<DimenInput
value={state.get('height').get('height1').toString()}
onChangeText={text => store.dispatch(updateHeight(text, 1))}
/>
<Text style={formStyles.text}>{'&'}</Text>
<DimenInput
value={state.get('height').get('height2').toString()}
onChangeText={text => store.dispatch(updateHeight(text, 2))}
/>
</View>
)
}
Height.contextTypes = {
store: React.PropTypes.object
}
I also want to learn all common redux and react techniques that I will see in the industry so I wanted to learn mapStateToProps() and connect(). I started to try and create these functions for my component above:
const mapStateToProps = (state) => {
return state.get('height').get('height1').toString()
}
const mapDispatchToProps = (dispatch) => {
return {
onHeightChanged: (text) => {dispatch(updateHeight(text, 1))}
}
}
Before realising that I cannot use those functions on this stateless component. I have to use them on a container component that would essentially hold this component but I don't really have one, the container of this component is:
export const Volcalc = () => {
return (
<View style={styles.container}>
<Text style={styles.text}>HEIGHT</Text>
<Height/>
<Text style={styles.text}>WIDTH</Text>
<Width/>
</View>
)
}
And then the container of that is just:
const App = () => {
return (
<Provider store={store}>
<Volcalc/>
</Provider>
)
}
The app I'm making is a single form - so it is tiny. At this stage I cannot see any situation where I would HAVE to use a container component. In a larger app would I be forced to use container components and then I will get the chance to use connect()? Which technique is better out of using only stateless, and using a container created with connect(), if it could be done either way?
Edit: this egghead lesson does something in between where they have a stateless component AddTodo that uses dispatch from the store and they call connect() on it. Possibly the scenario which would fit my use case for Height although I will need to keep passing it the full store since it uses store.get and store.dispatch - unless I pass it the store value for height instead of the full store.
By using connect on a container component you are keeping your app functional. Your data flows down to children. Your state is immutable.
Container components are useful in regards to connect because you're isolating your call to the store and, again, letting data flow down to child components. It's just a good way to structure your app and reduces the number of points the store is called in your app. It makes development much easier to manage and presents fewer opportunities to create a mess.
You don't have to use connect(), but your components will then become more tightly coupled with redux. If you have Height container and you can remove those redux store code from Height component and make it reusable easily on other non-redux project.
Using class as object-oriented pattern and some functional oriented patterns all mixed together makes a beautiful result. It's perfect if you want to practice your functional programming skills, but sadly, if you do so for a more serious application, you will loose a lot of benefits from react and redux. Things like state, your application will be hard to update. Your code will be coupled. and close to modifications. In a bigger application, you will find it frustrating developing it. I took the exact same path not long ago and I'm enjoying react and redux way more mixing both. This is my opinion. If you want to use connect, component state and most of the advantages of react and redux with keeping it fully functional, you can use React.createClass(), this is a function receiving an object with other function in it. Like instead of initializing the component state in the class constructor, you will use a function, getInitialState in this case. Containers are very useful to get reusable components. Another problem is passing redux props down to some components. With a small app, it's easy to pass those props not more than 3 level deep, but in a big app, it's not. Imagine, each time your redux state change, your whole app re-render and to avoid losing your data, you will have store those data in your redux state. You redux store will be too big and will contain useless data and data that shouldn't be there.
To sum up, that's perfectly fine to do a small app in full functional mode, but for a bigger app, you will loose to much react and redux benefits, making your app hard to modify and develop.
It is good to see some other peoples thoughts on this because I'm coming in new. But i gather from the other answers on this question that there are some performance optimisations that connect() takes care of and also it is good to not pass the whole store to a component because the react advanced context feature is unstable and also it couples the component to redux when using the redux store in the component.
I have gone over the tutorials some more and realised I can use connect() on my stateless component giving the benefits above.
Code:
const mapStateToProps = (state) => {
return {
height1: state.get('height').get('height1').toString(),
height2: state.get('height').get('height2').toString()
}
}
const mapDispatchToProps = (dispatch) => {
return {
updateHeight: (text, number) => {
dispatch(updateHeight(text, number))
}
}
}
let Height = (props) => {
return (
<View style={formStyles.container}>
<DimenInput
value={props.height1}
onChangeText={text => props.updateHeight(text, 1)}
/>
<Text style={formStyles.text}>{'&'}</Text>
<DimenInput
value={props.height2}
onChangeText={text => props.updateHeight(text, 2)}
/>
</View>
)
}
Height.propTypes = {
height1: React.PropTypes.string.isRequired,
height2: React.PropTypes.string.isRequired,
updateHeight: React.PropTypes.func.isRequired
}
Height = connect(
mapStateToProps,
mapDispatchToProps
)(Height)
I've started learning React without Redux or Flux and have been hearing a lot about Redux and how it seems to be the favourable pattern to use for managing state going forward. My understanding of it is that the entire state of the App lives in the store which I believe is at the top of the React tree. The various child components then 'subscribe' to various states that are relevant to them.
This is somewhat confusing for me as I thought the core structure of React is already setup in this way? Ie if my component has a certain state then to pass it down to its child components in order to use if further down the React tree I would need to add in this.state.example or this.props.example to a component. To me with this approach i'm 'subscribing' the component in a way as well..
Apologies if this is not the right place for questions like this but if someone could tell me what i'm missing here or the added benefit of Redux that would be very helpful!
You are on the right track on the subscribing portion, but what makes Redux wonderful and many other Flux like state management patterns is that you don't have to pass properties down the child chain just so you could update a childs component like so (you could if you wanted to, but not needed):
function Parent() {
return <ChildOne color="red" />
}
function ChildOne(props) {
return <ChildTwo color={props.color} />
}
function ChildTwo(props) {
return <h1>The Color was: {props.color}</h1>
}
It allows you to "dispatch" (a redux/flux term) an action to the state store to update a property on whichever object a component may be subscribed to.
A helpful library for that "connection" is react-redux. It has many capabilities, but the main that I see is connect which is a higher ordered component (HOC) that "wraps" your component with more logic including the part of the redux store that you want to subscribe to.
So the above could be:
export class Parent extends React.Component {
componentDidMount() {
this.props.dispatch(changeColor('red'));
}
render() {
return <ChildOne />
}
}
export default connect((state) => ({ //This property is the redux store
parent: state.parent,
}))(Parent) //higher order component that wraps the component with redux functionality
function ChildOne(){
return (
<ChildTwo />
);
}
export function ChildTwo(props) { //will have childTwo bound in props object
return (
<h1>The Color is: {props.childTwo.color}
);
}
export default connect((state) => ({ //This property is the redux store
childTwo: state.childTwo,
}))
Where the biggest difference is that you didn't have to pass the color from Parent down 2 levels to ChildTwo because it was "subscribed" to the childTwo object within the redux store and you connected that bit of state to the component so any change to the store will trigger the component to rerender from the state change.
The passing of properties and using Redux will make more sense with this medium post of Presentation and Container components, where passing of properties makes sense as you are only going down one child layer deep and the container component is handling logic such as ajax requests, or dispatches to parts of the redux store, etc.
Say I have a top most smart component called Forecast that looks like this:
function mapStateToProps(state) {
return {
dates: state.getIn(['forecast', 'dates']),
isFetching: state.getIn(['forecast', 'isFetching'])
};
}
export default connect(mapStateToProps, {
fetchForecast
})(Forecast));
Which wraps a Forecast component like this:
import { getSummary, getDayForecast } from '../selectors/selectors';
export default class Forecast extends Component {
render() {
const { dates, isFetching } = this.props;
return (
<div className="row">
{dates.map(date => (
<Weather
key={date}
date={date}
getSummary={getSummary}
getDayForecast={getDayForecast}
/>
))}
</div>
);
}
};
Here I am passing 2 selectors as props into a Weather component. The selectors look like this:
import { createSelector } from 'reselect';
import moment from 'moment';
import { fromJS } from 'immutable';
const getDay = (state, key) => state.getIn(['forecast', 'forecast']).find(x => x.get('id') === key);
export const getSummary = createSelector(
[getDay],
(day => {
const firstPeriod = day.get('periods').first();
return fromJS({
date: day.get('date'),
outlook: firstPeriod.get('outlook'),
icon: firstPeriod.get('icon')
});
})
);
export const getDayForecast = createSelector(
[getDay],
(day) => day.get('periods').map(period => fromJS({id: period.get('id') }))
);
I don't have to pass these selectors down as props, I could easily just reference them in the weather component but I am confused as to how I would use these selectors in the Weather component as the Weather component is also dumb and won't have any reference to state. I only want 1 container or smart component at the top which the child components call or get props passed down.
The only way I can see of making this work is to have an intermediatary WeatherContainer component that looks something like this:
import React, { Component, PropTypes } from 'react';
import { connect } from 'react-redux';
import Weather from '../components/Weather';
import { getSummary, getDayForecast } from '../selectors/selectors';
function mapStateToProps(state, ownProps) {
return {
summary: getSummary(state, ownProps.date),
detail: getDayForecast(state, ownProps.date)
};
}
export default(connect(mapStateToProps,{}))(Weather);
And I would call like this:
{dates.map(date => (
<WeatherContainer
key={date}
date={date}
getSummary={getSummary}
getDayForecast={getDayForecast}
/>
))}
This seems completely wrong to have to create a container component like this.
How can I make use of selectors in dumb components or how can I pass them down as props baring in mind that they also need reference to the state?
In your WeatherContainer mapStateToProps you use your selectors but you're still passing them down as props. This is not necessary.
Besides that, you should know that creating your container WeatherContainer is the right way to go about things. You should never give a selector to a component. They should always be used in mapStateToProps. React-Redux will reevaluate this when state changes and will tell React to update your components whenever the result is different. This is a very important point. If you just grab the state inside a component, whether using a selector or not, then React-Redux doesn't know you're using this data and won't we able to tell React to rerender when this data changes.
Now, a lot of people are confused on this matter. There are dumb components, which just display stuff, and container components, which do stuff, like make API calls or implement functionality of sorts. But when you take a dumb component and connect it to Redux, then this doesn't make for a smart or container component. It still only displays stuff. Even if you use mapDispatchToProps to feed it some event listeners, this still doesn't really make the component smart. It could become smart if it contains significant code in mapStateToProps or mapDispatchToProps I guess. But such is life. The line between these things is just blurry.
The Redux Way is to connect everything that needs data. You can certainly pass data down to children, just as in plain React, but you create a more performant app by connecting components. Still, it's up to you to decide. But it is still important that anywhere you grab data from the store, it should be put inside a mapStateToProps so React-Redux can keep an eye on the data. You can safely pass it from a parent to a child as long as the data came from mapStateToProps.
This means passing selectors to children is a no-no. Also, where's the child going to get the state to pass as a parameter to the selectors? It doesn't work well so it's not a good idea. Note that whenever you connect a component, you're not creating an entirely new component. Is just a simple wrapper. It should contain very little code in very few lines. This should not give you pause. Just go for it. connect those components.
I should also mention that you can connect your Weather component directly inside the weather.js file. Unless you're going to reuse it, there's not much need to keep the unconnected component around. For testing you can export the unconnected component with a named export. If later on you decide you need to reuse theWeather component, you can always easily separate the component and the connect call into separate files.
I have a listview component which consists of a number of child listitem components.
Each child listitem have a showSubMenu boolean state, which display a few extra buttons next to the list item.
This state should update in response to a user event, say, a click on the component DOM node.
childcomponent:
_handleClick() {
... mutate state
this.props.onClick() // call the onClick handler provided by the parent to update the state in parent
}
However, it feels somewhat wrong to update state like, as it mutates state in different places.
The other way i figured i could accomplish it was to call the this.props.onClick directly, and move the child state into the parent as a prop instead, and then do change the state there, and trickle it down as props.
Which, if any, of these approaches is idiomatic or preferable?
First of all, I think that the question's title doesn't describe very well what's your doubt. Is more an issue about where the state should go.
The theory of React says that you should put your state in the higher component that you can find for being the single source of truth for a set of components.
For each piece of state in your application:
Identify every component that renders something based on that state.
Find a common owner component (a single component above all the
components that need the state in the hierarchy).
Either the common
owner or another component higher up in the hierarchy should own the
state.
If you can't find a component where it makes sense to own the
state, create a new component simply for holding the state and add it
somewhere in the hierarchy above the common owner component.
However, a Software Engineer at Facebook said:
We started with large top level components which pull all the data
needed for their children, and pass it down through props. This leads
to a lot of cruft and irrelevant code in the intermediate components.
What we settled on, for the most part, is components declaring and
fetching the data they need themselves...
Sure, is talking about data fetched from stores but what im traying to say is that in some cases the theory is not the best option.
In this case i would say that the showSubMenu state only have sense for the list item to show a couple of buttons so its a good option put that state in the child component. I say is a good option because is a simple solution for a simple problem, the other option that you propose means having something like this:
var GroceryList = React.createClass({
handleClick: function(i) {
console.log('You clicked: ' + this.props.items[i]);
},
render: function() {
return (
<div>
{this.props.items.map(function(item, i) {
return (
<div onClick={this.handleClick.bind(this, i)} key={i}>{item} </div>
);
}, this)}
</div>
);
}
});
If, in a future, the list view has to get acknowledge of that state to show something for example, the state should be in the parent component.
However, i think it's a thin line and you can do wathever makes sense in your specific case, I have a very similar case in my app and it's a simple case so i put the state in the child. Tomorrow maybe i must change it and put the state in his parent.
With many components depending on same state and its mutation you will encounter two issues.
They are placed in component tree so far away that your state will have to be stored in a parent component very high up in the render tree.
Placing the state very high far away from children components you will have to pass them down through many components that should not be aware of this state.
THERE ARE TWO SOLUTIONS FOR THIS ISSUE!
Use React.createContext and user context provider to pass the data to child elements.
Use redux, and react-redux libraries to save your state in store and connect it to different components in your app. For your information react-redux library uses React.createContext methods under the hood.
EXAMPLES:
Create Context
const ThemeContext = React.createContext('light');
class App extends React.Component {
render() {
// Use a Provider to pass the current theme to the tree below.
// Any component can read it, no matter how deep it is.
// In this example, we're passing "dark" as the current value.
return (
<ThemeContext.Provider value="dark">
<Toolbar />
</ThemeContext.Provider>
);
}
class ThemedButton extends React.Component {
// Assign a contextType to read the current theme context.
// React will find the closest theme Provider above and use its value.
// In this example, the current theme is "dark".
static contextType = ThemeContext;
render() {
return <Button theme={this.context} />;
}
}
}
// A component in the middle doesn't have to
// pass the theme down explicitly anymore.
function Toolbar() {
return (
<div>
<ThemedButton />
</div>
);
}
class ThemedButton extends React.Component {
// Assign a contextType to read the current theme context.
// React will find the closest theme Provider above and use its value.
// In this example, the current theme is "dark".
static contextType = ThemeContext;
render() {
return <Button theme={this.context} />;
}
}
REDUX AND REACT-REDUX
import { connect } from 'react-redux'
const App = props => {
return <div>{props.user}</div>
}
const mapStateToProps = state => {
return state
}
export default connect(mapStateToProps)(App)
For more information about redux and react-redux check out this link:
https://redux.js.org/recipes/writing-tests#connected-components