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;
Related
I started learning React approx. month ago and I'm very confused about the concept because its still something new to me(compared to my previous work in C++ and C).
To quickly summarize I would like to know what is React's equivalent of C++ return form a function. How would I return value(or values) from a function(in my case class functions/states) and use it in my other components.
I have made an simple script that changes background to simulate RGB light on mouse and I made it so the HSL color mode is applied to the background of the component. I would like to use this on multiple components,icons, etc on my page but it feels like there is a better way than importing all functions in three files making the work triple than requiered.
import React, { Component } from 'react'
import './colorStrip.scss'
class ColorStrip extends Component {
constructor(props) {
super(props)
this.colorHue=10;
this.colorSaturation=100;
this.colorLightness=50;
this.state = {
color:"hsl(0,100%,50%)"
}
this.changeColor(1);
}
changeColor = (speed) => {
this.colorHue+=10*speed;
if(this.colorHue>=360)
this.colorHue=0;
this.setState({
color : "hsl("+this.colorHue+","+this.colorSaturation+"%,"+this.colorLightness+"%)"
})
setTimeout(() => {this.changeColor(speed)},75)
}
render() {
return (
<svg style={{backgroundColor:this.state.color}} className="strip">
</svg>
)
}
}
export default ColorStrip
So I would like to use this.state.color(or this.state.colorHue or any state) in three other SVG components on my page.
I really looked some of the other answers but they were quite complex and requiered multiple returns which was confusing.
There are a couple different options you can use to achieve this.
One would be to move your function that calculates the colour to a higher level component (so one of the parent components), that has the child components you want to pass this state to, and then pass your state down through component props.
class parent extends component {
// your functions to calculate your colour
render () {
return <div>
<ChildComponent colourProp={this.state.color} />
<ChildComponent colourProp={this.state.color} />
<ChildComponent colourProp={this.state.color} />
</div>
}
}
Another option if you need the colour to change based on the child component, is to pass down the function that alters the colour to the child component. So similar to the example above, but you would pass down the colour changing function to the child as well.
<ChildComponent colourProp={this.state.color} changeColour={this.changeColourFunction}/>
Now you can call that function from your child
// Inside child component
this.props.changeColour(params)
And now your parent will change its state, and the new colour will get changed in the parent and passed down to all the children.
Lastly you can try using ReactContext, set it up in a file that's external to all your components and and import it to your components. In your parent component where you pass your initial state, you would use YourContext.Provider and pass your initial state. Then in your children you can use YourContext.Consumer. For more details on this see : https://reactjs.org/docs/context.html
As Jonathan said, you can pass state as props to other components, but only if they are connected. If the svgs you are using are not being rendered in the same file, things will become a little messy. In order to 'fix' this, people use state management tools, such as redux and context API.
Redux, for example, is built based on database design, so you can access the state globally. Tough it is really useful, the environment is not beginners friendly, and I do not advise you learning it until completely grasping react.
Try this way:
import './colorStrip.scss'
class ColorStrip extends Component {
constructor(props) {
super(props)
this.colorHue=10;
this.colorSaturation=100;
this.colorLightness=50;
this.state = {
color:"hsl(0,100%,50%)"
}
this.changeColor(1);
}
changeColor = (speed) => {
this.colorHue+=10*speed;
if(this.colorHue>=360)
this.colorHue=0;
this.setState({
color : "hsl("+this.colorHue+","+this.colorSaturation+"%,"+this.colorLightness+"%)"
})
setTimeout(() => {this.changeColor(speed)},75)
}
render() {
const { color } = this.props;
return (
<svg style={backgroundColor:color} className="strip">
</svg>
)
}
}
export default ColorStrip
I'd suggest creating a Higher-Order Component (HOC) to house the color logic and then you can wrap any component you want with this HOC and the wrapped component will have the logic & data you need.
For example:
import React, { Component } from "react";
function withColor(WrappedComponent) {
return class ComponentWithColor extends Component {
constructor(props) {
super(props);
this.colorHue=10;
this.colorSaturation=100;
this.colorLightness=50;
this.state = {
color:"hsl(0,100%,50%)"
}
this.changeColor(1);
}
changeColor = (speed) => {
this.colorHue+=10*speed;
if(this.colorHue>=360)
this.colorHue=0;
this.setState({
color : "hsl("+this.colorHue+","+this.colorSaturation+"%,"+this.colorLightness+"%)"
})
setTimeout(() => {this.changeColor(speed)},75)
}
render() {
const { color } = this.state;
return <WrappedComponent color={ color } { ...this.props }/>
}
}
}
Then if you define a new component, and you want it to have access to the color prop, just wrap the component class/function in withColor before constructing.
For example:
class MyComponent extends Component {
render() {
const { color } = this.props;
return (
<svg style={backgroundColor:color} className="strip">
</svg>
)
}
}
const MyComponentWithColor = withColor(MyComponent);
// then export & use MyComponentWithColor
The React documentation says to pass the function defined in the Root component as a prop to the Child Component if you plan to update context from a nested component.
I have implemented the same:
import React from 'react';
const DataContext = React.createContext();
/**
* The App.
*/
export default class App extends React.Component {
constructor() {
super();
this.updateGreet = this.updateGreet.bind( this );
this.state = {
greet: '',
updateGreet: this.updateGreet
}
}
updateGreet() {
this.setState({
greet: 'Hello, User',
});
}
render() {
return (
<DataContext.Provider value={ this.state }>
<GreetButton />
<DisplayBox />
</DataContext.Provider>
)
}
}
/**
* Just a button element. On clicking it sets the state of `greet` variable.
*/
const GreetButton = () => {
return (
<DataContext.Consumer>
{
( { updateGreet } ) => {
return <button onClick={ updateGreet }>Greet</button>
}
}
</DataContext.Consumer>
)
}
/**
* Prints the value of `greet` variable between <h1> tags.
*/
const DisplayBox = () => {
return (
<DataContext.Consumer>
{
( { greet } ) => {
return <h1>{ greet }</h1>
}
}
</DataContext.Consumer>
)
}
It's a very simple React App I created for learning the Context API. What I'm trying to achieve is to define the updateGreet() method within the GreetButton component instead of defining it inside the App component since the function has nothing to do with the App component.
Another advantage I see is that if I choose to remove the GreetButton component altogether, then I need not keep track of all the methods it uses defined within another components.
Is there a way we can achieve this?
I would argue that the updateGreet method does have to do with App since it is manipulating App state.
I don't see this as a context-specific issue so much as the normal react practice of passing functions down to child components.
To accomplish your wish you could bind and pass the App's setState method to the provider and then implement updateGreet in the GreetButton component, but that would be an anti-pattern and I wouldn't recommend it.
When I am working with the Context API I typically define my context in a separate file and implement a custom provider to suit my needs, passing the related methods and properties down and consuming them throughout the tree as needed.
Essentially, implement what you have in App as its own Provider class GreetProvider. In the render method for GreetProvider simply pass the children through:
render() {
return (
<DataContext.Provider value={ this.state }>
{ this.props.children }
</DataContext.Provider>
)
}
Now, all of your greeting logic can live together at the source, with the context. Use your new GreetProvider class in App and any of its children will be able to consume its methods.
In my app I have several form field components that all behave the same way but look slightly different. I want to be able to use the state and class methods of a single formfield component while providing some sort of alternative render method so that I can customize the appearance of this element on the fly. I know I can wrap children and then use the props.children in the component. But I'm looking to re-use the components methods somehow:
class Parent extends React.Component {
render() {
<div className="parent">
<ChildFormField
renderAlternate={self => {
return (
<div className="child--alternate">
<input onChange={self.doThing} />
</div>
);
}}
/>
</div>
}
}
// And the child component look something like...
class ChildFormField extends React.Component {
state = {
value: null
}
doThing = value => {
return this.setState({ value });
}
render() {
if (this.props.renderAlternate !== undefined) {
return this.props.renderAlternate();
}
// standard return
return <div />;
}
}
I'm relatively new to React outside of its basic usage. Is there a recommended way to achieve this functionality?
Your renderAlternate function expects a parameter self. So you need to pass this when calling it.
return this.props.renderAlternate(this);
See https://codesandbox.io/s/w759j6pl6k as an example of your code.
This recipe is known as render prop. It's widely used where it's suitable, but here it looks like bad design decision, primarily because it exists to access component self instance. This is the case for inheritance:
class AlternateChildFormField extends ChildFormField {
render() {
return (
<div className="child--alternate">
<input onChange={this.doThing} />
</div>
);
}
}
In React, function composition is usually preferred, but inheritance is acceptable solution if it serves a good purpose. ChildFormField requires doThing to be a method and not helper function because it needs to access this.setState.
An alternative is to use React 16.7 hooks and functional components. This way the same component can be expressed with composition:
const useThingState = () => {
const [state, setState] = useState({ value: null });
return value => {
return setState({ value });
};
}
const ChildFormField = props => {
// not used here
// const doThing = useThingState();
return <div />;
}
const AlternateChildFormField = props => {
const doThing = useThingState();
return (
<div className="child--alternate">
<input onChange={doThing} />
</div>
);
}
The common way this is managed in React ecosystem are High Order Components:
ref: https://reactjs.org/docs/higher-order-components.html
ref: https://medium.com/backticks-tildes/reusing-react-component-logic-with-higher-order-component-3fbe284beec9
Further, what you are looking for maybe a reuse of state logic.
As React 16.7-alpha you can use hooks to reuse state logic with functional components but APIs are subject of possible breaking changes.
ref: https://medium.com/#nicolaslopezj/reusing-logic-with-react-hooks-8e691f7352fa
The render method of this component does use any of the props supplied to the component.
Will the component re-render when the props change regardless?
class MyComponent extends React.Component {
constructor(props) {
super(props);
const { propValue } = props;
// do something with propValue...
}
render () {
return (
<div>foo</div>
);
}
}
Will render be called - yes. Unless you implement shouldComponentUpdate to return false.
Will the DOM be rerendered - no.
Also you might want to take a look at https://babeljs.io/docs/plugins/transform-react-constant-elements/ that hoists static elements up.
In
const Hr = () => {
return <hr className="hr" />;
};
Out
const _ref = <hr className="hr" />;
const Hr = () => {
return _ref;
};
Yes, the component will re-render unless you implement shouldComponentUpdate. You can inherit from PureComponent which uses shallow comparison of prop and state with previous values to determine if component should update or not.
As far as i know react will call the render method in the following scenarios
when your component get mounted initially
when state got changed using this.setState()
when your component receives new props
when this.forceUpdate() get called.
since you didn't implement shouldcomponentUpdate() the render method is going to get called
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.