could anyone tell me why is that won't work? Proper data is displaying in the console (console.log(this.state);), but it won't be transfered to MainContainer.
Same data initialized in the constructor>state>users working without issues. Where's the problem?
App
import React, {Component} from 'react';
import logo from './logo.svg';
import './App.css';
import Header from './components/header/Header';
import MainContainer from './containers/main-container/MainContainer';
class App extends Component {
constructor(props) {
super(props);
this.state = {
users: []
}
}
componentDidMount() {
fetch('https://jsonplaceholder.typicode.com/users')
.then(response => response.json())
.then(users => {
let u = users.map((user) => {
return {id: user.id, name: user.name, email: user.email}
})
return u;
})
.then(u => {
this.setState({users: u});
console.log(this.state);
});
}
render() {
return (
<div className="App">
<Header/>
<MainContainer users={this.state.users}/>
</div>
)
}
}
export default App;
MainContainer
import React from 'react';
import ActionBar from '../../components/action-bar/ActionBar'
import ListHeader from '../../components/list-header/ListHeader'
import ListItem from '../../components/list-item/ListItem'
import ListItemPlaceholder from '../../components/list-item-placeholder/ListItemPlaceholder'
class MainContainer extends React.Component {
constructor(props) {
super(props);
this.state = {
users : props.users
}
}
render() {
const list = this.state.users.map(
(user) =>
{
const liStyle = {
'background-color': user % 2 == 0 ? '#fbfcfc' : 'transparent',
};
return <ListItem key={user.id} style={liStyle} id={user.id} name={user.name} email={user.email}/>
}
);
return (
<div className={'main-container'}>
<ActionBar />
<ListHeader />
{list}
</div>
)
}
}
export default MainContainer;
.................................................................................................................
Best Regards!
crova
In your <MainContainer> component you store the users in its state in the constructor but you never alter it. You only need to use state when the component needs to alter it during its lifetime. But the users come from it's parent via the users prop which you never render. So just render that prop instead:
const MainContainer = props => (
<div className="main-container">
<ActionBar />
<ListHeader />
{props.users.map(({id, name, email}) => (
<ListItem
key={id}
style={{
backgroundColor: id % 2 === 0 ? '#fbfcfc' : 'transparent'
}}
id={id}
name={name}
email={email}
/>
))}
</div>
);
When the users change in the parent it will re-render and pass the new users array to the <MainContainer>.
Also note that if your component only renders props and has no own state it can be written as a stateless functional component.
Related
I want to pass brand.title to the child component - BrandDetail
This is my try and is not working, it simply renders the child component within the parent component and I want it to be rendered solely on the child component.
Parent component:
class BrandsList extends React.Component {
state = {
brands: [],
};
fetchBrands = () => {
axios.get('http://127.0.0.1:8000/api/brands/').then((res) => {
this.setState({
brands: res.data,
});
});
};
componentDidMount() {
this.fetchBrands();
}
render() {
return (
<div style={{ margin: 22 }}>
{this.state.brands.map((brand) => (
<div key={brand.id}>
<Link to={`/brands/${brand.id}`}>{brand.title}</Link>
<BrandDetail brandName={brand.title} />
</div>
))}
</div>
);
}
}
export default BrandsList;
Child component:
import React, { useEffect, useState } from 'react';
import MyLayout from '../MyLayout/MyLayout';
class BrandDetail extends React.Component {
constructor(props) {
super(props);
}
render() {
return (
<MyLayout>
<div>Yes this is the detail page of {this.props.brandName}</div>
</MyLayout>
);
}
}
export default BrandDetail;
UPDATE:
This is the answer I was looking for.
{ this.state.brands.map(brand =>
<div key={brand.id}>
<Link to={{
pathname: `/brands/${brand.title}`,
state: `${brand.title}`,
}}>{brand.title}
</Link>
</div>
)}
And child component:
<div>Yes this is the detail page of {props.location.state}</div>
I am working on a site that has a piece a global state stored in a file using zustand. I need to be able to set that state in a class component. I am able to set the state in a functional component using hooks but I'm wondering if there is a way to use zustand with class components.
I've created a sandbox for this issue if that's helpful:
https://codesandbox.io/s/crazy-darkness-0ttzd
here I'm setting state in a functional component:
function MyFunction() {
const { setPink } = useStore();
return (
<div>
<button onClick={setPink}>Set State Function</button>
</div>
);
}
my state is stored here:
export const useStore = create((set) => ({
isPink: false,
setPink: () => set((state) => ({ isPink: !state.isPink }))
}));
how can I set state here in a class componet?:
class MyClass extends Component {
constructor(props) {
super(props);
this.state = {};
}
render() {
return (
<div>
<button
onClick={
{
/* setPink */
}
}
>
Set State Class
</button>
</div>
);
}
}
A class component's closest analog to a hook is the higher order component (HOC) pattern. Let's translate the hook useStore into the HOC withStore.
const withStore = BaseComponent => props => {
const store = useStore();
return <BaseComponent {...props} store={store} />;
};
We can access the store as a prop in any class component wrapped in withStore.
class BaseMyClass extends Component {
constructor(props) {
super(props);
this.state = {};
}
render() {
const { setPink } = this.props.store;
return (
<div>
<button onClick={setPink}>
Set State Class
</button>
</div>
);
}
}
const MyClass = withStore(BaseMyClass);
Seems that it uses hooks, so in class you can work with the instance:
import { useStore } from "./store";
class MyClass extends Component {
render() {
return (
<div>
<button
onClick={() => {
useStore.setState({ isPink: true });
}}
>
Set State Class
</button>
</div>
);
}
}
Create a React Context provider that both functional and class-based components can consume. Move the useStore hook/state to the context Provider.
store.js
import { createContext } from "react";
import create from "zustand";
export const ZustandContext = createContext({
isPink: false,
setPink: () => {}
});
export const useStore = create((set) => ({
isPink: false,
setPink: () => set((state) => ({ isPink: !state.isPink }))
}));
export const ZustandProvider = ({ children }) => {
const { isPink, setPink } = useStore();
return (
<ZustandContext.Provider
value={{
isPink,
setPink
}}
>
{children}
</ZustandContext.Provider>
);
};
index.js
Wrap your application with the ZustandProvider component.
...
import { ZustandProvider } from "./store";
import App from "./App";
const rootElement = document.getElementById("root");
ReactDOM.render(
<StrictMode>
<ZustandProvider>
<App />
</ZustandProvider>
</StrictMode>,
rootElement
);
Consume the ZustandContext context in both components
MyFunction.js
import React, { useContext } from "react";
import { ZustandContext } from './store';
function MyFunction() {
const { setPink } = useContext(ZustandContext);
return (
<div>
<button onClick={setPink}>Set State Function</button>
</div>
);
}
MyClass.js
import React, { Component } from "react";
import { ZustandContext } from './store';
class MyClass extends Component {
constructor(props) {
super(props);
this.state = {};
}
render() {
return (
<div>
<button
onClick={this.context.setPink}
>
Set State Class
</button>
</div>
);
}
}
MyClass.contextType = ZustandContext;
Swap in the new ZustandContext in App instead of using the useStore hook directly.
import { useContext} from 'react';
import "./styles.css";
import MyClass from "./MyClass";
import MyFunction from "./MyFunction";
import { ZustandContext } from './store';
export default function App() {
const { isPink } = useContext(ZustandContext);
return (
<div
className="App"
style={{
backgroundColor: isPink ? "pink" : "teal"
}}
>
<h1>Hello CodeSandbox</h1>
<h2>Start editing to see some magic happen!</h2>
<MyClass />
<MyFunction />
</div>
);
}
If you aren't able to set any specific context on the MyClass component you can use the ZustandContext.Consumer to provide the setPink callback as a prop.
<ZustandContext.Consumer>
{({ setPink }) => <MyClass setPink={setPink} />}
</ZustandContext.Consumer>
MyClass
<button onClick={this.props.setPink}>Set State Class</button>
This worked out pretty well for me.
:
import React, { Component } from "react";
import { useStore } from "./store";
class MyClass extends Component {
constructor(props) {
super(props);
this.state = {};
}
render() {
return (
<div>
<button
onClick={
useStore.getState().setPink() // <-- Changed code
}
>
Set State Class
</button>
</div>
);
}
}
export default MyClass;
I like to create a high order component similar to redux connect:
function connectZustand(useStore, selector) {
return (Component) =>
React.forwardRef((props, ref) => <Component ref={ref} {...props} {...useStore(selector, shallow)} />);
}
eg:
import React, { Component } from 'react';
import create from 'zustand';
import shallow from 'zustand/shallow';
function connectZustand(useStore, selector) {
return (Component) =>
React.forwardRef((props, ref) => <Component ref={ref} {...props} {...useStore(selector, shallow)} />);
}
const useStore = create((set) => ({
isPink: false,
setPink: () => set((state) => ({ isPink: !state.isPink })),
}));
class MyClass extends Component {
render() {
const { setPink } = this.props;
return (
<div>
<button onClick={() => setPink()}>Set State Class</button>
</div>
);
}
}
const MyClassWithZustand = connectZustand(useStore, (state) => ({ setPink: state.setPink }))(MyClass);
export default function Test() {
const isPink = useStore((state) => state.isPink);
return (
<>
<MyClassWithZustand />
{isPink ? 'Is Pink' : 'Is Not Pink'}
</>
);
}
I am having an issue getting data to flow down to my props to where when component rendered, the props are not displaying.
This is the container that contains my RecipeList Component
*---Note: I am getting my data asynchronously from a api btw *
import { postRecipes } from '../actions/postRecipes.js'
import { getRecipes } from '../actions/getRecipes'
class RecipesContainer extends Component{
constructor(props){
super(props)
}
componentDidMount(){
this.props.getRecipes()
}
render(){
return (
<div>
<RecipeInput postRecipes={this.props.postRecipes} />
<RecipeList recipes={this.props.recipes} />
</div>
)
}
}
const mapStateToProps = state =>{
return{
recipes: state.recipes
}
}
const mapDispatchToProps = dispatch =>{
return{
postRecipes: (recipe) => dispatch(postRecipes(recipe)),
getRecipes: () => dispatch(getRecipes())
// deleteRecipe: id => dispatch({type: 'Delete_Recipe', id})
}
}
export default connect(mapStateToProps,mapDispatchToProps)(RecipesContainer)
Here is my RecipeList component
import React, {Component} from 'react';
import Recipe from './Recipe.js'
class RecipeList extends Component {
render() {
const { recipes } = this.props
return (
<div>
{recipes.map((recipe,index) => <Recipe recipe={recipe} key={index} />)}
</div>
)
}
}
export default RecipeList;
And here is the Recipe component that it mapping as I enter and submit a recipe
import React, {Component} from 'react'
class Recipe extends Component {
render(){
return(
<div>
<h3>Name: {this.props.name}</h3>
<p>Category:{this.props.category}</p> <-------this one I will have to call differently since this is a one to many relationship
<p>Chef Name: {this.props.chef_name}</p>
<p>Origin: {this.props.origin}</p>
<p>Ingredients: {this.props.ingredients}</p>
</div>
)
}
}
export default Recipe
EDIT: Added getRecipe action as requested.
export const getRecipes = () => {
const BASE_URL = `http://localhost:10524`
const RECIPES_URL =`${BASE_URL}/recipes`
return (dispatch) => {
dispatch({ type: 'START_FETCHING_RECIPES_REQUEST' });
fetch(RECIPES_URL)
.then(response =>{ return response.json()})
.then(recipes => { return console.log(recipes), dispatch({ type: 'Get_Recipes', recipes })});
};
}
Why isn't it displaying my results? I did console to make I was return my api data, and the Recipe component is rendering as just the html tags are rendering just fine.
You pass in a prop called recipe to your <Recipe /> component, but your component reads from a non-existant this.props.name, etc.
In your recipe list component, try this.
{recipes ? recipes.map((recipe,index) => <Recipe recipe={recipe} key={index} />) : null}
The below is App.js file
import React,{Component} from 'react'
//import logo from './logo.svg';
import './App.css'
import InputComponent from "./components/InputComponent";
import ResultComponent from "./components/ResultComponent";
class App extends Component {
render()
{
return (
<div className="App">
<InputComponent />
<ResultComponent/>
</div>
);
}
}
export default App;
The below is InputComponent
import React,{Component} from "react";
import axios from 'axios';
class InputComponent extends Component{
constructor(props) {
super(props)
this.state = {
owner : "",
repo : "",
}
}
//here event.target.value is setting value of target to this owner
ownerName = (event) => {
this.setState({
owner:event.target.value
})
}
//here event.target.value is setting value of target to this repo
repoName = (event) => {
this.setState({
repo:event.target.value
})
}
render(){
//let submit = this.props;
let {items} = this.state;
return(
<div>
<p>The current Owner is {this.state.owner} and the current Repo is {this.state.repo}</p>
<input type='text' onChange={this.ownerName} value={this.state.owner} placeholder='Enter Username' className='inputFields'/>
<br/>
<input type='text' onChange={this.repoName} value={this.state.repo} placeholder='enter Repository' className='inputFields'/>
<br/>
</div>
);
}
}
export default InputComponent;
The below is Result Component
import React,{Component} from "react"
import axios from "axios";
class ResultComponent extends Component{
constructor() {
super()
this.state = {
items: []
}
this.apiFetch=this.apiFetch.bind(this)
}
apiFetch = () => {
axios.get(`https://api.github.com/repos/${this.props.owner}/${this.props.repo}/issues`)
.then(response => {
console.log(response)
this.setState({
items:response.data,
})
})
.catch(error => {
console.log(error)
})
}
render(){
let {items} = this.state;
return(
<div className='submit'>
<button onClick={this.apiFetch}>Fetch Results</button>
<ul>
{items.map(item=>(
<li key={item.id}>
Issue-title: {item.title}
</li>
)
)}
</ul>
</div>
);
}
}
export default ResultComponent
I want to access the value of owner,repo from InputComponent in ResultComponent in my URL part
'''axios.get(https://api.github.com/repos/${this.props.owner}/${this.props.repo}/issues)'''
but not able to do so, can anyone help me what i am doing wrong. I am not able to figure out the issue I am new to React.
In general, there are the options for passing data between react components :
From Parent to Child using Props
From Child to Parent using Callbacks
Between Siblings :
(i) Combine above two methods
(ii) Using Redux
(iii) Using React’s Context API
Use design pattern like HOC or render Props for sharing code between React components (render code abstrait => good practice for reusable)
In your case, it's good pratice with the design pattern render Props. For example, I propose an example of codes :
class InputComponent extends React.Component {
constructor(props) {
super(props);
this.state = {
owner : "",
repo : "",
}
}
//here event.target.value is setting value of target to this owner
ownerName = (event) => {
this.setState({
owner:event.target.value
})
}
//here event.target.value is setting value of target to this repo
repoName = (event) => {
this.setState({
repo:event.target.value
})
}
render() {
return (
<input type='text' onChange={this.ownerName} value={this.state.owner} placeholder='Enter Username' className='inputFields'/>
{/*
use the `render` prop to dynamically determine what to render.
*/}
{this.props.render(this.state)}
</div>
);
}
}
class WithInputComponent extends React.Component {
render() {
return (
<div>
<InputComponent render={dataInput => (
<ResultComponent dataInput={dataInput} />
)}/>
</div>
);
}
}
Here the links in more details :
https://en.reactjs.org/docs/render-props.html
https://towardsdatascience.com/passing-data-between-react-components-parent-children-siblings-a64f89e24ecf
There are three answers to this question:
You should set your state as high on the DOM tree as you can so that
you can pass the values down to siblings. In simple terms, if state
is set by the parent of the two, you can just ask for state from the
parent and you're done.
You can use a state management system like Redux, which effectively
does the same thing behind the scenes.
You can use refs, but you probably shouldn't so ignore that.
If I were you, I would just bring my state up to App.js, modify it from InputComponent, and pass that modified state down to ResultComponent.
class App extends Component {
constructor(props){
super(props)
this.state = {//initial values}
}
changeSomething() {
// function that changes your state's values
}
render()
{
return (
<div className="App">
<InputComponent aFunctionProp={changeSomething} />
<ResultComponent inputVals={this.state}/>
</div>
);
}
}
export default App;
Check this out as well:
https://reactjs.org/tutorial/tutorial.html
Remember that when you pass down props through your component, you refer to them by their prop name, not by the value you pass in. So in InputComponent, you'll be looking for aFunctionProp() rather than changeSomething(). That was pretty confusing to me when I first learned React.
Im getting a big headache.. I dont know what Im doing wrong here. When my Podcast.js component renders, I get 'Cannot read property 'params' of undefined... '
Someone that can point me in the right direction?
This is the parent component of Podcast:
import React, { Component } from 'react';
import { BrowserRouter as Router, Route } from 'react-router-dom';
import NavLinks from './components/NavLinks';
import Home from './components/Home';
import Podcast from './components/Podcast';
import './App.css';
class App extends Component {
render() {
return (
<Router>
<div className="App">
<NavLinks />
<Route exact path='/' component={Home} />
<Route path='/podcast/:podID' component={Podcast} />
</div>
</Router>
);
}
}
export default App;
This is my main Component (Podcast):
import React, { Component } from 'react';
import PodcastList from './PodcastList';
class Podcast extends Component {
constructor(props) {
super(props);
this.state = {
podcast: []
};
}
// Fetches podID from props.match
fetchPodcast () {
const podID = this.props.match.params.podID
fetch(`https://itunes.apple.com/search?term=podcast&country=${podID}&media=podcast&entity=podcast&limit=20`)
.then(response => response.json())
.then(data => this.setState({ podcast: data.results }));
}
componentDidMount () {
this.fetchPodcast()
}
// Check if new props is not the same as prevProps
componentDidUpdate (prevProps) {
// respond to parameter change
let oldId = prevProps.match.params.podID
let newId = this.props.match.params.podID
if (newId !== oldId)
this.fetchPodcast()
}
render() {
return (
<div>
<PodcastList />
</div>
)
}
}
export default Podcast;
This is the component thats list's all podcasts:
import React, { Component } from 'react';
class PodcastList extends Component {
render() {
return (
<div>
<h2>Country ({this.props.match.params.podID}) </h2>
<ul>
{this.state.podcast.map(podcast =>
<li key={podcast.collectionId}>
<a
href={podcast.collectionId}>
{podcast.collectionName}</a>
</li>
)}
</ul>
</div>
)
}
}
export default PodcastList;
Where does the error comes from? Podcast or PodcastList ? Maybe because you're not passing the props down to PodcastList ?
Try:
<PodcastList {...this.props} {...this.state} />
Also, in the child component (PodcastList) use this.props and not this.state
I guess you are using react-router. To have match prop of the React Router you have to decorate it by withRouter decorator of the module
import React, { Component } from 'react';
import { withRouter } from 'react-router';
class PodcastList extends Component {
render() {
return (
<div>
<h2>Country ({this.props.match.params.podID}) </h2>
<ul>
{this.state.podcast.map(podcast =>
<li key={podcast.collectionId}>
<a
href={podcast.collectionId}>
{podcast.collectionName}</a>
</li>
)}
</ul>
</div>
)
}
}
export default withRouter(PodcastList);
UPDATE:
One of the ways how to handle podcast prop in the PodcastList. The solutions fits all React recommendations and best practices.
import React, { PureComponent } from 'react';
import PodcastItem from './PodcastItem';
class Podcast extends PureComponent { // PureComponent is preferred here instead of Component
constructor(props) {
super(props);
this.state = {
podcast: []
};
}
// Fetches podID from props.match
fetchPodcast () {
const podID = this.props.match.params.podID
fetch(`https://itunes.apple.com/search?term=podcast&country=${podID}&media=podcast&entity=podcast&limit=20`)
.then(response => response.json())
.then(data => this.setState({ podcast: data.results }));
}
componentDidMount () {
this.fetchPodcast()
}
// Check if new props is not the same as prevProps
componentDidUpdate (prevProps) {
// respond to parameter change
let oldId = prevProps.match.params.podID
let newId = this.props.match.params.podID
if (newId !== oldId)
this.fetchPodcast()
}
render() {
return (
<div>
<h2>Country ({this.props.match.params.podID}) </h2>
<ul>
{this.state.podcast.map(podcast => (
<PodcastItem key={podcast.collectionId}> podcast={podcast} />
))}
</ul>
</div>
)
}
}
export default Podcast;
import React from 'react';
// Here Stateless function is enough
const PodcastItem = ({ podcast }) => (
<li key={podcast.collectionId}>
<a href={podcast.collectionId}>{podcast.collectionName}</a>
</li>
);
export default PodcastItem;