Multiple instances of a component shouldn't share state - reactjs

Say I had a simple inputfieldcomponent like so:
import React, {PropTypes, PureComponent} from 'react';
import {render} from 'react-dom';
import {connect} from 'react-redux';
import updateInput from '../../actions/inputActions';
require('./SimpleInput.sass');
export class SimpleInput extends PureComponent {
constructor(props, context) {
super(props, context);
}
handleChange (event) {
this.props.updateField(event.target.value);
}
renderField () {
return (
<input type="text" value={this.props.value || ''} onChange={this::this.handleChange} placeholder={this.props.initial_value}/>
)
}
render () {
return(
<span>
{this.renderField()}
</span>
)
}
}
const mapStateToProps = (state) => {
return {value: state.value.value}
}
const mapDispatchToProps = (dispatch) => {
return {
updateInput: (value) => dispatch(updateInput(value))
};
};
AddressInput.propTypes = {
initial_value: PropTypes.string
};
AddressInput.defaultProps = {
initial_value: "What's the value?"
};
export default connect(mapStateToProps, mapDispatchToProps)(SimpleInput);
I then render two instances:
</SimpleInput initial_value='blah'/>
</SimpleInput>
However, when this is rendered, any update to one of the two fields updates both of them (due to redux only allowing for a single state).
What is the canonical way to approach this problem?

Here's one approach Abraham and giving an example to what Dan commented on. Check out this jsBin demonstrating the concept of a component (here it's SimpleInput) keeping its own internal state (such as a default placeholder) while still interacting with a Container (listening to onChange). While the example doesn't use Redux (for simplicity of creating the demo) you could easily substitute onChange handles with action dispatchers.
class SimpleInput extends React.Component {
constructor(props) {
super(props);
this.state = {
defaultPlaceholder: 'Default Placeholder'
}
}
render() {
return (
<input
value={this.props.value}
placeholder={this.props.placeholder || this.state.defaultPlaceholder}
onChange={this.props.onChange || ()=>{} }
/>
)
}
}
class SimpleInputContainer extends React.Component {
constructor(props) {
super(props);
this.changeInput1 = this.changeInput1.bind(this);
this.changeInput2 = this.changeInput2.bind(this);
this.state = {
input1: 'foo',
input2: 'bar',
}
}
changeInput1(e) {
this.setState({
input1: e.target.value
})
console.log(this.state);
}
changeInput2(e) {
this.setState({
input2: e.target.value
})
}
render() {
return (
<div>
<SimpleInput value={this.state.input1} onChange={this.changeInput1} />
<br />
<SimpleInput value={this.state.input2} onChange={this.changeInput2} />
<br />
<SimpleInput />
<br />
<SimpleInput placeholder={'Explicit Placeholder'} />
</div>
);
}
}
ReactDOM.render(<SimpleInputContainer />, document.getElementById('app'));
One of my use cases was an address lookup with an input managing it's value internally (no redux) and a lookup button emitting the value of the input via onClick={this.props.onSave} and in the Container handling all side effects/redux actions etc.
Lastly in your demo code, you're rerendering SimpleInput unnecessarily, which looks like a code smell. Hope that helps.

Related

Is there a way to test if a React component is within another component of a particular type?

Let's for a second assume we have 3 components.
export class ComponentA extends React.Component<IComponentAProps, IComponentAState>{
constructor(props: Readonly<IComponentAProps>) {
super(props)
}
render() {
return(
<ComponentB />
);
}
}
export class ComponentB extends React.Component<IComponentBProps, IComponentBState>{
constructor(props: Readonly<IComponentBProps>) {
super(props)
}
render() {
return(
<ComponentC />
);
}
}
export class ComponentC extends React.Component<IComponentBProps, IComponentBState>{
constructor(props: Readonly<IComponentBProps>) {
super(props)
}
render() {
return(
<ComponentA />
);
}
}
Now obviously this is going to cause an infinite loop. Is there a way for me to check in ComponentC to see whether or not it is ultimately contained within an instance of ComponentA?
I'm not sure this is what you need, but you can achieve this using a React context.
import React, { createContext, FunctionComponent, useContext, useEffect } from 'react';
export const TopDogContext = createContext<string>('');
// The top dog can never be nested!
export const TopDog: FunctionComponent = ({ children }) => {
const aboveMe = useContext(TopDogContext);
useEffect(() => {
if (aboveMe) {
setTimeout(() => alert('Yo, you can\'t nest me!'));
throw new Error('Yo, you can\'t nest me!');
}
}, [aboveMe]);
return (
<TopDogContext.Provider value={'I\'m the top dog!'}>
{ children }
</TopDogContext.Provider>
)
};
// -------
import { TopDog } from './top-dog';
function App() {
<TopDog>
<div className="App">
[... YOUR APP HERE ...]
{/* Will create an error if uncommented */}
{/* <TopDog /> */}
</div>
</TopDog>
}
Note that you can still have multiple <TopDog>, but they will never have one being the ancestor of another.

