How to pass a State to a multilevel component - React - reactjs

I am building a shopping cart which should have the number of cart items automatically updated on the top right corner. Currently the number comes from localStorage, so the page must be reloaded in order to see it working. I need it updated automatically so I think the State is the best approach, but have no idea how to use in this case.
I have created a State in single item (ProductItem component) which is updated for every action "Add to cart".
The current structure is:
Home (with CartButton) > Products > ProductItem
How can I pass the single State (ProductItem) to the up level (Products), sum up all single components states, then pass it again to up level (Home), then finally pass as a prop to CartButton?
How can I achieve that? Any help would be greatly appreaciated.
Structure
ProductItem.jsx
import PropTypes from 'prop-types';
import React from 'react';
import { Link } from 'react-router-dom';
class ProductsItem extends React.Component {
constructor() {
super();
this.state = {
qtyState: 1,
};
}
addCart = () => {
const {
title,
thumbnail,
price,
id,
available,
} = this.props;
this.setState((prevState) => ({ qtyState: prevState.qtyState + 1 }));
const cart = localStorage.cart ? JSON.parse(localStorage.cart) : [];
cart.push({
title,
thumbnail,
price,
id,
qtd: 1,
available,
});
localStorage.setItem('cart', JSON.stringify(cart));
}
render() {
const {
title,
thumbnail,
price,
id,
} = this.props;
return (
<div>
<p>{title}</p>
<img src={ thumbnail } alt={ title } />
<p>{ `$ ${price}` }</p>
<button type="button" onClick={ this.addCart }>Add to Cart</button>
</div>
);
}
}
ProductsItem.propTypes = {
title: PropTypes.string,
thumbnail: PropTypes.string,
id: PropTypes.string,
price: PropTypes.number,
available: PropTypes.number,
}.isRequired;
export default ProductsItem;

You should use a context to share data between component. Here is one of my answer to another post with a clean example on how to use the context API from React.

There are three ways
(As mentioned by "zillBoy")
React context API
Redux
(One addition)
You can pass callback function from Home to Product and another callback from Product to ProductItem. (passing data from child to parent component - react - via callback function)

Yes, I also had the same kind of problem. You can solve this problem by using any of the following:
React Context API (https://reactjs.org/docs/context.html)
Redux (https://redux-toolkit.js.org/)
I like redux, but context is easy to get started with so you can use that, so whenever you add an item to the cart your context state would update and show the updated state count on the cart icon.

Related

How to know where to add states in react.js

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!

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

How to re-render a page to get its default values in React js

I have a page that contains a lot of components such as TextFields and comboboxes. When a button, assume Clean, is clicked, I want to refresh the current page to set each component with their default values and also set state to initial state. When I tried window.location.reload(), it refreshes the whole page and user needs to login again.
I tried forceUpdate() but it preserves the current state of the page.
I also tried, it might be absurd I am newbie, to push history as below,
this.props.history.push("/");
this.props.history.push("new-record");
But this didn't work. Page stayed same.
How can I handle this?
try something similar for state reset:
const initialState = { name: 'React' }
class App extends Component {
constructor() {
super();
this.state = initialState
}
resetState=()=>{
this.setState(initialState)
}
render() {
return (
<div>
<Hello name={this.state.name} />
<button
onClick={this.resetState}>reset</button>
</div>
);
}
}
Here is an example with code:
Codepen
There is a local state with defaultInputValue and currentValue:
this.state = {
defaultInputValue: defaultValue,
currentValue: defaultValue
}
setDefault function restore default values:
setDefault = () => {
this.setState({currentValue: this.state.defaultInputValue});
}

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 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