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}
Related
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'}
</>
);
}
AutoComplete Class
import React from 'react';
import axios from 'axios';
import DeleteItem from '../DeleteItem/DeleteItem'
export default class AutoComplete extends React.Component{
constructor(props){
super(props)
this.state={
suggestions:[],
text:'',
persons:[],
delsuggestions:[],
}
this.delete = this.delete.bind(this);
}
// Here I am calling api
componentDidMount() {
axios.get(`https://jsonplaceholder.typicode.com/users`)
.then(res => {
const persons = res.data;
this.setState({ persons:persons });
})
}
//on entering values
onTextChanged= (e) =>{
const value=e.target.value;
let suggestions=[];
if(value.length>0){
suggestions=this.state.persons.map(item =>(item.name))
.filter(function(val){
return val.indexOf(value) > -1
})
}
this.setState(()=>({suggestions:suggestions,text:value}))
}
//On selecting a value from listitems
personSelected(value){
this.setState(()=>({
text:value,
persons:[],
}))
}
//delete function
handleDelete=id =>{
let delsuggestions=[];
this.setState(()=>({delsuggestions:[...this.state.suggestions].filter(el => el!= id)}))
console.log('delsuggestions'+delsuggestions)
}
//listing out filtered items
renderPersons(){
const {persons}=this.state
return(
<React.Fragment>
{this.state.suggestions.map(item => (
<DeleteItem
key={item}
searchitem={item}
onDelete={this.handleDelete}
/>
))}
</React.Fragment>
)
}
//render function
render(){
const {text} = this.state;
return(<div>
<input value={text} onChange={this.onTextChanged} type='text' style={{backgroundColor:'pink'}} />
{this.renderPersons()}
</div>)
}
}
DeleteItem Class
import React, { Component } from "react";
class DeleteItem extends Component {
render() {
return (
<div className="deletebuttom">
{this.props.searchitem} <button
onClick={() => this.props.onDelete(this.props.searchitem)}
>
Delete
</button>
</div>
);
}
}
export default DeleteItem;
what's problem with the below filter function
handleDelete=id =>{
let delsuggestions=[];
this.setState(()=>({delsuggestions:this.state.suggestions.filter(el => el.id != id)}))
console.log('delsuggestions'+delsuggestions)
}
I am not getting filtered values in delsuggestions.When I debug "unexpected end of input" is displayed onhover of el.
I am following the current tutorial:
Youtube tutorial at 12:51 mins.
I expect to see bunch of posts on my screen but my screen remains blank.
It appears I have followed everything told in the tutorial.
import React, { Component } from 'react';
class Posts extends Component {
constructor(props) {
super(props);
this.state = {
posts: []
}
}
componentWillMount() {
fetch('https://jsonplaceholder.typicode.posts')
.then(res => res.json())
.then(data => this.setState({posts: data}))
}
render() {
const postItems = this.state.posts.map(post => (
<div key={post.id}>
<h3>{post.title}</h3>
<p>{post.body}</p>
</div>
));
return (
<div>
<h1>Posts</h1>
{ postItems }
</div>
);
}
}
export default Posts;
and
import React, { Component } from 'react';
import './App.css';
import Posts from './components/Posts'
class App extends Component {
render() {
return (
<div className="App">
<Posts />
</div>
);
}
}
export default App;
My browser screen remains blank and I do not see any errors on console.
What am I missing ?
Don't know about the tutorial but it looks outdated...
Here is your App.js (parent component):
import React, { Component } from 'react';
import Posts from './Posts';
export default class App extends Component {
state = { posts: [] };
//fetch the posts and store them in the state
componentDidMount() {
fetch('https://jsonplaceholder.typicode.com/posts')
.then(response => response.json())
.then(posts => this.setState({ posts }))
.catch(error => console.log(error));
}
render() {
return (
<div>
{/* pass the state (posts) as props to Posts */}
<Posts posts={this.state.posts} />
</div>
);
}
}
Here is your Posts.js:
import React from 'react';
// No need for a class based comp
// destructure the props and you have all your data
const Posts = ({ posts }) => (
<div>
{posts.map(post => (
<div key={post.id}>
<h3>{post.title}</h3>
<p>{post.body}</p>
<hr />
</div>
))}
</div>
);
export default Posts;
Live Demo: https://jsfiddle.net/ctszdue9/7/
Try putting side effect/ api call inside
componentDidMount() { }
Change URL
https://jsonplaceholder.typicode.posts/
TO
https://jsonplaceholder.typicode.com/posts
Maybe this doesen't even look like a code, but is there any way I can change other components value/state on click?
import React from 'react';
import './pokemonList.css';
import {Component} from 'react';
import Pokemon from './Pokemon';
class PokemonList extends Component {
constructor(props){
super(props);
this.state = {
pokemons : [],
pokemon : {}
};
}
componentWillMount(){
fetch('https://pokeapi.co/api/v2/pokemon/').then(res=>res.json())
.then(response=>{
this.setState({
pokemons : response.results,
});
});
}
handleClick(id) {
fetch(`https://pokeapi.co/api/v2/pokemon/${id}/`)
.then(res => res.json())
.then(data => {
const pokemon = new Pokemon(data);
this.setState({ pokemon: pokemon });
})
.catch(err => console.log(err));
console.log("click happened");
}
render(){
const {pokemons} = this.state;
return (
<div className='pokemonList'> {pokemons.map(pokemon =>(
<button onClick={this.handleClick.bind(this)} className='pokemon-
btn' key={pokemon.name}>
{pokemon.name}
</button>
))}
</div>
)
}}
export default PokemonList;
At this point I'm not even sure where does handleClick() has to be, so I put it in my App component aswell. The output is ok, but clicking these buttons doesen't seem to do anything. They are supposed to show detailed pokemon information in component.
import React, {Component} from 'react';
import './pokemon-info.css';
const PokemonInfo = ({ pokemon }) => {
const { name,
height,
weight,
sprite,
statsSpeed,
statsSpecialDefense,
statsSpecialAttack,
statsDefense,
statsAttack,
statsHp
} = pokemon;
return (
<section className="pokemonInfo">
<img src={sprite} className='sprite-image' alt="pokemon_sprite"/>
<div className='data-wrapper'>
<h3 className="data-char">{pokemon.name}</h3><br />
<p className = 'data-char'>Height: {height}</p>
<p className = 'data-char'>Weight: {weight}</p><br />
<p className = 'data-char'>Stats: </p><br />
<p className = 'data-char'>Speed: {statsSpeed}</p>
<p className = 'data-char'>Special defense: {statsSpecialDefense}</p>
<p className = 'data-char'>Special attack: {statsSpecialAttack}</p>
<p className = 'data-char'>Defense: {statsDefense}</p>
<p className = 'data-char'>Attack: {statsAttack}</p>
<p className = 'data-char'>Hp: {statsHp}</p>
</div>
</section>
)
}
export default PokemonInfo;
Here is my App component
import React, { Component } from 'react';
import './App.css';
import PokemonList from './PokemonList';
import Pokemon from './Pokemon';
import PokemonInfo from './PokemonInfo';
class App extends Component {
constructor() {
super();
this.state = {
pokemon: {}
};
this.handleOnClick = this.handleOnClick.bind(this);
}
handleOnClick(id) {
fetch(`http://pokeapi.co/api/v2/pokemon/${id}/`)
.then(res => res.json())
.then(data => {
const pokemon = new Pokemon(data);
this.setState({ pokemon });
})
.catch(err => console.log(err));
}
render() {
return (
<div className="App">
<PokemonList />
<PokemonInfo pokemon={this.state.pokemon}/>
</div>
);
}
}
export default App;
It is obvious I did go wrong somewhere, but where?
Update:
Pokemon
class Pokemon {
constructor(data) {
this.id = data.id;
this.name = data.name;
this.height = data.height;
this.weight = data.weight;
this.sprite = data.sprites.front_default;
this.statsSpeed = data.stats[0].stats.base_stat;
this.statsSpecialDefense = data.stats[1].stats.base_stat;
this.statsSpecialAttack = data.stats[2].stats.base_stat;
this.statsDefense = data.stats[3].stats.base_stat;
this.statsAttack = data.stats[4].stats.base_stat;
this.statsHp = data.stats[5].stats.base_stat;
}
}
export default Pokemon;
Your App component should keep the state and pass updater functions as props to children components:
PokemonList
import React from "react";
import "./pokemonList.css";
import { Component } from "react";
import Pokemon from "./Pokemon";
class PokemonList extends Component {
render() {
const { pokemons } = this.props;
return (
<div className="pokemonList">
{pokemons.map(pokemon => (
<button
onClick={() => this.props.handleClick(pokemon.id)} // id or whatever prop that is required for request
className="pokemon-btn"
key={pokemon.name}
>
{pokemon.name}
</button>
))}
</div>
);
}
}
PokemonInfo - no change here.
APP
import React, { Component } from "react";
import "./App.css";
import PokemonList from "./PokemonList";
import Pokemon from "./Pokemon";
import PokemonInfo from "./PokemonInfo";
class App extends Component {
constructor() {
super();
this.state = {
pokemon: {},
pokemons: [],
};
this.handleOnClick = this.handleOnClick.bind(this);
}
componentDidMount() {
fetch("https://pokeapi.co/api/v2/pokemon/")
.then(res => res.json())
.then(response => {
this.setState({
pokemons: response.results
});
});
}
handleOnClick(id) {
fetch(`http://pokeapi.co/api/v2/pokemon/${id}/`)
.then(res => res.json())
.then(data => {
const pokemon = new Pokemon(data);
this.setState({ pokemon });
})
.catch(err => console.log(err));
}
render() {
return (
<div className="App">
<PokemonList pokemons={this.state.pokemons} handleClick={this.handleOnClick} />
<PokemonInfo pokemon={this.state.pokemon} />
</div>
);
}
}
More on lifting the state up.
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.