How to know where to add states in react.js - reactjs

New to react and did not know how to structure a google search for this so decided to ask it here. Was taking a react tutorial and the instructor did this:
#App.js
import React, { Component } from 'react';
import Ninjas from './Ninjas.js'
class App extends Component {
state = {
ninjas : [
{ name: 'Ryu', age:30, belt:'black', id:1 },
{ name: 'Jacy', age:34, belt:'yellow', id:2 },
{ name: 'Ikenna', age:20, belt:'green', id:3 },
{ name: 'Cole', age:50, belt:'red', id:4 }
]
}
render() {
return (
<div className="App">
<p>My First React App</p>
<hr/>
<Ninjas ninjas={ this.state.ninjas } />
</div>
);
}
}
export default App;
#Ninjas.js
import React, { Component } from 'react';
const Ninjas = (props) => {
const { ninjas } = props;
const ninjaList = ninjas.map(ninja => {
return (
<div className="ninja" key={ ninja.id }>
<div>Name: { ninja.name }</div>
<div>Age: { ninja.age }</div>
<div>Belt: { ninja.belt }</div>
<hr/>
</div>
)
})
return(
<div className="ninja-list">
{ ninjaList }
</div>
)
}
export default Ninjas
But then I tried this and it gave the same result:
#App.js
import React, { Component } from 'react';
import Ninjas from './Ninjas.js'
class App extends Component {
render() {
return (
<div className="App">
<p>My First React App</p>
<hr/>
<Ninjas />
</div>
);
}
}
export default App;
#Ninjas.js
class Ninjas extends Component {
state = {
ninjas : [
{ name: 'Ryu', age:30, belt:'black', id:1 },
{ name: 'Jacy', age:34, belt:'yellow', id:2 },
{ name: 'Ikenna', age:20, belt:'green', id:3 },
{ name: 'Cole', age:50, belt:'red', id:4 }
]
}
render() {
const ninjaList = this.state.ninjas.map(ninja => {
return(
<div className="ninja" key={ ninja.id }>
<div>Name: { ninja.name }</div>
<div>Age: { ninja.age }</div>
<div>Belt: { ninja.belt }</div>
<hr/>
</div>
)
})
return (
<div className="ninja-list">
{ ninjaList }
</div>
)
}
}
export default Ninjas
Why did he put the state in the parent App component and not in the nested Ninjas component?
And how do you know when to pass data down as props and not use it as a state in the component that needs the data?

First of all, congratulations on noticing this ;) You're 1 step closer to React Thinking
In your example, it doesn't make a difference whether ninjas state lives in App, or in <Ninjas/> component. It only matters when this app grows more complicated.
Smart Container vs Dumb Component
The tutorial example is building <Ninjas/> as a dumb/presentational component, which is why it did not use class, but was written as a Stateless Functional Component. It is merely used for displaying data in certain way.
But why? Because we might want to reuse <Ninjas/> component with different data set.
In an actual app, most likely you wouldn't hardcode the ninja's data as state. What usually happen is, a smart container (in this case, App) will make API call to backend server to retrieve all the ninja data, then save them as state.
Using the tutorial's structure, you now have the flexibility to:
Pass down ninjas state to other components that might need the data. For example, a <BeltCount/> component that displays the count for each belt color. Not the best example, but the point here is reusability.
<Ninjas> components can be reused as well! Now that it doesn't have any hardcoded state in it, anyone can reuse <Ninjas> by passing down different ninjas props.

In your second example you are passing an undefined state.ninjas it has no effect whatsoever. The reason why your second example works is because you define the state with the props from the first example.
Try to call it like in the first example with const { ninjas } = props and it won't work anymore.
The reason why you would take the first approach is that you can define an arbitrary list of ninjas while in the second one you have always the same ninjas.

I would like to answer the specific part:
how do you know when to pass data down as props and not use it as a state in the component that needs the data?
It probabaly is because the data in the state is being used / manipulated by some other elements as well. Example could be 'sort'/ 'delete' etc.
As a general rule, you should keep your state as local as possible, i.e, where the state data is being used. Think of the concept encapsulation.
Hope that helps.

