React Class Component using Functional Component cannot update state with passed function - javascript

I'm tinkering with a React application where I have a Class component and a Functional Component. My Class component has a function for updating a name in its state. I'm trying to pass this function to a Functional Component as a property, the invocation works fine and I can see the argument making it into the method while debugging the application in a browser, but things blow up when trying to invoke this.setState, because 'this' is 'undefined'. I know about React hooks but don't think they are able to deal with a parent components state. Is there a technique for handling this kind of situation?
Note: the below snippets are pseudo code so just assume there's a button and clicking it works
The Class Component
class MyClassComponent extends React.Component {
constructor(props) {
super(props);
this.setName = this.setName.bind(this);
this.state = {
name: '',
};
}
setName(name) {
this.setState({
name: name
});
}
render() {
return (
<MyFunctionalComponent
setName={this.setName}/>
);
}
}
The Functional Component
export const MyFunctionalComponent = props => {
const { setName } = props;
return (
<button onClick={setName('fred')}>
);
};
The exception
Uncaught TypeError: Cannot read property 'setState' of undefined

The issue in your code is in MyFunctionalComponent component. you are calling the setName function on component render.
Try this.
export const MyFunctionalComponent = props => {
const { setName } = props;
return (
<button onClick={() => setName('fred')}>
);
};
Working Example:
function MyFunctionalComponent(props) {
const { setName } = props;
return <button onClick={() => setName("fred")}>Click me</button>;
}
class MyClassComponent extends React.Component {
constructor(props) {
super(props);
this.setName = this.setName.bind(this);
this.state = {
name: ""
};
}
setName(name) {
this.setState({
name: name
});
}
render() {
return (
<div>
<MyFunctionalComponent setName={this.setName} />
<br />
{this.state.name}
</div>
);
}
}
function App() {
return <MyClassComponent />;
}
ReactDOM.render(<App />, document.getElementById('root'))
<script crossorigin src="https://unpkg.com/react#16/umd/react.production.min.js"></script>
<script crossorigin src="https://unpkg.com/react-dom#16/umd/react-dom.production.min.js"></script>
<div id="root"></div>

Related

How to properly render Component after this.setState in React

I have this React component
import React, { Component } from "react";
export default class ResourceForField extends Component {
constructor() {
super();
this.state = {
resources: [],
};
}
componentDidMount() {
// get the resources from the Link props and save it into the state
this.setState({
resources: this.props.location.resources,
});
}
// This component gets the id of current learningField from the url
// and the rest(like the resources) from the Link component
render() {
return (
<div>
{this.state.resources.map(res => (
<div>test</div>
))}
</div>
);
}
}
It gets the resources from the Link component, and that works fine. If I check out the state of the Component from the dev tools, the state looks right. And I thought with my logic this should work. So firstly, the state is empty, the component gets rendered, since the state is empty it doesn't render any components. Then, setState gets called, it gets all the resources and saves them into the state, and then the component would re-render, and it should work, but it doesn't. I'm getting a TypeError: Cannot read property 'map' of undefined error. What is the correct way to do this and how do I fix this?
Try this code:
import React, { Component } from "react";
export default class ResourceForField extends Component {
constructor() {
super();
this.state = {
resources: this.props && this.props.location && this.props.location.resources?this.props.location.resources:[],
};
}
componentDidMount() {
}
// This component gets the id of current learningField from the url
// and the rest(like the resources) from the Link component
render() {
return (
<div>
{this.state.resources.map(res => (
<div>test</div>
))}
</div>
);
}
}
Or use directly props
import React, { Component } from "react";
export default class ResourceForField extends Component {
constructor() {
super();
}
// This component gets the id of current learningField from the url
// and the rest(like the resources) from the Link component
render() {
return (
<div>
{
this.props && this.props.location &&
this.props.location.resources
?this.props.location.resources.map(res => (
<div>test</div>
))
:null
}
</div>
);
}
}
Or use componentWillReceiveProps or getDerivedStateFromProps life cycle methods.
Check this.props.location.resources is array.
See more: https://hackernoon.com/replacing-componentwillreceiveprops-with-getderivedstatefromprops-c3956f7ce607
For first check is this.props.location.resources array, or if data type changes you can add checking, you can use lodash isArray or with js like this:
import React, { Component } from "react";
export default class ResourceForField extends Component {
constructor() {
super();
this.state = {
resources: [],
};
}
componentDidMount() {
// get the resources from the Link props and save it into the state
Array.isArray(this.props.location.resources) {
this.setState({
resources: this.props.location.resources,
});
}
}
// This component gets the id of current learningField from the url
// and the rest(like the resources) from the Link component
render() {
return (
<div>
{this.state.resources.map(res => (
<div>test</div>
))}
</div>
);
}
}
Or you can just use hooks like this:
import React, { useState, useEffect } from "react";
export default function ResourceForField({location}) {
const [ resources, setResources ] = useState([]);
useEffect(() => {
if (location && Array.isArray(location.resources)) {
setResources(location.resources)
}
}, [location]);
return (
<div>
{resources.map(res => (
<div>test</div>
))}
</div>
);
}
If the internal state of ResourceForField doesn't change and always equals to its prop, you shouldn't save the prop in the state. You can instead create a pure functional component.
Also note that there's nothing preventing you from initializing the state from the props in constructor method. i.e. you're not required to wait for the component to mount in order to access the props.
So, I'd write the following component for ResourceForField:
function ResourceForField({resources = []}) {
return (
<div>
{
resources.map(res => (<div>test</div>))
}
</div>
);
}

React: parent component props in child without passing explicitly

