How to hide/display separate forms using buttons and redux? - javascript

I'm new to react and redux (and posting on stack overflow!).
I'd like to hide/display a redux-form based on a button choice.
I have two buttons: Option-A and Option-B.
I followed the redux tutorial exactly to have their onClick methods dispatch setVisibilityFilter(buttonprops.filter) through a container. See: FilterLink.js This works fine and updates the state's visibilityFilter with the corresponding option.
However, I'm stuck about how I should access the state's filter to hide/display different forms. I would like something similar to what formValueSelector does, but it isn't applicable for buttons (because they don't return values?)
This is my main component's code:
class MainForm extends Component {
render() {
const { error } = this.props
return (
<Grid.Column width={9}>
<Button.Group floated='right'>
<FilterLink filter={VisibilityFilters.SHOW_A}>A</FilterLink>
<Button.Or />
<FilterForm filter={VisibilityFilters.SHOW_B}>B</FilterLink>
</Button.Group>
/* If SHOW_A, display FORM_A, else if SHOW_B, display FORM_B */
</Grid.Column>
)
}}
I feel like just toying with the state directly now would waste the effort of implementing redux. I think I should be passing the value as a prop down to the child forms, but I'm confused how to do so, especially because I don't know how I would get that value without changing my onClick anyway, and onClick is already defined in FilterLink.js
There must be some way to access my state visibility filter to hide/display a form, just unsure how to get there. Thank you!

With connect, you can pass anything from the Redux Store to your component through its props.
So based on the link you posted, this should work:
import { connect } from 'react-redux'
class MainForm extends Component {
render() {
const { error, visibilityFilter } = this.props
return (
<Grid.Column width={9}>
<Button.Group floated='right'>
<FilterLink filter={VisibilityFilters.SHOW_A}>A</FilterLink>
<Button.Or />
<FilterForm filter={VisibilityFilters.SHOW_B}>B</FilterLink>
</Button.Group>
{visibilityFilter === VisibilityFilters.SHOW_A
? <FormA />
: <FormB />
}
</Grid.Column>
)
}}
const mapStateToProps = state => ({
visibilityFilter: state.visibilityFilter
})
export default connect(mapStateToProps)(MainForm)

Make sure you have connected the component you want to conditionally render things to the redux store.
import { connect } from 'react-redux'
...
const mapStateToProps = state => ({visibleFilter: state.visibilityFilter})
export default connect(mapStateToProps)(MainForm)
Then you can access this information in your connected component's props, e.g.
render() {
return {
{this.props.visibleFilter === VisibilityFilters.SHOW_A && (<FormA /> )}
{this.props.visibleFilter === VisibilityFilters.SHOW_B && (<FormB /> )}
}
}

Related

Passing props twice for a Higher Order Component?