With the example as is, there isn't any compelling reason for the state to be at the App level. I would expect that as the tutorial progresses and the example gets more complicated (state being changed in some manner and potentially used/displayed by multiple components), that the reasons for the state being where it is will become more clear.

There are two types of components in React: Container Component and Presentation Component.
Container component is the top level component and has the information about state(and other advanced things like Redux store etc.).
Presentation component are only responsible for representing your content.
The instructor has used functional component for your 'Ninjas' class and it accepts props from the top layer. This is the standard practice in React and I would recommend to follow it. As you progress in your learning, you will better understand why only top level component needs to have the knowledge of state. Good luck!

Related

Is it safe to pass mobx observable objects as props to react components

Say, I've a Product model which has 10+ attributes and some of them are also objects .
{
"product": {
"name": "shoe",
"brand": "....",
"categories": {
"abc": {
...
}
},
...
}
}
I need to update the Product in a react component, however, product also has some children to be viewed/changed by other components.
#observer
class ProductContainer extends Component {
#observable product;
render() {
return (
<ProductDetails product={product} />
)
}
}
#observer
class ProductDetails extends Component {
static propTypes = {
product: PropTypes.any
}
#autobind
#action
handleChangeName(event) {
const {product} = this.props;
product.name = event.target.value;
}
render() {
const {product} = this.props;
return (
<form>
<label>Name: <input type="text" onChange={this.handleChangeName} /> </label>
<CategoryView categories={product.categories} />
</form>
);
}
}
As seen in the example I've inner components such as CategoryView to which I pass an observable object's child (which is observable as well).
What I do in the example is I do not change the product prop reference but change it's children and attributes (such as name).
React says that props should be immutable, however the example below works just fine. Is it safe to do that tho?
That's totally fine with MobX, it is the right way.
Sometimes you don't want to change attributes like this, but create some sort of Model wrapper around your data (something like https://github.com/mobxjs/mobx-state-tree or its alternatives) and use Model methods to change stuff. But your example is ok too.
Although you need to wrap ProductDetails with observer decorator so it could react to changes. (maybe you just omitted it in your example)
It looks okay. Just remember to dereference values as late as possible.

Reactjs: Fade-in data not working properly

I have a button in App Component if i click in this button i get next item in array in the Component but the problem now the Fade-in Transition work only the fist item and not work's for the next item. how can i let Fade-in Transition work for next items?
My code:
import React, { Component } from 'react';
import FadeIn from 'react-fade-in';
class App extends Component {
constructor(props){
super(props);
this.state={indexTeam:0}
}
nextTeam=() => {
this.setState({ indexTeam: (this.state.indexTeam + 1) % teamList.length });
};
render() {
const teams = teamList[this.state.indexTeam];
return (
<div>
<FadeIn><h3>{teams.name}</h3></FadeIn>
<br/>
<button onClick={this.nextTeam}>Next Team</button>
</div>
);
}
}
const teamList = [
{
name: "1- Manchester United"
},
{
name: "2- Fc Barcelona"
},
{
name: "3- Inter Milan"
},
{
name: "4- Liverpool"
}
];
export default App;
Don't use that library. It does exactly that it should, fade in elements one by one when component (page in your case) mounts, but you need your transition on each rerender
If you will look through library that you are using (react-fade-in), you will notice that it reinits it's state on componentDidMount, so it doesn't work when you set state (so, just rerender it, not unmount and mount again).
I didn't come up with any fast solution how to fix or rewrite this library, so just think about yours.
Look through their realization (Which is simply based on css transition) and create your solution.
react-fade-in:
https://github.com/gkaemmer/react-fade-in/blob/master/src/FadeIn.js

Reactjs - passing state value from one component to another

