React.js Component Child - Global interaction - javascript

Component child in React.js
I am creating an app which can be seen as a crypto-portfolio list. Every coin item has some details like holdings and price. These are saved in a dictionary saved in localstorage and loaded in the portfolio container components' state.
The hierarchy can be seen as Container > PortfolioList > Portfolio Item.
My question is as follows: onClick of the Portfolio Item, I want to populate an entirely different component with an API call of that Portfolio Coin ( to show an historical graph ), called PortfolioDetails.
Question
How is this component - global interaction best handled in React.js? Is it best practice to define a state in the PortfolioContainer and change it upon clicking on the PortfolioItem child (passing it through 2 parents), or is there a way to re-render the PortfolioDetails component onClick, like I tried here?
Thank you!
export default class PortfolioContainer extends React.Component{
constructor(props){
super(props);
this.state = (
{portfolio:
{ICX:{transactions:[{purchase: '$49.99', amount: 50}], price:{raw:{BTC:{PRICE:3.20}}, display:{BTC:{PRICE:3.20}}}},
currency: 'BTC',
total: 0
}
)
}
render(){
return(
<Container>
<Row>
<Col xs="12">
<PortfolioList currency={this.state.currency} portfolio={this.state.portfolio} />
<PortfolioDetails show='portfolio'/>
</Col>
</Row>
</Container>
);
}
}
export class PortfolioList extends React.Component{
constructor(props){
super(props);
}
render(){
const rows = [];
if(Object.keys(this.props.portfolio).length > 0){
Object.keys(this.props.portfolio).map((coin, details) =>{
rows.push(
<CoinRow
coin={coin}
details={this.props.portfolio[coin]}
currency={this.props.currency}
key={coin}
/>
)
});
}
return(
<table>
<thead>
<tr>
<th>Coin</th>
<th>Holdings</th>
<th>Price</th>
</tr>
</thead>
<tbody>
{rows}
</tbody>
</table>
);
}
}
export class CoinRow extends React.Component{
constructor(props){
super(props)
this.handleClick = this.handleClick.bind(this);
}
handleClick(e){
e.preventDefault();
// Get coin ID from clicked event
var coinId = e.currentTarget.attributes['data-id'].value;
// Populate portfolio details with coin details
<PortfolioDetails show={coinId}/>
}
render(){
const coin = this.props.coin;
const details = this.props.details;
var holdings = null;
return (
<tr data-id={coin} onClick={this.handleClick}>
<td>{coin}</td>
<td>{holdings}</td>
<td>{details.price.display[this.props.currency].PRICE}</td>
</tr>
);
}
}
export class PortfolioDetails extends React.Component{
constructor(props){
super(props);
}
render(){
var showing = this.props.show;
return(
<div>
// Showing details for the following coin
<p>{showing}</p>
</div>
);
}
}

The best way to do what you want is using Redux.
This way you would have a flow like this:
Can you see that the UI is defined by the State and there is a unique Store that contains the application State? That's why Redux works nicely in your use case.
You can keep a State in your Store like:
cryptoPorfolio: { // this is the name of the reducer, it will be part of the state tree, read below
coin: ''
}
Now you can update this State firing an action in the 'UI', that will be sent to the Reducer that finally makes the update. Something like this:
Action
{
type: 'UPDATE_COIN',
coinName: 'ETH'
}
Action Creator (just functions that can be triggered by the UI)
function updateCoin(coin) {
return {
type: 'UPDATE_COIN',
coinName: 'Ethereum'
}
}
You can use bindActionCreators from react-redux to pass your action creator to your component.
Then, in your Component, you would call this action creator like a normal function passed as a prop in order to send an action to the reducer.
Finally, in the reducer, you can update the Store. For example:
function cryptoPorfolio(state = initialState, action) {
switch (action.type) {
case UPDATE_COIN:
return Object.assign({}, state, {
coin: action.coinName
})
default:
return state
}
}
For more detailed info, read Redux usage with React.

Yes the best practice would be to have the PortfolioContainer manage the state of what children are hidden/shown. That would be the easiest way to get the whole component tree to update. You can have a handlePortfolioItemClick method on there, which can update the state to show the different data inside PortfolioDetails.

Related

What's the right way to change the state of another component in React

I have two components, one that contains a checkbox and one that's a button. The intent is that this former component let's call it Row, if the checkbox is changed, the edit button would enable itself, and if the checkboxes are not ticked anymore the Edit button disables itself. I was planning on adding onclick event listeners but then I recently read about states and figured this is probably a better idea.
This is my Button:
class EditButton extends React.Component {
constructor() {
super();
this.state = {
clickable: false
}
}
render() {
const {clickable} = this.state
if (clickable) {
return (
<Button className="button-amber">Edit</Button>
)
} else {
return (
<Button disabled>Edit</Button>
)
}
}
}
and this is my Row component
class MyRow extends React.Component {
constructor(props) {
super(props);
this.payload = this.props.payload;
}
render() {
const toRoute = "/foo/" + this.props.payload["id"]
const tags = []
for (const [index, value] of this.props.payload["tags"].entries()) {
tags.push(<Badge>{value}</Badge>)
}
return (
<tr className="white-text">
<td>
<Form.Group>
<Form.Check type="checkbox"/>
</Form.Group>
</td>
<td>
STUFF
</td>
<td>
{this.payload["label"]}
</td>
<td>
{tags}
</td>
<td>
</td>
</tr>
);
}
}
My main app render Might look like so
render(){
<div>
<EditButton/>
<Table>
<MyRow payload=.../>
<MyRow payload=.../>
</Table>
</div>
}
And my intent is if the checkbox is clicked, check the state of all checkboxes to ensure that something is checked, if any checkbox is checked, then change the EditButton clickable state to true. What's the right way to do this? Normally I would use event listeners and individual selectors, but given that I'm using react feels like there should be a more straightforward way to modify the state of that component
There are basically two ways to share state between multiple components:
Shift state into a parent component.
Store the state externally using React Context or a state framework like Redux.
For your specific use case, I would suggest going for the first option. A good way of deciding when to use each option is to decide if the state is local to the sibling components or if it should be globally visible. Meaning that not every bit of your app state needs to be centrally managed.
Using your example, I created this snippet to show how it might work:
class Button extends React.Component {
render() {
const {onClick, disabled} = this.props
return (
<button onClick={onClick} disabled={disabled}>
Button
</button>
)
}
}
class Row extends React.Component {
render() {
const {checked, onChange} = this.props
return (
<input type="checkbox" checked={checked} onChange={onChange} />
)
}
}
class App extends React.Component {
constructor() {
super()
this.state = {
checked: false
}
this.handleCheckboxChange = this.handleCheckboxChange.bind(this)
this.handleButtonClick = this.handleButtonClick.bind(this)
}
handleCheckboxChange(e) {
this.setState({
checked: e.target.checked
})
}
handleButtonClick() {
console.log("Clicked")
}
render() {
const {checked} = this.state
return (
<div>
<Row checked={checked} onChange={this.handleCheckboxChange} />
<Button disabled={!checked} onClick={this.handleButtonClick} />
</div>
)
}
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
<script crossorigin src="https://unpkg.com/react#17/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom#17/umd/react-dom.development.js"></script>
<div id="root"/>
The App component handles the state of both the Row and Button components. You can handle how the child components will modify the parent state by providing callbacks that they will call upon some event, like toggling a checkbox.
React's built in state management is very limited, it's the reason a lot of users like to use Redux to handle more complex state were lots of controls interact. Personally I use my own state management using Proxys.
But you can pass setState's between components by passing them via props, below I've created a var called state that keeps track of the useStates for each component. This then allows independent setState calls between components.
I've also used React hooks here instead of class based, but the concept is the same for both..
function Button({state}) {
const butState = React.useState(false);
const [butEnabled] = butState;
state.butState = butState;
return <button
disabled={!butEnabled}
>Button</button>;
}
function Row({state, row}) {
const rowState = React.useState(false);
const [checked, setChecked] = rowState;
state.rows[row] = rowState;
function toggleChecked() {
state.rows[row][0] = !checked;
state.butState[1](state.rows.some(b => b[0]));
setChecked(state.rows[row][0]);
}
return <div>
<input value={checked} type="checkbox" onChange={toggleChecked}/>
</div>
}
function Page() {
const state = {
rows: []
}
return <div>
<Button state={state}/>
<Row state={state} row={1}/>
<Row state={state} row={2}/>
</div>
}
ReactDOM.render(
<Page/>,
document.querySelector('#mount'));
<script crossorigin src="https://unpkg.com/react#17/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom#17/umd/react-dom.development.js"></script>
<div id="mount"/>

React child component not re-rendering on state change

I realize questions like this have been asked before but from reading several Q&As here it seems like in a lot of cases people are recommending using componentWillUpdate but from my (very) basic understanding of React, if I setState() won't child components re-render if they are affected?
This is my App component (showing the State being set, the function to update the state handleClick, the Display component (which shows the current input from state) and a Button component which shows a number and is passed the function handleClick:
this.State = {
calcValue: 0
}
this.handleClick = this.handleClick.bind(this);
}
handleClick(val) {
this.setState({ calcValue: val })
}
render() {
return(
<div class="calcBody">
<Display currentValue={this.State.calcValue} />
<h1>Calculator</h1>
<div class="numPad">
<Button btn="num col1" operator={1} handleClick={this.handleClick.bind(this)} />
This is the Button component:
class Button extends React.Component {
constructor(props) {
super(props);
}
render() {
return(
/*the button when clicked takes the handleClick function and passes it props based on whatever number is pressed */
<button onClick={() => this.props.handleClick(this.props.operator)}>
<div class={this.props.btn}>{this.props.operator}</div>
</button>
)
}
}
Lastly, this is the Display component:
class Display extends React.Component {
constructor(props){
super(props);
this.props = {
currentValue: this.props.currentValue
}
}
render() {
return(
<h1>{this.props.currentValue}</h1>
);
}
}
I'm wondering why this does not update when handleClick(val) is called?
You're defining state as this.State which is incorrect it should be lowercased: this.state:
this.state = {
calcValue: 0
}
Also, this line:
this.props = {
currentValue: this.props.currentValue
}
doesn't have much sense, as props are passed outside, component shouldn't change them.

Accessing the data from one component to another component

Hi I am starting to learn reactjs. So after understanding the basics Im starting to work on database connectivity using reactjs. In the code Im trying to get the userId and Password to establish a DB connectivity and trying to list the tables available in the DB. In the Login.js I have create a form (userId and Password) when login button is clicked I will make a connectivity and execute the Show Table query to list all the tables in the DB, and move to the Table.js page where I try to list the available tables. Right now I able to connect to the DB but not able to display the tables in the Table.js, so how to display the tables list in the Table.js file, because I have placed my DB connectivity and query inside a button event in the Login.js. Also Is there a possible to declare a variable global and access it across the another js files. Any help would be great, thank you.
Login.js
import React from 'react';
import TableContent from './tables';
class Login extends React.Component{
constructor(){
super();
this.state={
showComponent : false,
};
// this.buttonClick = this.buttonClick.bind(this);
}
buttonClick(event){
event.preventDefault();
this.setState({
showComponent: true,
})
var db = require('#dataBase/dynamoConnect')({
"UserId": "XXXXXXXXXXXXXXXXXXXXXXXX",
"Password": "YYYYYYYYYYYYYYY",
"region": "ZZZZZZZZZZ"
});
db.query("SHOW TABLES",(err,data)=>{
const tableList = data;
console.log(tableList);
})
}
render(){
return(
<div>
<form>
<label>User Id :</label>
<input type="text" className="test"/>
<br/>
<label>Password :</label>
<input type="text" className="test" />
<button onClick={this.buttonClick.bind(this)} className="connect" > Login</button>
</form>
{this.state.showComponent && <TableContent />}
</div>
)
}
}
export default Login;
Table.js
import React from 'react';
class TableContent extends React.Component {
constructor() {
super();
this.state = {
showComponent: false,
};
this.buttonClick = this.buttonClick.bind(this);
}
buttonClick(event) {
event.preventDefault();
this.setState({
showComponent: true,
})
}
render() {
return (
<div>
<form>
<div id="first">
<label> Table </label>
<br />
//Display the tables from DB here
<select name="sometext" multiple="multiple" >
<option>Table1</option>
<option>Table2</option>
<option>Table3</option>
<option>Table4</option>
<option>Table5</option>
</select>
</div>
<div id="second">
<label> SQL </label>
<br/>
<textarea rows="4" cols="50">SQL </textarea>
</div>
<button onClick={this.buttonClick.bind(this)} > Execute </button>
<div id="third" >
{this.state.showComponent && <SampleTable />}
</div>
</form>
</div>
)
}
}
export default TableContent;
First.
The Table.js component need to know the data to display.
1 - you have to save result of the query in component state, by calling this.setState({tableData: tableList}) in query callback:
db.query("SHOW TABLES",(err,data)=>{
const tableList = data;
this.setState({
tableData: tableList,
});
})
2 - you need to pass saved result as a property to TableContent, like this:
in Login.js:
{this.state.showComponent && <TableContent data={this.state.tableData} />};
3 - render data in the child component. You can get access to it via this.props.data. You can iterate over an result array and render all table rows in single loop. Take a look at this react doc.
Second:
Also Is there a possible to declare a variable global and access it across the another js files
In short - yes. You can export functions, variables, classess from your module.
Small example:
// scriptA.js;
export const a = 42;
// scriptB.js;
import { a } from 'path/to/scriptA.js';
console.log(a) // will print 42;
This example assumes you are using es6 import/export feature. You can require it as well.
There are a number of strategies for communicating between components, but the easiest way (without using Flux or Redux) is to use a parent component to act as a mediator for the communication.
Basically, the parent passes a callback to one component that sets some state to pass down the the other component, for example:
Child creating data
class Child1 extends React.Component {
constructor() {
super()
this.handleClick = this.handleClick.bind(this)
}
handleClick() {
this.props.setMessage("hello world")
}
render() {
return <button onClick={this.handleClick}>say hello</button>
}
}
Child using data
const Child2 = ({message}) => {
return <p>{message}</p>
}
Parent
class Parent extends React.Component {
constructor() {
super()
this.state = { message: "" }
}
render() {
return (
<div>
<Child1 setMessage={(message) => this.setState({ message })} />
<Child2 message={this.state.message} />
</div>
)
}
}
If they can't be siblings, this pattern can get a bit strenuous, but can still be achieved by having the mediator component as the lowest common ancestor and passing the relevant props all the way down. At that point though, you may want to investigate Flux or Redux and externalising the state from the components entirely.

What's the proper way to pass dependencies between components in React?

Imagine that Component A creates a list of items that Component B needs to display. What's the proper way to pass data from Component A to Component B from their parent?
For example, let's say that Component A's constructor creates a list of items and has a function _getListItems() that returns that list. I'm hoping the parent can then pass that list on to other components via props.
My naive (non-working) implementation has their parent attempting to render the components like this:
render () {
return (
<div>
<h1>Data Test</h1>
<ComponentA ref='compa'/>
<ComponentB items={this.refs.compa._getListItems()}/>
</div>
);
}
....although the code above doesn't work, I hope it illustrates what I'm trying to do.
ps. nOOb to react and javascript, so forgive me if the answer to my question's obvious...
Divide your components into two separate categories.
Presentational Component that has responsibility to display a thing. This component should not have state (except for UI state).
Container Component that knows the data.
https://medium.com/#dan_abramov/smart-and-dumb-components-7ca2f9a7c7d0#.skmxo7vt4
So, in your case the data should created by parent of ComponentA and ComponentB and pass the data to both ComponentA and ComponentB via props.
Example:
render(){
let items = this._getListItems();
return (
<div>
<ComponentA items={items} />
<ComponentB items={items} />
</div>
);
}
Edit
Rewrite OP's approach in the comment:
class MyContainer extends React.Component {
constructor(props) {
super(props);
this.state = { stuff: [1,2,3] };
}
render() {
return (
<div>
<ComponentA items={this.state.stuff} />
<ComponentB items={this.state.stuff} />
</div>
);
}
}
Following the accepted answer above, I've just had a (related) EUREKA moment, so I'm going to expand on the answer; when the parent uses its own state to pass props to its children, whenever the parent's state changes, its render() function is called, thus updating the children with the updated state. So you can do stuff like this:
class MyContainer extends React.Component {
constructor(props) {
super(props);
let sltd = this.props.selected
this.state = {
stuff: [1,2,3],
selected: sltd
};
}
_handleAStuff(value) {
this.setState(selected: value)
//do other stuff with selected...
}
_handleBStuff(value) {
this.setState(selected: value)
//do other stuff with selected...
}
render() {
return (
<div>
<ComponentA items={this.state.stuff} selected={this.state.selected} parentFunc={this._handleAStuff.bind(this)} />
<ComponentB items={this.state.stuff} selected={this.state.selected} parentFunc={this._handleBStuff.bind(this)} />
</div>
);
}
}
MyContainer.defaultProps = {
selected: 0
}
class ComponentA extends React.Component {
constructor(props) {
super(props)
}
_handleSelect(value) {
this.props.parentFunc(value.label)
}
render() {
const itm = this.props.items.map(function(values) {
return { value: values, label: values}
})
return (
<div>
<Select
options={itm}
value={this.props.selected}
onChange={this._handleSelect.bind(this)}
/>
</div>
);
}
}
// ComponentB...
The callback pattern above means that ComponentA and ComponentB do not need to maintain state, they simply 'render stuff', which is also pretty cool. I'm beginning to see the power of REACT...

Programmatically open a route with state in react

I have two types of item, one of which can contain data similar to the other.
Currently when form is used to save an item it saves it then uses browserHistory.push to show the next page.
But I wish add a button that will
save the currently item
redirect them to the form to add the other item type,
partially fill out this form with the data from the first item.
Is there a way to do this using react and not using local storage or session variables?
You should take a look to Redux (or other Flux based libraries) to store data between components and routes, avoiding the excessive prop nesting.
browserHistory.push won't work. It only moves you to a certain location but it doesn't update the application state. You need to update application state, which then will reflect into location update, but not in the opposite direction. Keep in mind that, in React, data comes first, and its representation, even though mutable, doesn't change the data back. The same applies to the location.
To make the redirect alone work, I'd recommend wrapping your component into withRouter higher-order component.
import React, { Component } from 'react';
import { withRouter } from 'react-router';
class MyComponent extends Component {
render() {
return (
<div>
<button
onClick={() => this.props.router.push('/new-location')}>
Click me to go to /new-location
</button>
</div>
);
}
}
But if you need to pass data from one component to another, and the two aren't in hierarchy, I'd agree with Alomsimoy and recommend using Redux. But if, for some reason, it's not an option, you can store this data in a component that is parent to both forms:
class FormA extends Component {
render() {
return (
<form onSubmit={() => this.props.onSubmit()}>
<input
type="text"
value={this.props.inputA}
onChange={(event) => this.props.handleChangeA(event)} />
</form>
);
}
}
class FormB extends Component {
render() {
return (
<form onSubmit={() => this.props.onSubmit()}>
<input
type="text"
value={this.props.inputB}
onChange={(event) => this.props.handleChangeB(event)} />
</form>
);
}
}
while their parent would rule the location and state updates:
class Forms extends Component {
constructor() {
super();
this.state = {};
}
handleChange(name, value) {
this.setState({
[name]: value
});
}
renderForm() {
const {
params: {
stepId
}
} = this.props;
if (stepId === 'step-a') { // <- will be returned for location /form/step-a
return (
<FormA
inputA={this.state.inputA}
handleChangeA={(event) => this.handleChange('inputA', event.target.value)}
onSubmit={() => this.props.router.push('/form/step-b')} />
);
} else if (stepId === 'step-b') { // <- will be returned for location /form/step-b
return (
<FormB
inputB={this.state.inputB}
handleChangeB={{(event) => this.handleChange('inputA', event.target.value)} />
);
}
}
render() {
const {
children
} = this.props;
console.log(this.state); // track changes
return (
<div>
{this.renderForm()}
<button
onClick={() => this.props.router.push('/new-location')}>
Click me to go to /new-location
</button>
</div>
);
}
}
export default withRouter(Forms);
so the route for them would look like
<Route path="form/:stepId" component={Forms} />

Categories

Resources