How to inject functions that change component state? - javascript

I want to keep some functions outside of my component for easier testing. However, I cannot change state with these functions because they cannot reference the component's state directly.
So I currently have the hacky solution where I set the function to a variable then call this.setState. Is there a better convention/more efficient way to do this?
Example function code in Tester.js:
const tester = () => {
return 'new data';
}
export default tester;
Example component code in App.js (without imports):
class App extends Component {
constructor() {
super();
this.state = {
data: ''
}
}
componentDidMount(){
let newData = tester();
this.setState({ data: newData })
}
render() {
return(
<div>{this.state.data}</div>
)
}
}

You could bind your tester function like this (this approach doesn't work with arrow functions):
function tester() {
this.setState({ data: 'new Data' });
}
class App extends Component {
constructor() {
super();
this.state = {
data: '',
};
this.tester = tester.bind(this);
}
componentDidMount() {
this.tester();
}
render() {
return (
<div>{this.state.data}</div>
);
}
}
But I would prefer a cleaner approach, where you don't need your function to access this (also works with arrow functions):
function tester(prevState, props) {
return {
...prevState,
data: 'new Data',
};
}
class App extends Component {
constructor() {
super();
this.state = {
data: '',
};
}
componentDidMount() {
this.setState(tester);
}
render() {
return (
<div>{this.state.data}</div>
);
}
}

You can pass a function to setState() that will return a new object representing the new state of your component. So you could do this:
const tester = (previousState, props) => {
return {
...previousState,
data: 'new data',
};
}
class App extends Component {
constructor() {
super();
this.state = {
data: ''
}
}
componentDidMount(){
this.setState(tester)
}
render() {
return(
<div>{this.state.data}</div>
)
}
}
The reason being that you now have access to your component's previous state and props in your tester function.
If you just need access to unchanging static placeholder values inside of your app, for example Lorem Ipsum or something else, then just export your data as a JSON object and use it like that:
// testData.js
export const testData = {
foo: "bar",
baz: 7,
};
...
// In your app.jsx file
import testData from "./testData.js";
const qux = testData.foo; // "bar"
etc.

Related

Dynamically update value based on State in React

I have a react class based component where I have defined a state as follows:
class MyReactClass extends React.Component {
constructor(props) {
super(props);
this.state = {
selectedDataPoints: new Set()
};
}
// This method is called dynamically when there is new addition of data
storeData = (metricName, dataPoint) => {
if (this.state.selectedDataPoints.has(dataPoint)) {
this.state.selectedDataPoints.delete(dataPoint);
} else {
this.state.selectedDataPoints.add(dataPoint);
}
};
render () {
return (
<p>{this.state.selectedDataPoints}</p>
);
}
}
Note that initially, the state is an empty set, nothing is displayed.
But when the state gets populated eventually, I am facing trouble in spinning up the variable again. It is always taking as the original state which is an empty set.
If you want the component to re-render, you have to call this.setState () - function.
You can use componentshouldUpdate method to let your state reflect and should set the state using this.state({}) method.
Use this code to set state for a set:
export default class Checklist extends React.Component {
constructor(props) {
super(props);
this.state = {
selectedDataPoints: new Set()
}
this.addItem = this.addItem.bind(this);
this.removeItem = this.removeItem.bind(this);
}
addItem(item) {
this.setState(({ selectedDataPoints }) => ({
selectedDataPoints: new Set(selectedDataPoints).add(item)
}));
}
removeItem(item) {
this.setState(({ selectedDataPoints }) => {
const newSelectedDataPoints = new Set(selectedDataPoints);
newSelectedDataPoints.delete(item);
return {
selectedDataPoints: newSelectedDataPoints
};
});
}
getItemCheckedStatus(item) {
return this.state.checkedItems.has(item);
}
// This method is called dynamically when there is new addition of data
storeData = (metricName, dataPoint) => {
if (this.state.selectedDataPoints.has(dataPoint)) {
this.state.selectedDataPoints.removeItem(dataPoint);
} else {
this.state.selectedDataPoints.addItem(dataPoint);
}
};
render () {
return (
<p>{this.state.selectedDataPoints}</p>
);
}
}

Pass this.state variable through App Context

When I set the array data using the function getData() then try to call it in the function updateData() I get an error saying the this.state.data is undefined. Any thoughts on how I can pass a this.state variable from one function to another function in the app context provider?
Example code is below:
Any thoughts? Thank you!
export class AppProvider extends React.Component {
constructor(props) {
super(props);
(this.state = {
data: [],
});
}
getData = async () => {
const data = "abc"
this.setState({
data,
});
}
updateData = async () => {
console.log(this.state.data)
}
render() {
return (
<AppContext.Provider value={this.state}>
{this.props.children}
</AppContext.Provider>
);
}
}
Three Things i would like to say,
you want to add the state variables separately so you want to do value={{data:this.state.data}}
if you plan on using these functions in another component you want to add these functions to the value prop as well
remove the async from the functions since there is no Promise to be resolved
export class AppProvider extends React.Component {
constructor(props) {
super(props);
this.state = {
data: []
};
}
getData = () => {
const data = "abc";
this.setState({
data
});
};
updateData = () => {
console.log(this.state.data);
};
render() {
return (
<AppContext.Provider
value={{
data: this.state.data,
getData: this.getData,
updateData: this.updateData
}}
>
{this.props.children}
</AppContext.Provider>
);
}
}
checked this in a small example, CodeSandbox here

Reactjs: How to prevent child from modifying props?

I am running into a strange issue where a component is updating a variable in the parent component that was passed to it as a prop.
The structure looks vaguely like so:
class ParentComponent extends Component {
const toPassToChild = [{ name: 'name', val: 0 }];
...
render() {
return(<ChildComponent p={toPassToChild} />);
}
}
class ChildComponent extends Component {
constructor(props) {
this.state = {
...
arrayOfObjects: this.props.p
}
}
modifyState() {
let aCopy = [...this.state.arrayOfObjects];
let member = aCopy.find(element => {
return element.name === 'name';
});
member.name = 'foo';
this.setState({
arrayOfObjects: aCopy
)};
}
}
When modifyState() is called, the value of toPassToChild is changed in ParentComponent to [{name: 'foo', val: 0}]. Is there any way to stop this? The issue does not occur with other props that are used as initial state, only the prop which is an array of objects.
When you modify member.name in modifyState, you're mutating the original object, since [...this.state.arrayOfObjects] still contains the references to the original objects.
Here's how you can update the array without mutating the original:
modifyState() {
const arrayOfObjects = this.state.arrayOfObjects.map(obj => {
if (obj.name === 'name') {
return { ...obj, name: 'foo' };
}
return obj;
});
this.setState({ arrayOfObjects });
}
I have change the way you copy in the child component. [...] only makes copy of first level elements.
class ParentComponent extends Component {
const toPassToChild = [{ name: 'name', val: 0 }];
...
render() {
return(<ChildComponent p={toPassToChild} />);
}
}
class ChildComponent extends Component {
constructor(props) {
this.state = {
...
arrayOfObjects: this.props.p
}
}
modifyState() {
let aCopy = JSON.parse(JSON.stringify(this.state.arrayOfObjects));
let member = aCopy.find(element => {
return element.name === 'name';
});
member.name = 'foo';
this.setState({
arrayOfObjects: aCopy
)};
}
}
Check the link for working version https://stackblitz.com/edit/react-kxtvoi?file=index.js

In ES6 with react, can I destructure an object just once for use in multiple methods?

import React, { Component } from 'react'
import { connect } from 'react-redux';
export default function(strategies = []) {
class Authentication extends Component {
constructor() {
super()
this.state = {
allGreen: true
}
}
componentWillMount() {
const { history } = this.props
strategies.map(strategy => {
if (!this.props.auth[strategy]) {
this.setState({ allGreen: false })
history.replace('/')
}
})
}
componentWillUpdate(nextProps, nextState) {
const { history } = this.props
strategies.map(strategy => {
if (!nextProps.auth[strategy]) {
this.setState({ allGreen: false })
history.replace('/')
}
})
}
render() {
if (!this.state.allGreen) return (<div></div>)
return this.props.children
}
}
function mapStateToProps(state) {
return {
auth: state.auth
}
}
return connect(mapStateToProps)(Authentication)
}
I'm using 'destructor object' usually in ES6
For example, const { history } = this.props like.
However, I want to know whether there is an efficient way to have just one object destruction, and use it in all of component's method.(componentWillMount, componentWillUpdate ...)
Above picture, I used object destruction twice in componentWillMount method and componentWillUpdate method. ( const { history } = this.props )
I want to destruct object just once! Is there any solution ?
There's not performance issues on using destructurization multiple times (you are not expanding the whole object if this is your fear).
An ES 6 code like::
const aaa = {
a: 5,
b: 'ffff'
}
const { bbb } = aaa;
... is translated to...
var aaa = {
a: 5,
b: 'ffff'
};
var bbb = aaa.bbb;
Try it to https://babeljs.io
So use it, or simply use this.props.history

Why react doesn't call render when state is changed?

I have problem with automatically re-rendering view, when state is changed.
State has been changed, but render() is not called. But when I call this.forceUpdate(), everything is ok, but I think that's not the best solution.
Can someone help me with that ?
class TODOItems extends React.Component {
constructor() {
super();
this.loadItems();
}
loadItems() {
this.state = {
todos: Store.getItems()
};
}
componentDidMount(){
//this loads new items to this.state.todos, but render() is not called
Store.addChangeListener(() => { this.loadItems(); this.forceUpdate(); });
}
componentWillUnmount(){
Store.removeChangeListener(() => { this.loadItems(); });
}
render() {
console.log("data changed, re-render");
//...
}}
You should be using this.state = {}; (like in your loadItems() method) from the constructor when you are declaring the initial state. When you want to update the items, use this.setState({}). For example:
constructor() {
super();
this.state = {
todos: Store.getItems()
};
}
reloadItems() {
this.setState({
todos: Store.getItems()
});
}
and update your componentDidMount:
Store.addChangeListener(() => { this.reloadItems(); });
You sholdn't mutate this.state directly. You should use this.setState method.
Change loadItems:
loadItems() {
this.setState({
todos: Store.getItems()
});
}
More in react docs
In your component, whenever you directly manipulate state you need to use the following:
this.setState({});
Complete code:
class TODOItems extends React.Component {
constructor() {
super();
this.loadItems();
}
loadItems() {
let newState = Store.getItems();
this.setState = {
todos: newState
};
}
componentDidMount(){
//this loads new items to this.state.todos, but render() is not called
Store.addChangeListener(() => { this.loadItems(); this.forceUpdate(); });
}
componentWillUnmount(){
Store.removeChangeListener(() => { this.loadItems(); });
}
render() {
console.log("data changed, re-render");
//...
}}

Categories