So I have an app that looks like this (tried to simplify as much as possible):
// file with context
const SomeContext = React.createContext({});
// file with app root
class App extends Component {
constructor() {
super();
this.state = {
someValue: 123
};
}
doSomething() {
// this does not work,
// as "this" is context value
this.setState({
someValue: someValue + 1
});
}
render() {
// create context values
const value = {
someValue: this.state.someValue
doSomething: this.doSomething
};
return <SomeContext.Provider value={value}><ChildComponent/></SomeContext>;
}
}
// file with child component
class ChildComponent extends Component {
render() {
return <button onClick={this.context.doSomething} />
}
}
ChildComponent.contextType = SomeContext;
I want context to have doSomething method that updates state of the app (specifically increase someValue by 1).
How to correctly execute doSomething method from child component?
Do I have to do doSomething: this.doSomething.bind(this)? or there is a better way?
edit question is less about how to get "this", but more about what's the recommended way to do it in this case. Maybe I should not add method to context at all?
Related
I have this situation but I don't know if it's ok to do something like this.
If I save an object inside the state of my component, can the object modify itself without using setState?
File A.js
export default class A {
constructor(value) {
this.value = value;
}
setValue(value) {
this.value = value;
}
}
File B_Component.js
import React from "react";
import A from "./A";
class B_Component extends React.Component {
constructor(props) {
super(props);
this.state = {
aObj = new A("foo")
}
}
bar = () => {
this.state.aObj.setValue("bar");
}
...render etc...
}
Basically this should modify the state of the component without using setState.
Is this correct or there may be problems?
Is this correct or there may be problems?
There may be problems. :-) If you're rendering that object's value, then you're breaking one of the fundamental rules of React, which is that you can't directly modify state. If you do, the component won't re-render.
Instead, you create a new object and save the new object in state:
bar = () => {
this.setState({aObj: new A("bar")});
}
Or if you want to reuse other aspects of the object and only change value:
bar = () => {
const aObj = new A(this.state.aObj); // Where this constructor copies all the
// relevant properties
aObj.setValue("bar");
this.setState({aObj});
}
The A constructor would be
constructor(other) {
this.value = other.value;
// ...repeat for any other properties...
}
Or you might give A a new function, withValue, that creates a new A instance with an updated value:
class A {
constructor(value) {
this.value = value;
// ...other stuff...
}
withValue(value) {
const a = new A(value);
// ...copy any other stuff to `a`...
return a;
}
}
Then:
bar = () => {
this.setState({aObj: this.state.aObj.withValue("bar")});
}
I'm writing a React app where the App.js has a lot of logic inside it tying together the rest of the components and keeping a coherent state. I have moved some of the code that were originally in App.js to helpermodule.js and bind the imported functions so that they can manipulate the state of the App component.
This is my setup:
App.js
import helperFunction from './helpermodule'
class App extends Component {
constructor(props) {
super(props)
this.state = { foo: 'bar' }
this.helperFunction = helperFunction.bind(this)
}
}
helpermodule.js
function helperFunction() {
this.setState({
bar: 'calculated' + this.state.foo,
})
}
export { helperFunction }
And I want to write a unit test for the function(s) inside the helpermodule. I can't find the relevant parts in the Enzyme or Jest documentation where this is covered. The goal is to see what effect helperFunction has on the App state.
I have tried things like this
test('helperFunction', () => {
let state = { foo: 'bar' }
let setState = (obj) => { this.state = obj }
return expect(helperFunction().bind(this))
.toBe({
...newShift,
id: 0,
})
}
But that just returns an error; TypeError: Cannot read property 'state' of undefined. Perhaps my entire approach is wrong so the problem can be circumvented but I'm not sure.
Don't use this.setState in an external function
helpermodule.js
function helperFunction(stateFoo) {
return {bar: 'calculated' + stateFoo};
}
export { helperFunction }
Then use the result to set state in the component. Also don't setState in constructor.
import helperFunction from './helpermodule'
class App extends Component {
constructor(props) {
super(props)
this.state = { foo: 'bar', ...helperFunction('bar') }
}
}
This will make testing easier as well as being a better structure in general.
no idea why would you follow this approach it is so weird and hard to follow and maintain but for the sake of your argument. the issue is that you are passing this to the bind while this has no property of state or setState so you should pass a mocked this instead.
here is how it could go
describe('helperFunction', () => {
function helperFunction() {
// #ts-ignore
this.setState({
// #ts-ignore
bar: 'calculated' + this.state.foo,
})
}
it('updates the provided stateHolder', () => {
const stateHolder = {
state: { foo: 'bar' },
// don't use arrow function otherwise it won't react the state via this
setState(obj: any) {
this.state = obj;
},
};
const importedHelperFunction = helperFunction.bind(stateHolder);
importedHelperFunction();
expect(stateHolder.state.foo).toBe('calculatedbar');
});
});
Is it wrong to use setState in a function outside of the React component?
Example:
// myFunction.js
function myFunction() {
...
this.setState({ ... })
}
// App.js
import myFunction from './myFunction
class App extends Component {
constructor() {
super()
this.myFunction = myFunction.bind(this)
}
...
}
I'm not sure the way you're binding will actually work. You could do something like:
export const getName = (klass) => {
klass.setState({ name: 'Colin'})
}
then
class App extends Component {
state = {
name: 'React'
};
handleClick = () => {
getName(this);
}
render() {
return (
<div>
<p>{this.state.name}</p>
<button onClick={this.handleClick}>change name</button>
</div>
);
}
}
Working example here.
So the only reasons to do this is if you are reducing repeated code, e.g. two components use the same logic before calling this.setState, or if you want to make testing easier by having a separate pure function to test. For this reason I recommend not calling this.setState in your outside function, but rather returning the object you need so it can you can call this.setState on it.
function calculateSomeState(data) {
// ...
return { updated: data };
}
class MyComponent extends React.Component {
constructor(props) {
super(props)
this.state = calculateSomeState(props.data);
}
handleChange = (e) => {
const value = e.target.value;
this.setState(calculateSomeState({ ...props.data, value }));
}
}
It looks like a bug waiting to happen... If you want to use an external function to set state, you can use the alternative syntax provided by React:
this.setState((prevState, props) => {
return updatedState; //can be a partial state, like in the regular setState
});
That callback can easily be extracted to an external function and it's guaranteed to work
It is not wrong, the function is never called outside the component. This is a mix-in technique. bind isn't needed, as long as the function isn't used as a callback. In this case myFunction is same among all instances, a more efficient way would be:
class App extends Component {}
App.prototype.myFunction = myFunction;
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 m actually learning reactjs and I m actually developping a little TODO list, wrapped inside of a "parent component" called TODO.
Inside of this parent, I want to get the current state of the TODO from the concerned store, and then pass this state to child component as property.
The problem is that I dont know where to initialize my parent state values.
In fact, I m using ES6 syntax, and so, I dont have getInitialState() function. It's written in the documentation that I should use component constructor to initialize these state values.
The fact is that if I want to initialize the state inside of my constructor, the this.context (Fluxible Context) is undefined actually.
I decided to move the initialization inside of componentDidMount, but it seems to be an anti pattern, and I need another solution. Can you help me ?
Here's my actual code :
import React from 'react';
import TodoTable from './TodoTable';
import ListStore from '../stores/ListStore';
class Todo extends React.Component {
constructor(props){
super(props);
this.state = {listItem:[]};
this._onStoreChange = this._onStoreChange.bind(this);
}
static contextTypes = {
executeAction: React.PropTypes.func.isRequired,
getStore: React.PropTypes.func.isRequired
};
componentDidMount() {
this.setState(this.getStoreState()); // this is what I need to move inside of the constructor
this.context.getStore(ListStore).addChangeListener(this._onStoreChange);
}
componentWillUnmount() {
this.context.getStore(ListStore).removeChangeListener(this._onStoreChange);
}
_onStoreChange () {
this.setState(this.getStoreState());
}
getStoreState() {
return {
listItem: this.context.getStore(ListStore).getItems() // gives undefined
}
}
add(e){
this.context.executeAction(function (actionContext, payload, done) {
actionContext.dispatch('ADD_ITEM', {name:'toto', key:new Date().getTime()});
});
}
render() {
return (
<div>
<button className='waves-effect waves-light btn' onClick={this.add.bind(this)}>Add</button>
<TodoTable listItems={this.state.listItem}></TodoTable>
</div>
);
}
}
export default Todo;
As a Fluxible user you should benefit from Fluxible addons:
connectToStores.
The following example will listen to changes in FooStore and BarStore and pass foo and bar as props to the Component when it is instantiated.
class Component extends React.Component {
render() {
return (
<ul>
<li>{this.props.foo}</li>
<li>{this.props.bar}</li>
</ul>
);
}
}
Component = connectToStores(Component, [FooStore, BarStore], (context, props) => ({
foo: context.getStore(FooStore).getFoo(),
bar: context.getStore(BarStore).getBar()
}));
export default Component;
Look into fluxible example for more details. Code exсerpt:
var connectToStores = require('fluxible-addons-react/connectToStores');
var TodoStore = require('../stores/TodoStore');
...
TodoApp = connectToStores(TodoApp, [TodoStore], function (context, props) {
return {
items: context.getStore(TodoStore).getAll()
};
});
As a result you wouldn't need to call setState, all store data will be in component's props.