Reputation: 22503
I am building an app with react. I've been using react for 2.5 years now but this is the first time I'm really starting with tests. Because of this I'm removing as much logic from my components. Most of this is quite simple because it's easy to think in pure functions for most logic.
I have found myself reusing one particular method across different components because it's my handler for input fields. After I copy pasted it for the 3rd time I thought there must be a cleaner solution.
This function currently exists inside 3 of my components in exactly the same way
/**
* @description
* Returns a function that updates the state form
*
* @param {String} stateKey - key in the state to update
* @returns {Function}
*/
@autobind
updateInputValue (prop) {
return (event) => {
this.setState({ [prop]: event.target.value });
}
};
I attempted to extract it out and pass this
to it like a variable, it worked but I wondered if there was a cleaner way to do it.
/**
* @description
* Returns a function that updates the state of the filter form
*
* @param {Object} componentThis - The components context
* @param {String} stateKey - key in the state to update
* @returns {Function}
*/
function updateInputValue (componentThis, stateKey) {
return (event) => {
componentThis.setState({ [stateKey]: event.target.value });
}
}
then in the input you're just doing
<Input id="foo"
value={this.state.foo}
onChange={updateInputValue(this, 'foo')} />
Something about passing this feels wrong (or at least open to something breaking), I wanted to know if there were other solutions for this problem?
Upvotes: 1
Views: 1007
Reputation: 2450
So I actually have done this on a different project of mine. I treat it like there is a reducer from redux when setting state locally and a function that gets called, given the state and returns the entire state to set again.
This makes it extremely easy to test and reuse across different components. You can find this open sourced here
The project I am doing this on is a table, so if I wanted to go to the next set of data I would do the following.
(I did simplify this example a bit)
import { nextPage } from '../actions/tableActions'
nextPage() {
this.setState(currentState => {
return nextPage({ state: currentState })
});
};
In my tableActions.js file it would look like
export const nextPage = ({ state }) => {
const { currentPage } = state.pagination;
return changePage({ ...state, currentPage: currentPage + 1 })
};
Now writing a test it is as simple as this.
it('should update the currentPage to the next page', () => {
const given = {
state: {
pagination: {
currentPage: 2,
},
}
};
const expected = {
pagination: {
currentPage: 3,
},
};
expect(actions.nextPage(given)).toEqual(expected);
});
Upvotes: 2
Reputation: 13976
Approach #1
updateInputValue (prop, event) {
return { [prop]: event.target.value };
};
In your code this
someFunction = (event) => {
this.setState(updateInputValue('someFieldkey', event))
}
Approach #2
updateInputValue (theThis, prop, event) {
theThis.setState({ [prop]: event.target.value });
};
In your code this
someFunction = (event) => {
updateInputValue(this, 'someFieldkey', event))
}
For approach #2, I haven't tried this but since everything in javascript is by reference, when you pass this
instance in that function and use it's setState
method I think it should work.
Upvotes: 0