React Redux update state from dumb child - reactjs

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.

Related

Show placeholder text in ReactJS input until click then show "typing"

Once I click the text box some of my div will show a "typing." Before clicking, it should show "this is just a sample."
<input type="text" onClick={this.state}>
Although your question is really messy, here's the sample code for you. Maybe it'll point you the right direction.
import React from "react";
export default class Test extends React.PureComponent {
constructor(props) {
super(props);
this.state = {
someState: ""
}
this.onChangeState = this.onChangeState.bind(this);
}
onChangeState(e) {
this.setState({
someState: //logic here
})
}
render() {
const {someState} = this.state;
return (
<>
<input type="text" onClick={e => this.onChangeState(e)}>
State value: {someState}
</>
)
}
}
Whenever you click chosen div, you'll call onChangeState method where you can manipulate your state. Don't forget that every state update causes rerender.
For more info visit official React docs.
if I understood correctly you want to display the word 'Typing' once you click on the div.
I think this will help you
import React from "react";
export default class Typing extends React.PureComponent {
constructor(props) {
super(props);
this.state = {
yourStateName: ""
}
this.onChangeState = this.onChangeState.bind(this);
}
onChangeState(e) {
this.setState({
yourStateName: "Typing"
})
}
render() {
const {yourStateName} = this.state;
return (
<div>
<input type="text" onClick={e => this.onChangeState(e)}>
value: {yourStateName}
<div/>
)
}
}

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

write to local state from props

I understand that the problem is rather trivial, but I can't deal with it, I need your help.
I tried all the solutions in similar questions, but it did not work for me
The bottom line is that when I mount the component, I run fetch and I get a list of articles from my API, but this does not suit me, since I don’t save them in the local state.
Besides, my terrible knowledge of React, I have 2 more problems:
1) When I navigate through the pages, when I return to the articles page, the number of results is duplicated in an arithmetic progression, as I understand it, this is the problem that I keep articles in props, but I need to save it in a local state.
2) From this my second problem expires. I tried everything, but I could not do props.articles -> state.articles, in order to apply this.state.articles.map in the future
//actions
import {FETCH_ALL_ARTICLES} from "../constants";
export const fetchAllArticles = () => {
return (dispatch) => {
let headers = {"Content-Type": "application/json"};
return fetch("/api/articles/", {headers, })
.then(res => {
if (res.status < 500) {
return res.json().then(data => {
return {status: res.status, data};
})
} else {
console.log("Server Error!");
throw res;
}
})
.then(res => {
if (res.status === 200) {
return dispatch({type: FETCH_ALL_ARTICLES, articles: res.data});
}
})
}
};
//component
import React, { Component } from 'react';
import {connect} from 'react-redux';
import {Link} from 'react-router-dom';
import {articles} from "../actions";
class Home extends Component {
constructor(props) {
super(props);
this.state = {
articles: []
}
console.log(this.props.articles)
};
componentDidMount() {
this.props.fetchAllArticles()
};
render() {
return (
<div>
<Link to='/notes'>Notes</Link>
<h2>All articles</h2>
<hr />
<table>
<tbody>
{this.state.articles.map((article, id) => (
<tr key={`article_${id}`}>
<td>{article.headline}</td>
<td>{article.description}</td>
<td>{article.created}</td>
<td>{article.author.username}</td>
<td>{article.image}</td>
</tr>
))}
</tbody>
</table>
</div>
)
}
}
const mapStateToProps = state => {
return {
articles: state.articles,
}
};
const mapDispatchToProps = dispatch => {
return {
fetchAllArticles: () => {
dispatch(articles.fetchAllArticles())
}
}
};
export default connect(mapStateToProps, mapDispatchToProps)(Home);
// reducer
import {FETCH_ALL_ARTICLES} from "../constants";
const initialState = [];
export default function articles(state=initialState, action) {
switch (action.type) {
case FETCH_ALL_ARTICLES:
return [...state, ...action.articles];
default:
return state;
}
}
Your question is unclear but I will try to explain based on the title 'write to local state from props'.
You can utilize component lifecycles as below to achieve that
componentWillReceiveProps(nextProps) {
if (nextProps.articles) {
this.setState({ articles: nextProps.articles });
}
}
Basically whenever there is an update to this component, this lifecycle method componentWillReceiveProps will get invoked before re-rendering, so we can call setState here and save it to local state.
when I return to the articles page, the number of results is duplicated in an arithmetic progression
This should not happened if you handle your reducer correctly. For example, after you fetch articles from API, clear your array then only store the value you receive from API. But then of course it's all depending on what you want to achieve
Every time your component mounts, you fetch all the articles.
When you fetch all the articles, you add them to your existing Redux state:
return [...state, ...action.articles];
To fix this, you can discard the old articles instead of keeping them:
return [...action.articles];
Or you can avoid fetching articles if they have already been fetched:
componentDidMount() {
if (!this.props.articles || this.props.articles.length === 0) {
this.props.fetchAllArticles()
}
};
You don't need to do anything with local state. Your Redux state is your single source of truth. Keeping another copy of the data in local state serves no purpose.
You can render your articles directly from this.prop.articles in render function.
import React, { Component } from 'react';
import {connect} from 'react-redux';
import {Link} from 'react-router-dom';
import {articles} from "../actions";
class Home extends Component {
componentDidMount() {
this.props.fetchAllArticles()
};
render() {
return (
<div>
<Link to='/notes'>Notes</Link>
<h2>All articles</h2>
<hr />
<table>
<tbody>
{this.props.articles.map((article, id) => (
<tr key={`article_${id}`}>
<td>{article.headline}</td>
<td>{article.description}</td>
<td>{article.created}</td>
<td>{article.author.username}</td>
<td>{article.image}</td>
</tr>
))}
</tbody>
</table>
</div>
)
}
}
const mapStateToProps = state => {
return {
articles: state.articles,
}
};
const mapDispatchToProps = dispatch => {
return {
fetchAllArticles: () => {
dispatch(articles.fetchAllArticles())
}
}
};
export default connect(mapStateToProps, mapDispatchToProps)(Home);
Your question is short, but as I understand you are looking to pass the data through props from parent component to child component and then you want to store them into local state.
For that, you need to add constructor into child component and assigned props to state like.
import React,{Component} from 'react';
class ChildComponent extends Component{
constructor(props){
super(props);
this.state={
name: props.name,
email: props.email
}
}
..........
// your child component logic
}
Pass the data through parent component like,
<ChildComponent name={this.state.name} email={this.state.email} />

Multiple instances of a component shouldn't share state

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.

Resources