How can I call a function _toggleDropdown or _onWindowClick from another class and file?
DropDown.js
export default class DropDown extends React.Component {
_toggleDropdown(e) {
e.preventDefault()
this.setState({
isActive: !this.state.isActive
})
}
_onWindowClick(event) {
const dropdownElement = findDOMNode(this)
if (event.target !== dropdownElement && !dropdownElement.contains(event.target) && this.state.isActive) {
this.setState({
isActive: false
})
}
}
}
Header.js
<a onClick={what???}>test</a>
If DropDown component is rendered within Header you can use refs to get dropdown instance and call its methods.
Header.js
render() {
return (<div>
<DropDown ref="dd"/>
<a onClick={e => this.refs.dd._toggleDropdown(e)}>Toggle</a>
</div>)
}
If they are totally unrelated you'd better switch from local state to some global state management solution like flux or redux. And make dropdown state to be a part of global application state that any component could change by dispatching corresponding action.
Well the only way to do this kind of thing is by passing the function as props to the Header component. I am not sure about your structures to make a clean snippet with the results. Maybe the design is not being clear enough to make it easy for you.
Related
There are numerous guides how a state can be stored in the context and how this state can be changed from any of the components. These examples store the state and an update function in the context.
But is it also possible to store the state somewhere else and store only the update function in the context?
The motivation of this question is that storing the state together with an updater function can be seen as a redundancy, which could be avoided.
I tried already many things and read much about this. But it seems not to work for me. But I don't understand why not. It should be possible that one component provides a setter function in the context and another component just calls this setter function.
I am aware, that this will only work if there is exactly one instance of the component, that provided the setter function.
Thanks to the help of one comment I found the answer.
The context in the following example is a function, which is visible in all components. Then in the component App there is a state and the setter. That setter is passed to the context. Once the setter is defined, it can be used by other components, such as the component GiveZag.
The good thing with this design is that the state and the way how it is updates is kept locally to where it belongs. It is often helpful to keep things as local as possible. Nothing of these details is revealed, except that there is a function, that can be called.
import React from 'react';
const ZigZagContext = React.createContext(
(newValue) => {console.log(newValue)}
);
class GiveZag extends React.Component {
render() {
return (
<ZigZagContext.Consumer>
{ setZigZag => (
<button onClick={() => setZigZag("zag")}>make zag</button>
)}
</ZigZagContext.Consumer>
);
}
}
class App extends React.Component {
setZigZag(newValue) {
this.setState({
zigzag : newValue
})
};
state = {
zigzag: "zig",
setZigZag: (newValue) => {this.setZigZag(newValue);}
};
render() {
return (
<ZigZagContext.Provider value={this.state.setZigZag}>
<h2>Current: { this.state.zigzag}</h2>
<p>Click button to change to zag</p>
<div><GiveZag /></div>
</ZigZagContext.Provider>
);
}
}
export default App;
Using the context is not always the best solution. This can be criticised in this case. The context enforces an unidirectional data flow.
Indeed the same can be achieved without the context mechanism. A solution that is simpler is the following code. This is not obvious and cannot be found so often in a web search. But it becomes clear when we keep in mind, that we have all features of JavaScript available. There is no need of using the context mechanism if not needed.
import React from 'react';
let ZigZagUpdater = (newValue) => {console.log(newValue)};
function GiveZag(props){
return (
<button onClick={() => ZigZagUpdater("zag")}>make zag</button>
);
}
class App extends React.Component {
setZigZag(newValue) {
this.setState({
zigzag : newValue
})
};
state = {
zigzag: "zig"
};
componentDidMount(){
ZigZagUpdater = (newValue) => {this.setZigZag(newValue);}
}
render() {
return (
<para>
<h2>Current: { this.state.zigzag}</h2>
<p>Click button to change to zag</p>
<div><GiveZag /></div>
</para>
);
}
}
export default App;
Lets say I have a component defined like this -
// actioncomponent.js
import React from 'react';
class ActionComponent extends React.Component {
state = {
isAction: false;
}
doAction = () => {
this.setState({isAction: true})
}
render () {
return (
<div>
Some render stuff..
</div>
)
}
}
export default ActionComponent
From another completely different file I want to set the state for the first component without rendering it in the new file so I need not use refs or props.
// newfile.js
import ActionComponent from './actioncomponent.js'
ActionComponent.doAction()
I'm aware the doAction can't be exported and calling it static doesn't have access to state either. How do I achieve something like this?
In React ecosystem you probably don't need this.
You can pass this method to a child component:
class ActionComponent extends React.Component {
state = {
isAction: false
}
doAction = () => {
this.setState({isAction: true})
}
render () {
return (
<div>
<Child doAction={this.doAction} />
</div>
)
}
}
And then in a Child component you can fire this action
// ...
render() {
<button onClick={() => props.doAction()}>Test</button>
}
If you need to fire action on parent, instead of child you might want to structure your state on upper level, or lift state up.
You can also achieve similar goal without drilling props, but you'll need some state management tool, e.g. Redux or in some cases Context API would be a great fit.
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);
}
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!
I am creating a custom React Component for a Dropdown menu. The structure I had in mind to use this component was something among the lines of
<Dropdown>
<DropdownTrigger> // The button that triggers the dropdown to open or close
open me
</DropdownTrigger>
<DropdownButton> // A button inside the dropdown
Option 1
</DropdownButton>
</Dropdown>
The way I wanted to implement this, is by having the Dropdown component check if there is a DropdownTrigger component in its children, and if there is, clone the given DropdownTrigger component using React.cloneElement and pass an onClick property, that calls a state update on the Dropdown component.
Small code snippet of Dropdown.js
import DropdownTrigger from './DropdownTrigger';
_toggleDropdown () {
this.setState({
isOpen: !this.state.isOpen
});
}
_renderTriggerButton () {
const triggerChild = this.props.children.find(child => child.type === DropdownTrigger);
return React.cloneElement(triggerChild, {
...triggerChild.props,
onClick: () => this._toggleDropdown()
});
}
Is this a correct approach and if so, what would be the cleanest/nicest possible way to validate the Dropdown component has a DropdownTrigger as child. As this means a developer always has to include a DropdownTrigger as child of the Dropdown component, I would like to have a nice way to tell developer they should pass a <TriggerButton> component as child of the dropdown.
I'm also open for suggestions about changes in the structure. Thanks!
Use React.cloneElement to pass additional properties to child components. In combination with instanceof you can do exactly what you want.
It could be something along the lines...
import React from 'react';
import DropdownTrigger from './DropdownTrigger';
class DropDown extends React.Component {
constructor(props) {
super(props);
}
...
render() {
return (
<div>
{React.Children.map(this.props.children, child => {
const additionalProps = {}
// add additional props if it's the DropdownTrigger
if (child instanceof DropdownTrigger) {
additionalProps.toggleDropDown = () => { } ...
}
return React.cloneElement(child, additionalProps);
})}
</div>
);
}
}
export default DropDown;