Reactjs, parent component, state and props - javascript

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.

Related

Change props value outsite react and reload component

As the title says, I want to change value of props and reload component in external js file.
<div data-group=""></div>
//external.js
const popupChat = document.getElementById('popupChatComponent');
popupChat.setAttribute('data-group', groupId);
//component.ts
export default class PopupChatRoot extends React.Component {
private readonly groupId: string;
constructor(props) {
super(props);
this.groupId = this.props.group;
}
render() {
return (
<div className="modal-body">
<p>{this.groupId}</p>
</div>
);
}
}
const component = document.getElementById('popupChatComponent');
if (component) {
const props = Object.assign({}, component!.dataset);
render(<PopupChatRoot {...props}/>, component);
}
How I can do this ?
What you can do is use a wrapper component or higher order component which provides those props to your component, and have that have that wrapper component integrated with your external javascript code.
Here is an HOC I use to do something similar:
export interface MyHocProps {
//the props you want to provide to your component
myProp: any;
}
export const withMyHOC = <T extends any>(params: any) =>
<P extends MyHocProps>(WrappedComponent: React.ComponentType<P>): React.ComponentClass<defs.Omit<P, keyof MyHocProps>> => {
return class extends React.PureComponent<defs.Omit<P, keyof MyHocProps>> {
//here you have access to params, which can contain anything you want
// maybe you can provide some sort of observable which causes this to re-render
render() {
return <WrappedComponent
{...this.props}
myProp={/*whatever*/}
/>;
}
}
};
From here, you would integrate this HOC with some kind of system to push changes to it. I recommend using an observable. Basically you want to have this HOC component subscribe to changes in some piece of observable data, and then force itself to re-render when it changes.
Alternatively, you can just expose some method on your component if it is just a singleton by doing something like window.reloadThisComponent = this.reload.bind(this);, but that should probably be considered a last resort.
It is just a generic example, it might help you to solve your problem. Actually I don't think you can change props of the root node.
// yourEventService.js
class YourEventService {
listeners = []
subscribe = listener => this.listeners.push(listener)
unsubscribe = listener => this.listeners = this.listeners.filter(item => item !== listener)
emit = message => listener.forEach(listener => listener(message))
}
export default new YourEventService() // singleton export
// someWhereElse.js
import yourEventService from './yourEventService'
window.addEventListener('click', () => yourEventService.emit('myNewGroup')) // it's just an event example
//component.js, sorry I don't know how to typescript well
import yourEventService from './yourEventService'
export default class PopupChatRoot extends React.Component {
state = {
groupId: this.props.group; // initial value is passed by props
}
componentDidMount() {
yourEventService.subscribe(this.handleMessage)
}
componentWillUnmount() {
yourEventService.unsubscribe(this.handleMessage)
}
handleMessage = message => {
this.setState({ groupId: message })
}
render() {
return (
<div className="modal-body">
<p>{this.state.groupId}</p>
</div>
);
}
}
const component = document.getElementById('popupChatComponent');
if (component) {
const props = Object.assign({}, component.dataset);
render(<PopupChatRoot {...props}/>, component);
}

Binding Props to State ReactJS

I read on the internet that it is a bad bad coding practice to perform the following:
class Test extends React.Component {
constructor(props) {
super(props);
this.state = {
someItem = props.someItem
};
}
}
However, if I had this scenario instead:
class Test extends React.Component {
constructor(props) {
super(props);
this.state = {
someItem = this.props.someItem
};
}
}
Test.propTypes = {
someItem: PropTypes.array
};
function mapStateToProps(state, ownProps) {
[...]
return {
someItem: someItem
};
}
export default withRouter(connect(mapStateToProps)(Test));
Will there be an issue? Cannot find anywhere on the net that does not say that i cannot do that.
I am trying to ensure that everytime i navigate back to this component, the component is able to get the data from the Redux Store. I tried using componentWillReceiveProps(), but so far it only runs once and componentDidMount and componentWillMount does not accept setState.
Cheers.
You should not save props in state unless and until you would want to modify it at a later point in time locally and update it after some action to the provider of props. In short you state must not be directly derivable from props at all points of time in your code.
Even though you use redux mapStateToProps to provide props to component, its still the same as coming from parent
class Test extends React.Component {
render() {
console.log(this.props.someItem);
}
}
Test.propTypes = {
someItem: PropTypes.array
};
function mapStateToProps(state, ownProps) {
[...]
return {
someItem: someItem
};
}
export default withRouter(connect(mapStateToProps)(Test));

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

Move function in React from component to referenced library

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

React: componentDidMount + setState not re-rendering the component

I'm fairly new to react and struggle to update a custom component using componentDidMount and setState, which seems to be the recommended way of doing it. Below an example (includes an axios API call to get the data):
import React from 'react';
import {MyComponent} from 'my_component';
import axios from 'axios';
export default class Example extends React.Component {
constructor(props) {
super(props);
this.state = {
data: []
};
}
GetData() {
return axios.get('http://localhost:5000/<route>');
}
componentDidMount() {
this.GetData().then(
(resp) => {
this.setState(
{data: resp.data}
)
}
)
}
render() {
return (
<MyComponent data={this.state.data} />
);
}
}
Doing console.log(this.state.data) just below render() shows that this.state.data does indeed get updated (from [] to whatever the API returns). However, the problem appears to be that MyComponent isn't rendered afresh by componentDidMount. From the Facebook react docs:
Setting state in this method will trigger a re-rendering.
This does not seem to be the case here: The constructor of MyComponent only gets called once (where this.props.data = []) and the component does not get rendered again. I'd be great if someone could explain why this is and whether there's a solution or a different way altogether to get the updating done.
UPDATE
I've added the code for MyComponent (minus some irrelevant features, as indicated by ...). console.log(data_array) prints an empty array.
import React from 'react';
class DataWrapper {
constructor(data) {
this._data = data;
}
getSize() {
return this._data.length;
}
...
}
export class MyComponent extends React.Component {
constructor(props) {
super(props);
this._dataWrapper = new DataWrapper(this.props.data);
this.state = {
data_array: this._dataWrapper,
};
}
render() {
var {data_array} = this.state;
console.log(data_array);
return (
...
);
}
}
You are falling victim to this antipattern.
In MyComponent constructor, which only gets called the first time it mounts, passed your empty array through new DataWrapper and now you have some local state which will never be updated no matter what your parent does.
It's always better to have one source of truth, just one state object anywhere (especially for things like ajax responses), and pass those around via props. In fact this way, you can even write MyComponent as a simple function, instead of a class.
class Example extends Component {
state = { data: [] }
GetData() { .. }
componentDidMount() {
this.GetData().then(res =>
this.setState({data: new DataWrapper(res.data)})
)
}
render() { return <MyComponent data={this.state.data} /> }
}
...
function MyComponent (props) {
// props.data will update when your parent calls setState
// you can also call DataWrapper here if you need MyComponent specific wrapper
return (
<div>..</div>
)
}
In other words what azium is saying, is that you need to turn your receiving component into a controlled one. Meaning, it shouldn't have state at all. Use the props directly.
Yes, even turn it into a functional component. This helps you maintain in your mind that functional components generally don't have state (it's possible to put state in them but ... seperation of concerns).
If you need to edit state from that controlled component, provide the functions through props and define the functions in the "master" component. So the master component simply lends control to the children. They want anything they talk to the parent.
I'm not posting code here since the ammendment you need to make is negligible. Where you have this.state in the controlled component, change to this.props.

Categories

Resources