I'm building a webpage and realized a common style shared by each component (same background, border, and title style). So I thought I should make an HOC which accepts the inner content of each component as well as a title, and returns an outer component which wraps this inner component and heading.
At first I ran into a lot of issues trying to get this to work, being new to React, but now it's finally working but I still don't understand how.
Here is my HOC
const BaseBlock = (WrappedComponent) => {
return class BaseBlock extends Component {
render () {
return (
<div className={styles['base-block']}>
<div className={styles['container']}>
<div className={styles['base-block-head']}>
{ this.props.title }
</div>
<div className={styles['base-block-body']}>
<WrappedComponent {...this.props} />
</div>
</div>
</div>
)
}
}
}
export default BaseBlock
This is the WrappedComponent:
const HighlightsBlock = (props) => {
return <ListsComponent items={props.items} />
}
export default BaseBlock(HighlightsBlock)
And this is the ListsComponent
const ListsComponent = (props) => {
if (props.items) {
return (
<ul className={styles['styled-list']}>
{props.items.map((item, idx) => {
return (
<li key={idx} className={styles['styled-list-item']}>{item}</li>
)
})}
</ul>
)
} else return (
<h3>No highlights</h3>
)
}
export default ListsComponent
And this is how I'm using the component in my app:
<HighlightsBlock items={this.getHighlights()} title='Highlights' />
Now, I can see the HighlightsBlock component receiving props twice (Once when I'm using it in my App with props, and once inside the HOC Baseblock as WrappedComponent ). If I remove props from either of these places it stops working. I don't understand how this is working.
When you render <HighlightsBlock items={this.getHighlights()} title='Highlights' /> you are actually rendering the component returned by HOC which in turn renders your actually HighlightsBlock component as <WrappedComponent {...this.props} />
You can think of HighlightsBlock component to be nested two level deep and hence you need to pass on the props to it, firstly as {...this.props} from within HOC and then receive it as props in functional component
This is because of this.getHighlights() in this line,
<HighlightsBlock items={this.getHighlights()} title='Highlights' />
Every time you pass props to child component this function is getting executed.
To solve this issue, maintain a state value in your parent component and set that value in getHighlights function like,
getHighlights(){
//you logic to get data
this.setState({items:data.items}); //considering `data` is object which has `items`
}
Now you can pass items like,
<HighlightsBlock items={this.state.items} title='Highlights' />

Modify render function of external React component (with no access)

I have to use a react component that I cannot modify. It's from an external source, due to changes. This could also be a component from a npm package that I import. This is what it looks like, a simple button:
class Button extends React.Component {
// ... more code above
render() {
const { onClick, disabled, children} = this.props;
return (
<button className={this.getClasses()} onClick={onClick} disabled={disabled}>
{this.props.symbol && <Icon symbol={this.props.symbol} />}
{children}
</button>
);
}
}
How can I add some functionality with no access to the file (I can create my own component that extends the button)? For example, I want a type prop in there. I thought I can just create a <ButtonExtend onClick={resetState} type="button />.
How can I do this? Ideally I would like to make this even more flexible, so I can also do: <ButtonExtend onClick={resetState} type="submit" name="extended button" />.
I would expect the html to render all the properties from <Button> with my additional html attributes. So I want to use the functionality of the original and my additional props. Or it this not even possible, to change the render method of another component, if the component doesn't make it possible?
Although public methods and properties of a component are accessible by refs (https://reactjs.org/docs/refs-and-the-dom.html) the pattern are you looking for is High Order Components (HOC, https://reactjs.org/docs/higher-order-components.html)
Unless a component was designed for customization, there is no straightforward way to do this.
Button is an example of badly designed component because it doesn't accept additional props. An issue and PR could be submitted to the repository in order to address original problem.
In extended component, this can be fixed by passing props from extended component.
Parent render result could be modified:
class ButtonExtend extends Button {
// ... more code above
render() {
const button = super.render();
const { symbol, children, ...props } = this.props;
return React.cloneElement(button, {
children: [
symbol && <Icon symbol={symbol} />,
...children
],
...props
});
}
If an element that needs to be modified is nested, this may become messy and result in unnecessarily created elements.
A cleaner way is to paste render in extended component and modify it:
class ButtonExtend extends Button {
// ... more code above
render() {
const { symbol, children, ...props } = this.props;
return (
<button className={this.getClasses()} {...props}/>
{symbol && <Icon symbol={symbol} />}
{children}
</button>
)
}
}
This way it can be used as
<ButtonExtend onClick={resetState} type="submit" name="extended button" />

Initialize state with dynamic key based on props in reactJS

How to initialize state with dynamic key based on props? The props is a data fetched from external source (async). So the props will change when the data is succesfully downloaded. Consider a component like this.
edit: I want to make the state dynamic because I want to generate a dialog (pop up) based on the item that is clicked. the DialogContainer is basically that. visible prop will make that dialog visible, while onHide prop will hide that dialog. I use react-md library.
class SomeComponent extends React.Component {
constructor() {
super();
this.state = {};
// the key and value will be dynamically generated, with a loop on the props
// something like:
for (const item of this.props.data) {
this.state[`dialog-visible-${this.props.item.id}`] = false}
}
}
show(id) {
this.setState({ [`dialog-visible-${id}`]: true });
}
hide(id) {
this.setState({ [`dialog-visible-${id}`]: false });
}
render() {
return (
<div>
{this.props.data.map((item) => {
return (
<div>
<div key={item.id} onClick={this.show(item.id)}>
<h2> Show Dialog on item-{item.id}</h2>
</div>
<DialogContainer
visible={this.state[`dialog-visible-${item.id}`]}
onHide={this.hide(item.id)}
>
<div>
<h1> A Dialog that will pop up </h1>
</div>
</DialogContainer>
</div>
);
})}
</div>
)
}
}
// the data is fetched by other component.
class OtherComponent extends React.Component {
componentDidMount() {
// fetchData come from redux container (mapDispatchToProps)
this.props.fetchData('https://someUrlToFetchJSONData/')
}
}
The data then is shared via Redux.
However, based on my understanding so far, state can be updated based on props with componentWillReceiveProps or the new getDerivedStateFromProps (not on the constructor as above). But, how to do that on either method?
The example here only explains when the state is initialized on the constructor, and call setState on either cWRP or gDSFP. But, I want the key value pair to be initialized dynamically.
Any help/hint will be greatly appreciated. Please do tell if my question is not clear enough.
import React from 'react';
import {connect} from 'react-redux';
import {yourAction} from '../your/action/path';
class YourClass extends React.Component {
state = {};
constructor(props){
super(props);
}
componentDidMount(){
this.props.yourAction()
}
render() {
const {data} = this.props; //your data state from redux is supplied as props.
return (
<div>
{!data ? '' : data.map(item => (
<div>{item}</div>
))}
</div>
)
}
}
function mapStateToProps(state) {
return{
data:state.data //state.data if that is how it is referred to in the redux. Make sure you apply the correct path of state within redux
}
}
export default connect(mapStateToProps, {yourAction})(YourClass)
If you do this, <div>{item}</div> will change as you change the data state. The idea is to just map the redux state to your class props - you don't have to map the props back to the state. The render() automatically listens to changes in props supplied by redux. However, if you do want to somehow know redux state change in events, you can add the following functions.
componentWillReceiveProps(newProps){
console.log(newProps)
}
getDerivedStateFromProps(nextProps, prevState){
console.log(nextProps);
console.log(prevState);
}

Display Component based on another component lifecycle

I have recently encountered an issue regarding the usage of one of my costum components. I have created a "Chargement" (Loading in French) Component for a project I am working on.
This component is a simple circular spinner with a dark background that when displayed, informs the user that an action is going on.
import React, {Fragment} from 'react';
import { CircularProgress } from 'material-ui/Progress';
import blue from 'material-ui/colors/blue';
import PropTypes from 'prop-types';
import { withStyles } from 'material-ui/styles';
import {bindActionCreators} from 'redux';
import {connect} from 'react-redux';
const styles = theme => ({
chargement: {
position: 'fixed',
left: '50%',
top: '50%',
zIndex: 1
}
});
class Chargement extends React.Component {
render () {
const { classes } = this.props;
if (this.props.chargement) {
return (
<Fragment>
<div className='loadingicon'>
<CircularProgress size={80} style={{ color: blue[500] }}/>
</div>
<div className='loadingBackground'/>
</Fragment>
);
} else {
return null;
}
}
}
const mapStateToProps = (state) => {
return {
chargement: state.App.chargement
};
};
const mapDispatchToProps = (dispatch) => {
return bindActionCreators({
}, dispatch);
};
Chargement.propTypes = {
classes: PropTypes.object.isRequired
};
let ChargementWrapped = withStyles(styles)(Chargement);
export default connect(mapStateToProps, mapDispatchToProps)(ChargementWrapped);
This component is displayed based on a boolean variable in my redux Store called "chargement".
It works like a charm whenever I am using it to make api call and load data. However, one of the components in my Web App takes quite a bit of time to render (1-2 seconds). This component renders a pretty big list of data with expansion panels. I tried to set my display variable based on the componentWillMount and componentDidMount functions.
class ListView extends React.Component {
componentWillMount () {
this.props.setChargement(true);
}
componentDidMount () {
this.props.setChargement(false);
}
However with this particular case the "chargement" component never displays.
I also tried to create a "Wrapper Component" in case the issue came from my "chargement" component being somewhat related to the re-rendered component as a children. :
export default class AppWrapper extends React.Component {
render () {
return (
<Fragment>
<Reboot />
<EnTete />
<Chargement />
<App />
</Fragment>
);
}
}
The "App " component is the one that takes a few seconds to render and that I am trying to implement my "chargement" component for. I am pretty sure this as to do with the component lifecycle but everything I tried so far failed.
My current stack is : React with Redux and MaterialUi
What am I missing ?
Thanks for your help!
Ps: You might want to check the explanation and precision I added on the main answer comments as they provide further context.
Not sure if I understood correctly, but I think the problem is simply your API call takes more time than your component mounting cycle, which is totally normal. You can solve the problem by rearranging a bit the places where to put the IO.
Assuming you are making the API call from AppWrapper, dispatch the Redux action in componentDidMount i.e. fetchListItems(). When the API call resolves, the reducer should change its internal loading value from true to false. Then, AppWrapper will receive chargement as a prop and its value will be false. Therefore, you should check what this value is in AppWrapper's render method. If the prop is true, you render the Chargement component or else, render ListView.
Also, try always to decouple the IO from the view. It's quite likely that you'll need to reuse Chargement in other situations, right? Then, make it a simple, generic component by just rendering the view. Otherwise, if you need to reuse the component, it will be coupled to one endpoint already. For this, you can use a Stateless Functional Component as follows:
const Chargement = () =>
<Fragment>
<div className='loadingicon'>
<CircularProgress size={80} style={{ color: blue[500] }}/>
</div>
<div className='loadingBackground'/>
</Fragment>
I found a way to fix my issue that does not involve the use of the "chargement" component like I had initially planned. The issue revolved around the usage of Expansion Panels from the Material-Ui-Next librairy.
The solution I found is the following :
Instead of trying to show a Loading component while my list rendered, I reduced the render time of the list by not rendering the ExpansionDetail Component unless the user clicked to expand it.
This way, the list renders well under 0.2 seconds on any devices I've tested. I set the state to collapsed: false on every panel inside the constructor.
class ListItem extends React.Component {
constructor (props) {
super(props);
this.state = {
collapsed: false
};
this.managePanelState = this.managePanelState.bind(this);
}
managePanelState () {
if (this.state.collapsed) {
this.setState({collapsed: false});
} else {
this.setState({collapsed: true});
}
}
Then I use the onChange event of the expansion panel to switch the state between collapsed and not on every ListItemDetail element.
<ExpansionPanel onChange={() => this.managePanelState()}>
I guess sometimes the solution isn't where you had initially planned.
Thanks to everyone who took time to look into my problem!

If I need to get properties for a component from an API should I do that before the component loads?

Say I have a comp that is inside of a Scene (react-native-router-flux). It lets people choose their favorite fruits.
import React, {Component} from 'react';
import {View, Text, StyleSheet} from 'react-native';
import {MKCheckbox} from 'react-native-material-kit';
var styles = StyleSheet.create({});
export default class PickAFruit extends Component {
render() {
console.log(this.props.fruits);
return (
<View>
{
this.props.fruits.map((x)=> {
return (
<View key={x.key}>
<Text>{x.key}</Text>
<MKCheckbox checked={this.props.checked} key={x.key} onCheckedChange={(e) => {
this.props.update(e, '' + x.key)
}}/>
</View>
)
})
}
</View>
)
}
}
In the parent comp I'm loading the list of fruits from an API in the didMount:
componentDidMount() {
ApiInst.getFruits().then((fruits) => {
console.log(fruits);
console.log(this.props.fruits);
this.props.fruits = fruits;
});
}
I'm also setting a default fruits array in the parent class. It seems like the properties won't load via the API though, the list of fruit is always the "unknown" value, never the new values. Do I need to load the list of fruits before the Profile scene is loaded? When is the correct time to set properties for a component if they will come from an API?
setState seems like the easy answer but these settings don't "feel" like state, they feel like properties that would be injected at build-time (i.e. when the component is built, not the app). Is this a distinction without a real difference?
You can't modify props. Props are passed from parent to child component, and only the parent can change them.
Use setState instead:
this.setState({fruits: fruits});
And access them from state:
<PickAFruit fruits={this.state.fruits} />
You may also want to set a default state in the component constructor:
constructor(props) {
super(this);
this.state = {fruits: null};
}
this.props.fruits = fruits;
won't effect child component, and to be honest - I'm not sure it will work at all. If you don't want to use flux architecture I think the best solution is to update parent's state on componentDidMount() and pass it as props to child component:
componentDidMount() {
ApiInst.getFruits().then((fruits) => {
this.setState({fruits: fruits});
});
}
render() {
return (
<PickAFruit fruits={this.state.fruits} />
);
}
Every state change will invokre render() method, so after API call PickAFruit component will be rerendered, with fruits passed as a props.

Categories