Not sure if i'm using react context correcly

I've created a form in react and after some research i think that if you don't want to use an external library to manage the form, the context could be the best choice, expecially in my case where i've many nested component that compose it.
But, i'm not sure that putting a function inside my state is a good thing.
But let me give you some code:
configuration-context.js
import React from 'react'
export const ConfigurationContext = React.createContext();
ConfigurationPanel.jsx
import React, { Component } from 'react'
import { Header, Menu, Grid } from 'semantic-ui-react'
import ConfigurationSection from './ConfigurationSection.jsx'
import {ConfigurationContext} from './configuration-context.js'
class ConfigurationPanel extends Component {
constructor(props) {
super(props)
this.state = {
activeItem: '',
configuration: {
/* the configuration values */
banana: (data) => /* set the configuration values with the passed data */
}
}
}
handleItemClick = (e, { name }) => this.setState({ activeItem: name })
render() {
return (
<ConfigurationContext.Provider value={this.state.configuration}>
<Grid.Row centered style={{marginTop:'10vh'}}>
<Grid.Column width={15} >
<div className='configuration-panel'>
/* SOME BUGGED CODE */
<div className='configuration-section-group'>
{this.props.data.map((section, i) => <ConfigurationSection key={i} {...section} />)}
</div>
</div>
</Grid.Column>
</Grid.Row>
</ConfigurationContext.Provider>
)
}
}
ConfigurationItem.jsx
import React, { Component } from 'react'
import { Input, Dropdown, Radio } from 'semantic-ui-react'
import {ConfigurationContext} from './configuration-context.js'
class ConfigurationItem extends Component {
static contextType = ConfigurationContext
constructor(props) {
super(props)
}
handleChange = (e, data) => this.context.banana(data)
itemFromType = (item) =>{
switch (item.type) {
case "toggle":
return <div className='device-configuration-toggle-container'>
<label>{item.label}</label>
<Radio name={item.name} toggle className='device-configuration-toggle'onChange={this.handleChange} />
</div>
/* MORE BUGGED CODE BUT NOT INTERESTING*/
}
}
render() {
return this.itemFromType(this.props.item)
}
}
So, at the end i've a ConfigurationContext that is just a declaration, everything is inside the parent state.
The thing that i don't like is putting the banana function inside the state (it will have more logic that just logging it)
What do you think about it?
Any suggestion is appreciated.
Thanks
banana is just a regular function and you do not have to put it in the state, just do:
class ConfigurationPanel extends Component {
banana = data => console.log(data)
...
render() {
return (
<ConfigurationContext.Provider value={{banana}}>
...
}
After that you can use this.context.banana(data) as normal.

Accessing variable from imported class from another React script

I'm importing a class from another script in my main React App, and would like to access a variable within that class from the main App. Basically the user types something into a textbox, then clicks a button to add that value to a variable. In the main App I import that class, then have another button to print those values (selectedvalues). I'm not entirely sure how to do it, but this is my code so far:
Class I am importing:
import React, { Component } from 'react';
class MyModule extends Component {
constructor() {
super();
this.state = {
selectedValues: '',
}
}
addValue() {
this.selectedValues += document.getElementById('textBox1').value + ', '
return this.selectedValues
}
render() {
return(
<div>
<input type='text' id='textBox1' />
<button onClick={() => this.addValue()}>Add Value</button>
</div>
)
}
}
export default MyModule
And where I would like to actually access that value
import React, { Component } from 'react';
import MyModule from './myModule.js'
class App extends Component {
constructor() {
super();
this.state = {
}
}
printValues() {
console.log(document.getElementById('themodule').selectedvalues)
}
render() {
return(
<MyModule id='themodule' />
<button onClick={() => printValues()}>Print values</button>
)
}
}
export default App
Is there a way I can do this?
Thanks!
Edit JS-fiddle here https://jsfiddle.net/xzehg1by/9/
You can create Refs and access state and methods from it. Something like this.
constructor() {
this.myRef = React.createRef();
}
render() { ... <MyModule id='themodule' ref={this.myRef} /> }
printValues() {
console.log(this.myRef)
}
more info here https://reactjs.org/docs/refs-and-the-dom.html
Basically, your state (selectedValues) has to go one level up in the React tree. You have to declare it as App's state, and then pass it down to MyModule via props.
Btw in addValue(), you're not changing any state. And this.selectedValues will be undefined. It's this.state.selectedValues, and this.props.selectedValues once you correct your code.
I think you should first read all react concepts and then start working on it. Anyhow i am modifying your code in one way to get your desired functionality but remember this is not best practice you have to use Redux for this kind of features
import React, { Component } from 'react';
class MyModule extends Component {
constructor() {
super(props);
this.state = {
inputValue : ''
};
this.handleInput = this.handleInput.bind(this);
this.addValue = this.addValue.bind(this)
}
handleInput(e){
this.setState({
inputValue : e.target.value
})
}
addValue() {
this.props.addValue(this.state.inputValue);
}
render() {
return(
<div>
<input type='text' id='textBox1' onChange={handleInput} />
<button onClick={this.addValue}>Add Value</button>
</div>
)
}
}
export default MyModule
and your main component should be
import React, { Component } from 'react';
import MyModule from './myModule.js'
class App extends Component {
constructor() {
super(props);
this.state = {
selectedValues : ''
};
this.printValues = this.printValues.bind(this);
this.addValues = this.addValues.bind(this);
}
printValues() {
console.log(this.state.selectedValues);
}
addValues(val){
this.setState({
selectedValues : this.state.selectedValues + " , "+val
})
}
render() {
return(
<React.Fragment>
<MyModule addValue={this.addValues}/>
<button onClick={this.printValues} >Print values</button>
</React.Fragment>
)
}
}
export default App
This should do your work

React Redux update state from dumb child

Is there any way to update parent state from dumb child component? I have grid layout which has children (headers and body) and i want fo use headers as filters with input. I connect my grid to db with parent component a pass all my data down to body and headers, but i want to get filters from headers to connected smart component. Only way i found is to connect GridHeader to store and pass data directly to it.
Problem with my solutoin is that i want to use my grid in diferent pages with diferent data types so i need all my grid components to be dumb. With my solution i have to write specific GridHeader for every data/table type and connect every header to different store key. One would be connected to catalog: store.catalog second would be users: store.users etc...
My solution its not DRY and i dont like it :/ Please help
Here is my GridHeader:
import React from 'react';
import { connect } from 'react-redux';
import { filterCatalog, setFilters } from '../../redux/actions/catalogActions';
#connect((store) => {
return {
catalog: store.catalog
};
})
export default class GridHeader extends React.Component {
constructor(props) {
super(props);
this.handleChange = this.handleChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
}
handleChange(e) {
const name = e.target.name;
let filters = {
[name]: e.target.value
}
this.props.dispatch(setFilters(filters))
}
handleSubmit(e) {
if (e.which == 13){
e.target.blur();
//console.log('submit', this.props.catalog.filters)
this.props.dispatch(filterCatalog(this.props.catalog.filters))
}
}
render() {
const headerItem = this.props.headers.map((x) => {
return (
<th class={x.class} key={x.name}>
<div class="input-field">
<input type={x.type} id={x.id} name={x.name} placeholder={x.placeholder} class={x.class} disabled={x.disabled} onChange={this.handleChange} onKeyPress={this.handleSubmit}/>
<label for={x.name}>{x.label}</label>
</div>
<a class="filter-button hidden">
<i class="material-icons">backspace</i>
</a>
</th>
)
});
return (
<thead>
<tr>
{headerItem}
</tr>
</thead>
);
}
}
You could create wrapper component which will be provide data for GridHeader. For example:
First usage
#connect((store) => {
return {
catalog: store.catalog
};
})
export default class FirstWrapperGridHeader extends React.Component {
render() {
<GridHeader {...props} />
}
}
Second usage:
#connect((store) => {
return {
catalog: store.anotherData
};
})
export default class SecondWrapperGridHeader extends React.Component {
render() {
<GridHeader {...props} />
}
}
Your GridHeader should be not connected to redux store,should be just dumb.