Is it possible to have the props of the parent component to be available in child component without passing them down?
I am trying to implement a provider pattern, so that to access all the provider props in its child components.
EX:
Suppose the below provider comp FetchProvider will fetch the data and theme props on its own, and when any child component is enclosed by it, I want to access both props "data" and "theme" in the child component as well. How can we achieve it?
class FetchProvider
{
proptypes= {
data: PropTypes.shape({}),
theme: PropTypes.shape({})
}
render()
{
// do some
}
mapStateToProps()
{
return {data, theme};
}
}
class ChildComponent
{
proptypes= {
name: PropTypes.shape({})
}
render()
{
const{data, them} = this.props; // is this possible here?
// do some
}
}
and if I try to above components as below.
<FetchProvider>
<ChildComponent name="some value"/> //how can we access parent component props here? without passing them down
<FetchProvider/>
This is exactly what react context is all about.
A Consumer can access data the a Provider exposes no matter how deeply nested it is.
// Context lets us pass a value deep into the component tree
// without explicitly threading it through every component.
// 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(props) {
return (
<div>
<ThemedButton />
</div>
);
}
function ThemedButton(props) {
// Use a Consumer 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".
return (
<ThemeContext.Consumer>
{theme => <Button {...props} theme={theme} />}
</ThemeContext.Consumer>
);
}
Here is a small running example:
Note This is the react v16 context API.
Your use case can be solved with the usage of React context. With the help of Context, any child that is wrapped by a provided can be a consumer for the data that is provided by the Provider
In your case, you can use it like
context.js
export const FetchContext = React.createContext();
Provider.js
import { FetchContext } from 'path/to/context.js';
class FetchProvider extends React.Component
{
proptypes= {
data: PropTypes.shape({}),
theme: PropTypes.shape({})
}
render()
{
const { data, theme, children } = this.props;
return (
<FetchContext.Provider value={{ data, theme}}>
{children}
</FetchContext.Provider>
)
}
mapStateToProps()
{
return {data, theme};
}
}
ChildComponent.js
class ChildComponent extends React.Component
{
proptypes= {
name: PropTypes.shape({})
}
render()
{
const{data, them} = this.props; // use it from props here
// do some
}
}
export default (props) => (
<FetchContext.Consumer>
{({ data, theme }) => <ChildComponent {...props} data={data} theme={theme} />}
</FetchContext.Consumer>
)
However given the fact that you are already using Redux, which is build on the concept of Context, you might as well use redux and access the values within the child component since they are the same values that are supplied from the Redux store to the child by parent.
class ChildComponent extends React.Component
{
proptypes= {
name: PropTypes.shape({})
}
render()
{
const{data, them} = this.props; // use it from props here
// do some
}
}
const mapStateToProps = (state) => {
return {
data: state.data,
theme: state.theme
}
}
You can use React.Children to iterate over the children and pass whatever props you want to send to the new cloned elements using React.cloneElement.
EX:
class Parent extends React.Component {
constructor(props) {
super(props);
}
render() {
const { children } = this.props;
const newChildren = React.Children.map(children, child =>
React.cloneElement(child, { myProp: 'test' }));
return(
<View>
{newChildren}
</View>
)
}
}
Are you looking for:
class MyParent extends Component {
render() {
return <MyChild {...this.props}>
// child components
</MyChild>
}
}
This would pass all of the props passed into MyParent to the MyChild being rendered.

React JS - Passing functions for child components

I have an App component and a function 'modalToggled' inside its.
I want to pass the function to multiple child components until I get to the last one, the 'interiores' component.
Like this:
<App> -> <Coluna1> -> <MenuPrincipal> -> <Portfolio> -> <PortfolioMenu> -> <interiores>
App Component, the parent of all components:
import React, { Component } from 'react';
import Coluna1 from './Coluna1'
class App extends Component {
constructor(props) {
super(props)
this.state = {
modalOn: false
}
this.modalToggled = this.modalToggled.bind(this)
}
modalToggled = (on) => {
this.setState({modalOn: on});
}
render() {
return (
<div>
<Coluna1 onModalToggle={this.modalToggled}/>
</div>
)
}
}
export default App;
This is the 'Coluna1' the first child component. I did the same thing in the another ones: 'MenuPrincipal', 'Portfolio', 'PortfolioMenu'
class Coluna1 extends Component {
constructor(props){
super(props)
}
render() {
return (
<div>
<Header />
<MenuPrincipal onModalToggle={this.props.modalToggled} />
</div>
)
}
}
export default Coluna1
Therefore here is the last component interiores, when I click on the button there appears an error message:
TypeError: _this.props.onModalToggle is not a function
import React, { Component } from 'react'
import Modal from 'react-responsive-modal';
class Interiores extends Component {
constructor(props) {
super(props)
this.state = {
open: false
}
}
onOpenModal = () => {
this.setState({ open: true });
this.props.onModalToggle(true);
};
onCloseModal = () => {
this.setState({ open: false });
this.props.onModalToggle(false);
};
render() {
const { open } = this.state;
return (
<div>
<button onClick={this.onOpenModal}>Open modal</button>
<Modal open={open} onClose={this.onCloseModal} center></Modal>
</div>
)
}
}
export default Interiores;
Does anybody know how to solve it? Thank you
It happens, because in App class you pass prop with name onModalToggle:
<Coluna1 onModalToggle={this.modalToggled}/>
But in Coluna1 you receive this props with wrong name, modalToggled:
<MenuPrincipal onModalToggle={this.props.modalToggled} />
Just make the names of props equal. In Coluna1 and other intermediate components pass and receive this props as onModalToggle:
<MenuPrincipal onModalToggle={this.props.onModalToggle} />
This is the problem
modalToggled = (on) => {
this.setState({modalOn: on});
}
Since this is a class function it needs to be defined like
modalToggled(on) {
this.setState({modalOn: on});
}

How to use composition in ReactJs

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

setState/use State in external function react

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

Categories

Resources