I'm learning React 16.3, and it's new Context API. In particular Updating Context from a Nested Component. In their example they set a method that is defined in the constructor rather than a standard method.
class App extends React.Component {
constructor(props) {
super(props);
// What is the benefit of doing this here?
this.toggleTheme = () => {
this.setState(state => ({
theme:
state.theme === themes.dark
? themes.light
: themes.dark,
}));
};
this.state = {
theme: themes.light,
toggleTheme: this.toggleTheme,
};
}
render() {
// The entire state is passed to the provider
return (
<ThemeContext.Provider value={this.state}>
<Content />
</ThemeContext.Provider>
);
}
}
Everything I've read regarding lifting state up and passing methods down to children has been done using the below pattern. Why is the above preferred over the below? Are there any differences?
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
theme: themes.light,
toggleTheme: this.toggleTheme,
};
this.toggleTheme = this.toggleTheme.bind(this);
}
// Could it be done here?
toggleTheme() {
this.setState(state => ({
theme:
state.theme === themes.dark
? themes.light
: themes.dark,
}));
};
render() {
// The entire state is passed to the provider
return (
<ThemeContext.Provider value={this.state}>
<Content />
</ThemeContext.Provider>
);
}
}
If you use the first approach which is defining the method inside the constructor like this
constructor() {
this.toggleTheme = () => {
this.setState(state => ({
theme:
state.theme === themes.dark
? themes.light
: themes.dark,
}));
};
}
Then when your component usesthis.toggleTheme as a callback, you don't have to bind its this reference to the current component in which it is defined, e.g. this.toggleTheme = this.toggleTheme.bind(this), on the other hand, if you define toggleTheme as a method outside the constructor as in your second example, and if toggleTheme is passed as a callback, you will get "setState is not defined" or something like that when toggleTheme is invoked
Also, with the first approach toggleTheme is added as a instance property to the component class meaning each component instance will have a separate copy of toggleTheme, whereas the second approach will add it to the prototype of the component class which is better in terms of memory consumption because all component instances will share that method on the prototype
The difference between this two approaches:
class MyComponenet extends React.Component {
constructor(props) {
super(props);
this.method = () => {
console.log('Method');
}
}
render() {
return null;
}
}
... and ...
class MyComponenet extends React.Component {
method() {
console.log('Method');
}
render() {
return null;
}
}
Is that the first approach defines the method with the arrow notation which automatically binds the function's this to be the instance of the component class while the other doesn't.
You could change the second example to:
class MyComponenet extends React.Component {
method = () => {
console.log('Method');
}
render() {
return null;
}
}
This would be the same as the first example, but keep in mind you have to enable your transpiler option that allows this syntax.
Related
I saw thousands post about it so I am a bit confuse, I did use arrow function, I binded my method changed for componentDidUpdate but I still can't manage to
in my async call setState my data and pass it to my child component.
ParentComponent
class ParentComponent extends React.Component {
constructor(props) {
super(props);
this.state = {
activeNav: 1,
loading: true,
data: []
};
this.fetchData = this.fetchData.bind(this);
}
fetchData = () => {
var that = this;
getMyData()
.then(res => {
console.log(res); // Output res Object
that.setState({
data: res
})
})
.catch((error) => {
console.log(error);
});
}
componentDidUpdate = () => this.fetchData()
render() {
const { data, loading } = this.state;
return (
<>
<ChildComponent data={this.data} loading={loading}/>
</>
);
}
}
ChildComponent
class CurrentUp extends React.Component {
constructor(props) {
super(props);
}
componentDidUpdate = () => {
console.log(this.props.data); // Output []
console.log(this.props.loading); // Output true
}
}
render() {
console.log(this.props.data); // Output []
console.log(this.props.loading); // Output true
return (
<div>
</div>
);
}
}
What am I missing ?
Solved, i am not sure how. I kept trying different stuff
You are console logging the data in the componentDidMount method. This method runs only once when a component is mounted. When you update the data in the parent component, and pass it to the child component, it doesn't re-create the entire component instance. Only the updates are passed down. You can access those updates in componentDidUpdate method. Or, if you are directly accessing props, you can log the data inside the render method.
class CurrentUp extends React.Component {
constructor(props) {
super(props);
}
render = () => {
console.log(this.props.data);
console.log(this.props.loading);
return null;
}
}
Other points:
You don't need to bind the function with this if you are using an arrow function.
Inside the arrow function, it is safe to use the this keyword. No need of assigning it to that.
Try to bring some more code cause I see the child component you posted is an instead current up component. Send the real child component and then move the var in your fetchData function to the constructor.
Well here I want to use one method to another component, And for that I found a way through composition.
And this is what I did for that
file1.js
import ProductList from '../../views/Products/ProductList';
class CloseableTab extends Component {
constructor() {
super();
this.tpItem = () => {
console.log("hello, item clicked");
};
}
render() {
return (
<div>
<ProductList
itemChange={this.tpItem} />
</div>
);
}
}
export default CloseableTab;
Then in productList I want to call the "tpItem" method by calling itemChange in prop.
Though before that I tried to console the 'prop' of product list. So, it shows me null object in the console. And for that I used the code below:
ProductList.js
export default class ProductList extends Component {
constructor() {
super();
};
render() {
console.log(this.props);
return { }
}
}
So, this gives me null object in the console.
I'll appreciate your help, thanks.
Did you make constructor props enabled ?
Just pass props parameter in constructor
constructor(props) {
super(props)
}
The constructor for a React component is called before it is mounted.
When implementing the constructor for a React.Component subclass, you
should call super(props) before any other statement. Otherwise,
this.props will be undefined in the constructor, which can lead to
bugs.
Its not ideal to define functions in the constructor of the component, you can declare them outside of constructor and pass them down, also, in ProductList you are trying to render an object which isn't supported. if you don't want to return anything use return null.
Below code works as expected.
class CloseableTab extends Component {
constructor() {
super();
this.tpItem = () => {
console.log("hello, item clicked");
};
}
render() {
console.log(this.tpItem);
return (
<div>
<ProductList
itemChange={this.tpItem} />
</div>
);
}
}
class ProductList extends Component {
render() {
console.log(this.props);
return null
}
}
However you must write it like
class CloseableTab extends Component {
tpItem = () => {
console.log("hello, item clicked");
};
render() {
console.log(this.tpItem);
return (
<div>
<ProductList
itemChange={this.tpItem} />
</div>
);
}
}
Working sandbox
Considering this pseudocode:
component.js
...
import {someFunc} from "./common_functions.js"
export default class MyComp extends Component {
constructor(props) {
super(props);
this.someFunc = someFunc.bind(this);
this.state = {...};
}
_anotherFunc = () = > {
....
this.someFunc();
}
render() {
...
}
}
common_functions.js
export function someFunc() {
if(this.state.whatever) {...}
this.setState{...}
}
How would I bind the function someFunc() to the context of the Component? I use it in various Components, so it makes sense to collect them in one file. Right now, I get the error "Cannot read whatever of undefined". The context of this is unknown...
You can't setState outside of the component because it is component's local state. If you need to update state which is shared, create a store (redux store).
In your case, you can define someFunction at one place and pass it the specific state variable(s) or entire state. After you are done in someFunction, return the modified state and update it back in your component using setState.
export function someFunc(state) {
if(state.whatever) {...}
const newState = { ...state, newValue: whateverValue }
return newState
}
_anotherFunc = () = > {
....
const newState = this.someFunc(this.state);
this.setState({newValue: newState});
}
it's not a React practice and it may cause lot of problems/bugs, but js allows to do it:
Module A:
export function your_external_func(thisObj, name, val) {
thisObj.setSate((prevState) => { // prevState - previous state
// do something with prevState ...
const newState = { // new state object
someData: `This is updated data ${ val }`,
[name]: val,
};
return newState
});
}
Then use it in your react-app module:
import { your_external_func } from '.../your_file_with_functions';
class YourReactComponent extends React.Component {
constructor(props, context) {
super(props, context);
this.state={
someName: '',
someData: '',
};
}
handleChange = (e) => {
const { target } = event;
const { name } = target;
const value = target.type === 'checkbox' ? target.checked : target.value;
your_external_func(this, name, value);
}
render() {
return (<span>
{ this.state.someData }
<br />
<input
name='someName'
value={ this.state.someName }
onChange={ this.handleChange }
/>
</span>);
}
}
It's a stupid example :) just to show you how you can do it
The best would obviously to use some kind of external library that manages this. As others have suggested, Redux and MobX are good for this. Using a high-order component to wrap all your other components is also an option.
However, here's an alternative solution to the ones above:
You could use a standard javascript class (not a React component) and pass in this to the function that you are calling from that class.
It's rather simple. I've created a simple example below where the state is changed from a function of another class; take a look:
class MyApp extends React.Component {
constructor() {
super();
this.state = {number: 1};
}
double = () => {
Global.myFunc(this);
}
render() {
return (
<div>
<p>{this.state.number}</p>
<button onClick={this.double}>Double up!</button>
</div>
);
}
}
class Global {
static myFunc = (t) => {
t.setState({number: t.state.number*2});
}
}
ReactDOM.render(<MyApp />, document.getElementById("app"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="app"><div>
There is a functional form of setState that can even be used outside of a component.
This is possible since the signature of setState is:
* #param {object|function} partialState Next partial state or function to
* produce next partial state to be merged with current state.
* #param {?function} callback Called after state is updated.
See Dan's tweet: https://twitter.com/dan_abramov/status/824308413559668744
This all depends on what you are trying to achieve. At first glance I can see 2 options for you. One create a child component and two: use redux as redux offers a singular state between all of your child components.
First option:
export default class parentClass extends Component {
state = {
param1: "hello".
};
render() {
return (
<Child param1={this.state.param1}/>
);
}
}
class Child extends Component {
render() {
console.log(this.props.param1);
return (
<h1>{this.props.param1}</h1>
);
}
}
Now the above child component will have the props.param1 defined from the props passed from it's parent render function.
The above would work but I can see you're trying to establish a 'common' set of functions. Option 2 sort of provides a way of doing that by creating a singular state for your app/project.
If you've haven't used redux before it's pretty simple to use once you've got the hang of it. I'll skip out the setup for now http://redux.js.org/docs/basics/UsageWithReact.html.
Make a reducer like so:
import * as config from './config';//I like to make a config file so it's easier to dispatch my actions etc
//const config.state = {param1: null}
//const config.SOME_FUNC = "test/SOME_FUNC";
export default function reducer(state = config.state, action = {}) {
switch(action.type) {
case config.SOME_FUNC:
return Object.assign({}, state, {
param1: action.param1,
});
break;
default:
return state;
}
}
}
Add that to your reducers for your store.
Wrap all your components in the Provider.
ReactDOM.render(
<Provider store={store} key="provider">
<App>
</Provider>,
element
);
Now you'll be able to use redux connect on all of the child components of the provider!
Like so:
import React, {Component} from 'react';
import {connect} from 'react-redux';
#connect(
state => (state),
dispatch => ({
someFunc: (param1) => dispatch({type: config.SOME_FUNC, param1: param1}),
})
)
export default class Child extends Component {
eventFunction = (event) => {
//if you wanted to update the store with a value from an input
this.props.someFunc(event.target.value);
}
render() {
return (
<h1>{this.props.test.param1}</h1>
);
}
}
When you get used to redux check this out https://github.com/redux-saga/redux-saga. This is your end goal! Sagas are great! If you get stuck let me know!
Parent component example where you define your callback and manage a global state :
export default class Parent extends Component {
constructor() {
super();
this.state = {
applyGlobalCss: false,
};
}
toggleCss() {
this.setState({ applyGlobalCss: !this.state.applyGlobalCss });
}
render() {
return (
<Child css={this.state.applyGlobalCss} onToggle={this.toggleCss} />
);
}
}
and then in child component you can use the props and callback like :
export default class Child extends Component {
render() {
console.log(this.props.css);
return (
<div onClick={this.props.onToggle}>
</div>
);
}
}
Child.propTypes = {
onToggle: PropTypes.func,
css: PropTypes.bool,
};
Well for your example I can see you can do this in a simpler way rather than passing anything.
Since you want to update the value of the state you can just return it from the function itself.
Just make the function you are using in your component async and wait for the function to return a value and set the state to that value.
import React from "react"
class MyApp extends React.Component {
constructor() {
super();
this.state = {number: 1};
}
theOnlyFunction = async() => {
const value = await someFunctionFromFile( // Pass Parameters );
if( value !== false ) // Just for your understanding I am writing this way
{
this.setState({ number: value })
}
}
render() {
return (
<div>
<p>{this.state.number}</p>
<button onClick={this.double}>Double up!</button>
</div>
);
}
}
And in SomeOtherFile.js
function someFunctionFromFile ( // catch params) {
if( //nah don't wanna do anything ) return false;
// and the blahh blahh algorithm
}
you should use react Context
Context lets us pass a value deep into the component tree without explicitly threading it through every component.
here is a use case from react docs : create a context for the current theme (with "light" as the default).
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>
);
}
}
// 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} />;
}
}
resource: https://reactjs.org/docs/context.html
I'm learning React and I'm not sure how to setup this pattern. It could be something really easy I'm just missing.
I have a main component that controls state. It has all of the functions to update state and passes these down to child components via props. I've simplified the code to focus on one of these functions.
Here's the component now, all works as it should:
ManageMenu.js
import React from 'react'
class ManageMenu extends React.Component {
constructor() {
super()
this.toggleEditing = this.toggleEditing.bind(this)
// Set initial state
this.state = {
menuSections: []
}
}
toggleEditing(id) {
const menuSections = this.state.menuSections
menuSections.map(key => (key.id === id ? key.details.editing = id : ''))
this.setState({ menuSections })
}
render() {
return (
...
)
}
}
export default ManageMenu
The toggleEditing is passed via props to a child component that uses it to render an editing form if the edit button is clicked.
I have about 10 of these different functions in this component and what I would like to do is move them to an external lib/methods.js file and then reference them. Below is the code I would like to have, or something similar, but React doesn't like what I'm doing. Throws a syntax error:
Failed to compile.
Error in ./src/components/ManageMenu.js
Syntax error: Unexpected token
toggleEditing(id, menuSectionId, this.state, this)
Here is what I would like to do...
lib/methods.js
const toggleEditing = function(id, state, that) {
const menuSections = state.menuSections
menuSections.map(key => (key.id === id ? key.details.editing = id : ''))
that.setState({ menuSections })
}
module.exports = {
toggleEditing
}
And then in my component:
ManageMenu.js
import React from 'react'
import { toggleEditing } from '../lib/methods'
class ManageMenu extends React.Component {
constructor() {
super()
// Set initial state
this.state = {
menuSections: []
}
}
toggleEditing(id, this.state, this)
render() {
return (
...
)
}
}
export default ManageMenu
Any help is appreciated, thanks!
Thanks to #Nocebo, the answer on how to externalize functions is here:
Externalise common functions in various react components
In my particular situation,
I need to remove the “floating” toggleEditing(id, this.state, this) call in the middle of nowhere. Update: This error happens “because it is invoking a method within a class definition.” (see Pineda’s comment below)
Remove the leading this. on the right side of the this.toggleEditing statement in constructor()
Update the function in lib/methods.js to remove the state and that variables since its bound to this in the constructor()
See updated code below.
ManageMenu.js
import React from 'react'
import { toggleEditing } from '../lib/methods'
class ManageMenu extends React.Component {
constructor() {
super()
this.toggleEditing = toggleEditing.bind(this)
// Set initial state
this.state = {
menuSections: []
}
}
render() {
return (
...
)
}
}
export default ManageMenu
lib/methods.js
const toggleEditing = function(id) {
const menuSections = this.state.menuSections
menuSections.map(key => (key.id === id ? key.details.editing = id : ''))
this.setState({ menuSections })
}
module.exports = {
toggleEditing
}
You're error arises because you are invoking toggleEditing in your ManageMenu.js class definition rather than defining a function.
You can achive what you want by setting a local class member this.toggleEditing to the bound function returned by the .bind method and do so within the constructor:
import React from 'react'
import { toggleEditing } from '../lib/methods'
class ManageMenu extends React.Component {
constructor() {
super()
this.state = {
menuSections: []
}
// bind external function to local instance here here
this.toggleEditing = toggleEditing.bind(this);
}
// don't invoke it here, bind it in constructor
//toggleEditing(id, this.state, this)
render() {
return (
...
)
}
}
export default ManageMenu
I am creating a player library and want the React flow to go like this:
PlayerHOC -> PlaylistHOC -> FooterContainer.
The reason I want it to go in this direction is that PlayerHOC has methods on it that PlaylistHOC and FooterContainer need to access (i.e from props).
My code:
class FooterContainer extends React.Component {
render() {
return (
<div>
<div className="jp-type-footer" >
//...
</div>
</div>
);
}
}
class FooterPlayer extends React.Component {
constructor() {
super();
this.options = {
smoothPlayBar: false,
muted: true,
//...
};
}
render() {
return (
<Player {...this.options} />
);
}
};
export const PlaylistHOC = (WrappedComponent) => class extends React.Component {
constructor(props) {
super(props);
//Add a new stateClass for the extra loop option
this.stateClass = merge({
shuffled: "state-shuffled",
loopedPlaylist: "state-loop-playlist"
}, this.props.stateClass);
}
setPlaylist = () => {}
};
export const PlayerHOC = (WrappedComponent) => class extends React.Component {
constructor(props) {
super(props);
//get passed in props from FooterPlayer and PlaylistHoc
}
play = () => {}
pause = () => {}
};
const Player = PlayerHOC(PlaylistHOC(FooterContainer));
export default connect()(FooterPlayer);
I also pass in props from FooterPlayer to PlayerHOC which works fine. However, I also want to pass in default props from PlaylistHOC to PlayerHOC that will never be updated and I can't figure out how to this while also keeping this flow.
For example: const Player = PlaylistHOC(PlayerHOC(FooterContainer)); this would allow me to pass in initial props from PlaylistHOC and FooterPlayer to PlayerHOC but then I would not be able to access PlayerHOC methods by props.
How do I do this?
I would use const Player = PlaylistHOC(PlayerHOC(FooterContainer)); because a parent component can't receive props from it's children.
Looks like both PlaylistHOC and PlayerHOC are mixins, so they should inherit from the component being wrapped instead of React.Component.
I've changed a code just a little bit to be able to test it, but the key idea of it is how I've extended WrappedComponent instead of React.Component in your mixins.
class FooterContainer extends React.Component {
render() {
return (
<div>
<div className="jp-type-footer">
<button onClick={this.play.bind(this)}>Play</button>
</div>
</div>
);
}
}
class FooterPlayer extends React.Component {
constructor() {
super();
this.options = {
smoothPlayBar: false,
muted: true
//...
};
}
render() {
return (
<Player {...this.options} />
);
}
};
export const PlaylistHOC = (WrappedComponent) => class extends WrappedComponent {
constructor(props) {
super(props);
//Add a new stateClass for the extra loop option
//this.stateClass = merge({
// shuffled: "state-shuffled",
// loopedPlaylist: "state-loop-playlist"
//}, this.props.stateClass);
}
setPlaylist() {
}
};
export const PlayerHOC = (WrappedComponent) => class extends WrappedComponent {
constructor(props) {
super(props);
//get passed in props from FooterPlayer and PlaylistHoc
}
play() {
console.log('playing');
}
pause() {
}
};
const Player = PlaylistHOC(PlayerHOC(FooterContainer));
export default connect()(FooterPlayer);
By the way, try decorators for some really fancy syntax like
#PlayerlistHOC
#PlayerHOC
class FooterContainer {
}
Be warned decorators are not definitive and might change a lot.