Change state of TextField by TextField orther

I have 2 TextField in 2 components.
When a TextField change value, how I can send and change value of TextField of remaining?
This is example for my issue. This is my issue.
I have url http://localhost:8000/search?search=php&category=catqgkv4q01ck7453ualdn3sd&page=1
Search page Js:
class SearchPage extends Component {
constructor(props) {
super(props);
let search = typeof this.props.location.query.search !== '' ? this.props.location.query.search : '';
if(search){
this.props.dispatch(setTextSearch(search));
}
};
render() {
return (
<MuiThemeProvider>
<div id='search-page'>
<SearchTextBox textSearch={this.props.textSearch}/>
</div>
</MuiThemeProvider>
)
}
}
// Retrieve data from store as props
function mapStateToProps(state) {
return {
textSearch: getTextSearch(state)
}
}
SearchPage.contextTypes = {
router: React.PropTypes.object
};
export default connect(mapStateToProps)(SearchPage);
Search Action:
import callApi from '../../util/apiCaller';
// Export Constants
export const ACTIONS = {
SET_TEXT_SEARCH: 'SET_TEXT_SEARCH'
};
export function setTextSearch(search) {
return {
type: ACTIONS.SET_TEXT_SEARCH,
search
};
}
Search Reducer:
import { ACTIONS } from './SeachActions';
// Initial State
const initialState = {
textSearch: '',
};
const SearchReducer = (state = initialState, action) => {
switch (action.type) {
case ACTIONS.SET_TEXT_SEARCH:
state.textSearch = action.search;
return {...state};
default:
return state;
}
};
/* Selectors */
export const getTextSearch = state => state.categories.textSearch;
// Export Reducer
export default SearchReducer;
I have component SearchTextBox
import React from 'react';
import TextField from 'material-ui/TextField';
export default class SearchTextBox extends React.Component {
constructor(props) {
super(props);
this.state = {
value: this.props.textSearch,
};
};
render() {
return (
<TextField
hintText="Search"
className="search-txtbox"
ref='searchText'
style={{height : '40'}}
underlineShow={false}
value={this.state.value}
onChange={this.handleChange}
autoFocus
onKeyPress={this.handleEnter}
/>
);
}
}
How can I change value by data parameter "search" on URL
So your problem seems to be about sharing the same data with other components ( passing some kind of data to each other, not only the react component state, could be anything ).
You should be aware of the available ways to communicate data between components.
1 - Props
2 - Context
3 - Global variables ( anti - pattern until you really need, when you need you should use redux or similar that makes use of both props and context to create a big global data tree without creating global variables )
So there is no other way to communicate data between components.
Then since we know available options, second question becomes how are the component I want to communicate data between positioned relative to each other.
1 - One is the direct parent of another.
2 - One is the indirect parent of another.
3 - Both share the same parent.
Assuming your TextFields share the same parent, luckily, here is a working code for you to get the idea.
const TextField = ({
value = '', handleInputChange = ''
}) => <input type="text" value={value} onChange={ handleInputChange }/>
class ParentC extends React.Component {
state = {
sharedInputValue : ''
}
constructor(props){
super(props)
this.state = {
sharedInputValue : ''
}
this.handleInputChange = this.handleInputChange.bind(this)
}
handleInputChange( event ){
this.setState({ sharedInputValue : event.target.value})
}
render(){
return (
<div>
<TextField
value={ this.state.sharedInputValue }
handleInputChange={ this.handleInputChange }
/>
<TextField
value={ this.state.sharedInputValue }
handleInputChange={ this.handleInputChange }
/>
</div>
)
}
}

Resources