I have two components SideNav and Dashboard (two are in different js files). SideNav will have selectbox as filters. I have to pass an array from Dashboard component to Sidebar component. This array has to given as values for select box (which is inside sidenav component).
P.S. What will be the case if I have two different component classes defined in two different JS files.
e.g. HomeComponent/Home.js -> Parent component
Dashboard/Dashboard.js -> Child component
I am making API call on "Home.js" file and getting some data. I want to pass these data to "Dashboard.js" file (component)
All the examples I studied, they show two components in the same JS file.
class Dashboard extends Component {
constructor(props) {
super(props);
this.state = {viz:{},filterData:{}};
}
var data1= ['1','2','3'];
this.setState({data1: data1}, function () {
console.log(this.state.data1);
});
}
//Sidebar
class Sidebar extends Component {
constructor(props) {
super(props);
this.state = {
data: ['opt1','opt2']
};
}
handleClick(e) {
e.preventDefault();
e.target.parentElement.classList.toggle('open');
this.setState({data: this.state.data1}, function () {
console.log(this.state.data);
});
}
render() {
const props = this.props;
const handleClick = this.handleClick;
return (
<div className="sidebar">
<nav className="sidebar-nav">
<Nav>
<li className="nav-item nav-dropdown">
<p className="nav-link nav-dropdown-toggle" onClick={handleClick.bind(this)}>Global</p>
<ul className="nav-dropdown-items">
<li> Organization <br/>
<select className="form-control">
<option value="">Select </option>
{this.state.data.map(function (option,key) {
return <option key={key}>{option}</option>;
})}
</select>
If you have to pass state from Dashboard to Sidebar, you have to render Sidebar from Dashboard's render function. Here, you can pass the state of Dashboard to Sidebar.
Code snippet
class Dashboard extends Component {
...
...
render(){
return(
<Sidebar data={this.state.data1}/>
);
}
}
If you want the changes made on props (data1) passed to Sidebar be received by Dashboard, you need to lift the state up. i.e, You have to pass a function reference from Dashboard to Sidebar. In Sidebar, you have to invoke it whenever you want the data1 to be passed back to Dashboard.
Code snippet.
class Dashboard extends Component {
constructor(props){
...
//following is not required if u are using => functions in ES6.
this.onData1Changed = this.onData1Changed.bind(this);
}
...
...
onData1Changed(newData1){
this.setState({data1 : newData1}, ()=>{
console.log('Data 1 changed by Sidebar');
})
}
render(){
return(
<Sidebar data={this.state.data1} onData1Changed={this.onData1Changed}/>
);
}
}
class Sidebar extends Component {
...
//whenever data1 change needs to be sent to Dashboard
//note: data1 is a variable available with the changed data
this.props.onData1changed(data1);
}
Reference Doc : https://facebook.github.io/react/docs/lifting-state-up.html
You can only pass props from parent to child component. Either restructure your components hierarchy to have this dependence, or use a state/event management system like Redux (react-redux) .
I had the same issue with parent and child components and the solution was simply send down the function (which is altering the state in the parent component) as a prop to the child component. In this way both are sharing that particular variable's state. Hope this straightforward approach helps you!
I believe keeping the status values aligned with the page URL is another good way, not only to pass values, but also to keep the page status controllable with urls.
Imagine that you are building an advanced search page, where different components will control the search criteria, hence, in addition to search functionality, user should be able to keep his search settings by the used URL.
Supposing that clicking on a link in component x adds a query string criteria1=x to the current page url, and so on for the other components. Let's say we have also configured the search functionality to depend on the URL to read state values from it, this way, you will be able to pass values from a specific component to any number of components without restrictions.
This is called as props drilling.
You can pass data from one component to another by several ways
useContext Hook
Context API
Redux (Its a pattern)
Context with useContext hook, this is a better approach as using Context will increase the code complexity
const themes = {
light: {
foreground: "#000000",
background: "#eeeeee"
},
dark: {
foreground: "#ffffff",
background: "#222222"
}
};
const ThemeContext = React.createContext(themes.light);
function App() {
return (
<ThemeContext.Provider value={themes.dark}>
<Toolbar />
</ThemeContext.Provider>
);
}
function Toolbar(props) {
return (
<div>
<ThemedButton />
</div>
);
}
function ThemedButton() {
const theme = useContext(ThemeContext);
return (
<button style={{ background: theme.background,
color: theme.foreground
}}> I am styled by theme context!</button> );
}
For detailed information you can visit the links : useContext , Context and Redux
Redux is better for large-scale application, and if you have multiple Context always go for useContext hook.

How would I make it so that props.match.params.id can be accessed to any of the children Components?

I'm currently trying to wrap my head around how to structure a ReactJS application. Here's the relevant information:
Dev Environment: npm, webpack
Dependencies: React, ReactDOM, React Router, React Router DOM
Code: (Cleaned up a bit as to not reveal more sensitive information.)
/***********
TABLE OF CONTENTS
01.0.0 - Imports
02.0.0 - Data
03.0.0 - Components
03.1.0 -- Base
03.2.0 -- Location
03.3.0 -- Main
03.3.2 --- Map
04.0.0 - Render
***********/
/* 01.0 Imports */
import React from 'react';
import ReactDOM from 'react-dom';
import $ from 'jquery';
import {
HashRouter,
Route,
Switch,
Link
} from 'react-router-dom';
console.log("Set-up successful");
/* 02.0 Data */
const locationDataArray = {
locations: [
{
"location": "Scenario A",
"slug": "lipsum",
"googleUrl": "https://www.google.com/maps/place/Central+Park/#40.7828687,-73.9675438,17z/data=!3m1!4b1!4m5!3m4!1s0x89c2589a018531e3:0xb9df1f7387a94119!8m2!3d40.7828647!4d-73.9653551",
"mapUrl": "locations/scenA/main-map.png",
"areaMaps": [
{
"name": "Overall",
"url": "inner/map-overall.html"
}, {
"name": "A",
"url": "inner/map-a.html"
}, {
"name": "B",
"url": "inner/map-b.html"
}
],
"overallPDF": "diagram.pdf",
"mapKey": "mapkey.txt",
"mapImagemap": "imagemap.txt"
} // list truncated for your convenience.
],
all: function() {return this.locations},
get: function(id) {
const isLocation = q => q.slug === id
return this.locations.find(isLocation)
}
}
/* 03.0 Components */
/* 03.1 Base */
class App extends React.Component {
constructor() {
super();
console.log("<App /> constructor locationDataArray: " + locationDataArray);
this.state = {
locationData: locationDataArray,
test: 'testing!'
};
}
render() {
console.log("<App /> locationDataArray: " + locationDataArray);
console.log("<App /> state, testing: " + this.state.test);
return(
<div>
<Switch>
<Route exact path='/' component={LocationGrid} />
<Route path='/locations/:id' component={MainMap}/>
</Switch>
</div>
);
}
}
/* 03.2.0 -- Location */
class LocationGrid extends React.Component {
constructor() {
super();
}
render() {
return(
<div>
<h2>Location Grid</h2>
<div>
<Link to="/locations">Main Map</Link>
</div>
<div>
<ul>
{ // this part works with extra items in locationDataArray.
locationDataArray.all().map(q => (
<li key={q.slug}>
<Link to={`locations/${q.slug}`}>{q.location}</Link>
</li>
))
}
</ul>
</div>
</div>
);
}
}
/* 03.3.0 -- Main */
class MainMap extends React.Component {
constructor(props) {
super();
console.log("Main map is on");
const location = locationDataArray.get(
props.match.params.id
);
if (!location) {
console.log("No such location!");
}
if (location) {
console.log("These tests show that the locationDataArray is accessible from this component.");
console.log("A: props.match.params.id is: " + props.match.params.id);
console.log("B: Location is: " + location.location);
console.log("C: Map URL: " + location.mapUrl);
console.log("State is: " + location);
}
}
render() {
return (
<div>
<MapHolder />
</div>
);
}
}
/* 03.3.2 --- Map */
//var mapUrl = require(location.mapUrl);
class MapHolder extends React.Component {
render() {
console.log("And these tests show that the locationDataArray is NOT accessible from this component.");
console.log("1. Map location test, mapUrl: " + location.mapUrl);
console.log("2. Map location test, slug: " + location.slug);
return <div>
<img id="imagemap" className="map active" src={location.mapUrl} width="1145" height="958" useMap="#samplemap" alt="" />
<map name="samplemap" id="mapofimage">
<area target="" alt="1" title="1" href="inner/01.html" coords="288,356,320,344,347,403,315,420" shape="poly"></area>
{/* Additional imagemap areas. Truncated for your conveinence.*/}
</map>
<p>A map would be here if I could figure out the bug.</p>
</div>
}
}
/* 04.0.0 -- Render */
ReactDOM.render((
<HashRouter>
<App />
</HashRouter>
), document.getElementById('container'));
package.json and webpack.config.js are all in a git repository, if needed.
Overall Goal: I want to dynamically serve information to a main component and children components via a React Router route param and JSON object. Essentially, I want to be able to associate a param with an item in the object, so that the component can just refer to something like arrayItem.itemProperty and it can work dynamically.
I am trying to stay away from Redux as much as possible, since I'm already slightly overwhelmed by React now.
The Current Problem:
The array (on lines 26-55) is in the code, but anything outside of 1 component (<MainMap />, lines 113-138) can't access it.
An example of one of these child Components would be <MapHolder />, which is on lines 142-156. (I get ReferenceError: props is not defined in those cases.)
How would I make it so that props.match.params.id (or any other thing that would make this work) can be accessed to any of the children Components?
(Note: I'm well aware that the code isn't perfect. But this is still in early production stages, so I'd like to just resolve that one issue and hopefully the rest will clear up as a result of that.)
When you're using import/export, global variables aren't shared between modules. This is a good thing, but you can export multiple variables from a module. For example, you can export locationDataArray; in your App.js to access it from other modules without breaking the existing exports and imports for that module. That being said you almost never want to share global data in React like this. As a general rule of thumb you should "lift" data that components depend on to the highest component in the tree that needs in (you can even make an extra parent component to make this composition easier if you'd like).
When child components rely on data from part of a parent component's data, you should generally pass in subsets of data from parent components via props. Add a location prop to MapHolder with the actual map data for the given id, instead of making the component look up the id itself. Then in MapHolder's parent, render it like this:
<MapHolder
location={locationDataArray.locations.find(location => location.slug === ID)}
/>
What you're looking for is React's Context feature. It's pretty low-level, and is an unstable API likely to change at any point. Meaning: use with caution.
In some cases, you want to pass data through the component tree without having to pass the props down manually at every level. You can do this directly in React with the powerful "context" API.
TL;DR:
First, declare the shape of the context object you will be making accessible to children, and implement getChildChildContext to actually return it:
class MyComponent extends React.Component {
...
static childContextTypes = {
foo: PropTypes.string // or whatever you have
};
getChildContext() {
return {foo: "some value"};
}
...
}
Then, when you want to access that context in a child component, declare that you wish to do so by declaring contextTypes on your child component:
class SomeChildComponent extends React.Component {
static childContextTypes = {
foo: PropTypes.string
};
render() {
// use in your component by accessing this.context.foo
}
}
If it feels klunky, that's because it's meant to be:
The vast majority of applications do not need to use context.
If you want your application to be stable, don't use context. It is an experimental API and it is likely to break in future releases of React.
You will probably get much better results from using a state management library such as redux or mobx to manage state that should be accessible from arbitrary components.

How to get the state of a React app?

So, I've built up this React app with some state. I want to know what that state is, so I can save it to localStorage and let state carry from one session to another. Basically, the app is pretty complex and I don't want people to lose their "place" just because the closed it and opened it again later.
Reading through the React docs though, I don't see anything that references accessing a component's state from outside of React.
Is this possible?
You should never ever try to get a state from a component as a component should always be representing and not generate state on its own. Instead of asking for the component's state, ask for the state itself.
That being said, I definitely see where you're coming from. When talking about React, the term "state" seems to be pretty ambiguous indeed.
I don't see anything that references accessing a component's state
from outside of React.
Once and for all, here's the difference:
Local state, shouldn't persist: this.state, this.setState et al. Local state lives only within the component and will die once the component dies.
Global state, can be persisted: this.props.passedState. Global state is only passed to the component, it can not directly modify it. The view layer will adjust to whatever global state it got passed.
Or in simple:
this.state means local state, won't get persisted.
this.props.state means passed state, could be persisted, we just don't know and we don't care.
Example
The following example uses stuff of Babel's stage-1 Preset
React is all about giving a display representation of a data structure. Let's assume we have the following object to visibly represent:
let fixtures = {
people: [
{
name: "Lukas"
},
{
name: "fnsjdnfksjdb"
}
]
}
We have an App component in place, that renders Person components for every entry in the people array.
import React, { Component, PropTypes } from 'react';
import ReactDOM from 'react-dom';
class Person extends Component {
static propTypes = {
name: PropTypes.string.isRequired
}
render() {
return (
<li>
<input type="text" value={this.props.name} />
</li>
);
}
}
class App extends Component {
static propTypes = {
people: PropTypes.array.isRequired
}
render() {
let people = this.people.map(person => {
<Person name={person.name} />
});
return (
<ul>
{people}
</ul>
);
}
}
ReactDOM.render(
<App people={fixtures} />,
document.getElementById('yourid')
);
Now, we will implement focus functionality. There are 2 options:
We don't care about which person was focused the last time the user used the app, so we use local state.
We do care about which person was focused the last time the user used the app, so we use global state.
Option 1
The task is simple. Just adjust the People component so that it knows (and rerenders) once the focus changed. The original data structure won't be changed and the information whether a component is focused or not is
merely client side information that will be lost once the client is closed/reset/whatever.
State is local: We use component.setState to dispatch changes to local state
class Person extends Component {
static propTypes = {
name: PropTypes.string.isRequired
}
constructor(...args) {
super(...args);
this.state = {
isFocused: false
}
this.onFocus = this.onFocus.bind(this);
this.onBlur = this.onBlur.bind(this);
}
static propTypes = {
name: PropTypes.string.isRequired
}
onFocus() {
this.setState({
isFocused: true;
});
}
onBlur() {
this.setState({
isFocused: false;
});
}
render() {
let borderColor = this.state.isFocused ? '#ff0' : '#000';
style = {
border: `1px solid ${borderColor}`
}
return (
<li>
<input
style={style}
type="text"
value={this.props.name}
onFocus={this.onFocus}
onBlur={this.onBlur}
/>
</li>
);
}
}
Option 2
We actually want to persist the focused element to whatever store (eg. backend) we have, because we care about the last state. State is global: React components receive only props as "state", even granular information as whether an element is focused. Persist and feed global state to the app and it will behave accordingly.
function setFocus(index) {
fixtures.people[index].isFocused = true;
render();
}
function clearFocus(index) {
fixtures.people[index].isFocused = false;
render();
}
function render() {
ReactDOM.render(
<App people={fixtures} />,
document.getElementById('yourid')
);
}
class Person extends Component {
static propTypes = {
name: PropTypes.string.isRequired,
isFocused: PropTypes.bool,
index: PropTypes.number.isRequired
}
static defaultProps = {
isFocused: false
}
constructor(...args) {
super(...args);
this.onFocus = this.onFocus.bind(this);
this.onBlur = this.onBlur.bind(this);
}
static propTypes = {
name: PropTypes.string.isRequired
}
onFocus() {
setFocus(this.props.index);
}
onBlur() {
clearFocus(this.props.index);
}
render() {
let borderColor = this.props.isFocused ? '#ff0' : '#000';
style = {
border: `1px solid ${borderColor}`
}
return (
<li>
<input
style={style}
type="text"
value={this.props.name}
onFocus={this.onFocus}
onBlur={this.onBlur}
/>
</li>
);
}
}
class App extends Component {
static propTypes = {
people: PropTypes.array.isRequired
}
render() {
let people = this.people.map((person, index) => {
<Person name={person.name} index={index} isFocused={person.isFocused} />
});
return (
<ul>
{people}
</ul>
);
}
}
render();
I think the solution to your problem really lies in how your application is modelled.
Ideally what you would need (depending on complexity) would be a single (flux/redux) store upon which you could subscribe to changes, if it diffs then save it to localStorage.
You would then need to determine a way to bootstrap this data into your single store.
Their is no API per se (that I know of) to do specifically what you want.
Don't try to get the state from outside of React -- pass the state from React to wherever it needs to be.
In your case, I would do this componentDidUpdate.
var SampleRootComponent = React.createClass({
componentDidUpdate: function() {
localStorage.setItem(JSON.stringify(this.state))
}
})
You're right in that there is no way to get the state from outside; you have to set it from React. But there is no reason to view this as a bad thing -- just call an external function from inside of React if you need to pass the info to something else, as shown. No functionality is lost.

Resources