React Context API - form input is undefined - javascript

I'm trying to build simple todo application with new context API but I'm having a hard time trying to debug why my input value is undefined.
I don't quite understand how to execute a function provided by Provider and pass arguments to it.
here is my code:
import React, { Component } from "react";
import logo from "./logo.svg";
import "./App.css";
const AppContext = React.createContext();
class AppProvider extends Component {
constructor(props) {
super(props);
this.state = {
todoList: [{ text: "first test element", isCompleted: false }]
};
}
addTodo = (e) => {
e.preventDefault();
console.log(e.target.value);
this.setState({
todoList: [
...this.state.todoList,
{ text: e.target.value, isCompleted: false }
]
});
};
render() {
const { todoList, uniqueId } = this.state;
return (
<AppContext.Provider
value={{ todoList, addTodo: this.addTodo }}
>
{this.props.children}
</AppContext.Provider>
);
}
}
const Consumer = AppContext.Consumer;
class App extends Component {
constructor(props) {
super(props);
this.state = { value: "" };
}
render() {
return <AppProvider>
<div className="App">
<header className="App-header">
<img src={logo} className="App-logo" alt="logo" />
<h1 className="App-title">TodoList</h1>
</header>
<Consumer>
{state => <form onSubmit={state.addTodo}>
<input type="text" />
<button type="submit" onSubmit={state.addTodo}>
Add Todo
</button>
</form>}
</Consumer>
<Consumer>
{val => (
<ul>
{val.todoList.map((item, index) => (
<li key={index}>{item.text}</li>
))}
</ul>
)}
</Consumer>
</div>
</AppProvider>;
}
}
export default App;
Sorry if my questions is trivial but I've been trying to solve it for quite a bit and I can't find what's wrong.

You just had some issues with how your form was sending the value through. Also, you should try decouple your events and your state, as shown in the working example here.

Related

React: Render and link toggle button outside the class

I have the following example where the toggleComponent.js is working perfectly.
The problem here is that I don't want to render the <ContentComponent/> inside the toggle, rather I want the opposite, I want to toggle the <ContentComponent/> that will be called in another component depending on the state of the toggle.
So the <ContentComponent/> is outside the toggleComponent.js, but they are linked together. So I can display it externally using the toggle.
An image to give you an idea:
Link to funtional code:
https://stackblitz.com/edit/react-fwn3rn?file=src/App.js
import React, { Component } from "react";
import ToggleComponent from "./toggleComponent";
import ContentComponent from "./content";
export default class App extends React.Component {
render() {
return (
<div>
<ToggleComponent
render={({ isShowBody, checkbox }) => (
<div>
{isShowBody && <h1>test</h1>}
<button onClick={checkbox}>Show</button>
</div>
)}
/>
<ToggleComponent
render={({ isShowBody, checkbox }) => (
<div>
{isShowBody && (
<h1>
<ContentComponent />
</h1>
)}
<button onClick={checkbox}>Show</button>
</div>
)}
/>
</div>
);
}
}
Bit tweaked your source.
Modified ToggleComponent
import React from "react";
export default class ToggleComponent extends React.Component {
constructor() {
super();
this.state = {
checked: false
};
this.handleClick = this.handleClick.bind(this);
}
handleClick = () => {
this.setState({ checked: !this.state.checked });
this.props.toggled(!this.state.checked);
};
checkbox = () => {
return (
<div>
<label>Toggle</label>
<span className="switch switch-sm">
<label>
<input type="checkbox" name="select" onClick={this.handleClick} />
<span />
</label>
</span>
</div>
);
};
render() {
return this.checkbox();
}
}
Added OtherComponent with ContentComponent inside.
import React, { Component } from "react";
import ContentComponent from "./content";
export default class OtherComponent extends React.Component {
render() {
return <div>{this.props.show ? <ContentComponent /> : null}</div>;
}
}
Separated as per your requirement.
Modified App
import React, { Component, PropTypes } from "react";
import ToggleComponent from "./toggleComponent";
import OtherComponent from "./otherComponent";
export default class App extends React.Component {
constructor() {
super();
this.toggled = this.toggled.bind(this);
this.state = { show: false };
}
toggled(value) {
this.setState({ show: value });
}
render() {
return (
<div>
<ToggleComponent toggled={this.toggled} />
<OtherComponent show={this.state.show} />
</div>
);
}
}
Working demo at StackBlitz.
If you want to share states across components a good way to do that is to use callbacks and states. I will use below some functional components but the same principle can be applied with class based components and their setState function.
You can see this example running here, I've tried to reproduce a bit what you showed in your question.
import React, { useState, useEffect, useCallback } from "react";
import "./style.css";
const ToggleComponent = props => {
const { label: labelText, checked, onClick } = props;
return (
<label>
<input type="checkbox" checked={checked} onClick={onClick} />
{labelText}
</label>
);
};
const ContentComponent = props => {
const { label, children, render: renderFromProps, onChange } = props;
const [checked, setChecked] = useState(false);
const defaultRender = () => null;
const render = renderFromProps || children || defaultRender;
return (
<div>
<ToggleComponent
label={label}
checked={checked}
onClick={() => {
setChecked(previousChecked => !previousChecked);
}}
/>
{render(checked)}
</div>
);
};
const Holder = () => {
return (
<div>
<ContentComponent label="First">
{checked => (
<h1>First content ({checked ? "checked" : "unchecked"})</h1>
)}
</ContentComponent>
<ContentComponent
label="Second"
render={checked => (checked ? <h1>Second content</h1> : null)}
/>
</div>
);
};
PS: A good rule of thumb concerning state management is to try to avoid bi-directional state handling. For instance here in my example I don't use an internal state in ToggleComponent because it would require to update it if given checked property has changed. If you want to have this kind of shared state changes then you need to use useEffect on functional component.
const ContentComponent = props => {
const { checked: checkedFromProps, label, children, render: renderFromProps, onChange } = props;
const [checked, setChecked] = useState(checkedFromProps || false);
const defaultRender = () => null;
const render = renderFromProps || children || defaultRender;
// onChange callback
useEffect(() => {
if (onChange) {
onChange(checked);
}
}, [ checked, onChange ]);
// update from props
useEffect(() => {
setChecked(checkedFromProps);
}, [ checkedFromProps, setChecked ]);
return (
<div>
<ToggleComponent
label={label}
checked={checked}
onClick={() => {
setChecked(previousChecked => !previousChecked);
}}
/>
{render(checked)}
</div>
);
};
const Other = () => {
const [ checked, setChecked ] = useState(true);
return (
<div>
{ checked ? "Checked" : "Unchecked" }
<ContentComponent checked={checked} onChange={setChecked} />
</div>
);
};

Passing the result of a component to render returns an undefined?

I am trying to write a wrapper component around an API call to render "loading" if the api hasnt updated. Im very new to react, but I can t seem to figure out why the state isnt being passed to the ApiResp component:
Here is the console.log of the state changes..... why is the final apiResp in console.log undefined?
App.js
class App extends React.Component {
async componentDidMount() {
let xx = await axios.get(apiUrl)
console.log(`componentDidMount`, xx)
this.setState({ loading: false, body: xx });
}
render() {
console.log(`rendering ComponentLoading`, this.state)
const DisplayComponent = ComponentLoading(ApiResp)
return (
<div className="App">
<header className="App-header">
<img src={face} /*className="App-logo"*/ alt="face-img" />
</header>
<br></br>
<div>
<DisplayComponent isLoading={AppState.loading} body={AppState.body} />
</div>
</div>
);
}
}
export default App;
ComponentLoading:
import React from 'react';
function ComponentLoading(Component) {
return function WihLoadingComponent({ isLoading, ...props }) {
if (!isLoading) return <Component {...props} />;
return (
<p style={{ textAlign: 'center', fontSize: '30px' }}>
Loading
</p>
);
};
}
export default ComponentLoading;
apiResp.js
import React from 'react';
const ApiResp = (data) => {
console.log(`apiResp:`, data)
if (!data || data.statusCode !== 200) {
return <p>Err: {JSON.stringify(data)}</p>;
}
else
return (
<div>
<h3>obj:</h3>
{JSON.stringify(data)}
</div>
);
};
export default ApiResp;
ComponentLoading is a Higher Order Component. const DisplayComponent = ComponentLoading(ApiResp) is decorating the ApiResp component:
const ApiResp = (data) => {
console.log(`apiResp:`, data)
if (!data || data.statusCode !== 200) {
return <p>Err: {JSON.stringify(data)}</p>;
}
else
return (
<div>
<h3>obj:</h3>
{JSON.stringify(data)}
</div>
);
};
and returning a component you've called DisplayComponent.
As a component ApiResp is consuming a props object called data and only accesses a statusCode prop.
DisplayComponent is passed two props:
<DisplayComponent isLoading={AppState.loading} body={AppState.body} />
AppState isn't defined in the parent component but it seems this.state has the values you want passed to DisplayComponent.
Solution
Access and pass the correct object to props.
<DisplayComponent
isLoading={this.state.loading}
body={this.state.body}
/>
I suggest also moving the const DisplayComponent = ComponentLoading(ApiResp) declaration out of the render method, and also outside the App component.
const DisplayComponent = ComponentLoading(ApiResp);
class App extends React.Component {
state = {
loading: true,
body: null,
};
async componentDidMount() {
let xx = await axios.get(apiUrl)
console.log(`componentDidMount`, xx)
this.setState({ loading: false, body: xx });
}
render() {
return (
<div className="App">
<header className="App-header">
<img src={face} /*className="App-logo"*/ alt="face-img" />
</header>
<br></br>
<div>
<DisplayComponent
isLoading={this.state.loading}
body={this.state.body}
/>
</div>
</div>
);
}
}

How can i pass props through methods inside components?

i have a react component named "List" that renders smaller components "Post" using a button through method "Addpost()" that takes 2 props from the input form. I have saved the input in 2 varables but i don't know how to pass these props to the Addpost() method inside the return of List's render().
//=========== List component ==============
class List extends React.Component{
renderPost(title,content){
return(
<Post titolo={title} contenuto={content}/>
);
}
renderPost just render the Post component in a in the HTML
addPost(title,content){
title = document.getElementById("inputTitle").value;
content = document.getElementById("inputContent").value;
console.log(title, content)
this.renderPost(title,content);
}
addPost should take the input value and use renderPost to render the Post component with that title and content
render(){
return(
<div>
{this.renderPost("testTitle","testContent")}
<form>
Title:<br></br>
<input type="text" id="inputTitle"/><br></br>
Content:<br></br>
<input type="text" id="inputContent"/>
</form><br></br>
<button className="square"
how can i make this work? title and content are not defined
onClick={() =>
this.addPost(title,content)
Add Post!
</button>
</div>
);
}
}
//=========== Post component ==============
class Post extends React.Component {
render() {
return (
<li className="w3-padding-16">
<img src="/w3images/workshop.jpg" alt="Imagedf" className="w3-left w3-margin-right" />`enter code here`
<span className="w3-large">
{this.props.titolo}
</span><br></br>
<span>{this.props.contenuto}</span>
</li>
);
}
}
Basically, whenever you're dealing with forms and inputs, you would use refs.
App.js
import React from 'react';
import logo from './logo.svg';
import './App.css';
import PostList from './components/PostList'
import AddPostForm from './components/AddPostForm'
class App extends React.Component {
constructor(props) {
super(props)
this.state = {
posts: [] //state is handled here
}
this.addPost = this.addPost.bind(this)
}
addPost(title, content) {
let newPost = { title, content }
this.setState(({ posts }) => { return { posts: [...posts, newPost] } } )
}
render() {
const { posts } = this.state
return (
<div>
<AddPostForm onNewPost={this.addPost} /> //we pass addPost to the component
<br />
<PostList posts={posts} />
</div>
);
}
}
export default App;
Post.js
import React from 'react';
function Post({titolo, contenuto}) {
return (
<li className="w3-padding-16">
<img src="/w3images/workshop.jpg" alt="Imagedf" className="w3-left w3-margin-right" />`enter code here`
<span className="w3-large">
{titolo}
</span><br></br>
<span>{contenuto}</span>
</li>
);
}
export default Post
AddPostForm.js
import React from 'react';
const addPostForm = ({onNewPost = f => f}) => { //onNewPost method is passed by props from the parent
let _titleInput, _contentInput //these are our refs, see the docs for more information
const submit = (e) => {
e.preventDefault()
onNewPost(_titleInput.value, _contentInput.value) //here we call the addPost function that was passed to the component
_titleInput.value = '' //empty the inputs
_contentInput.value = ''
_titleInput.focus() //set focus
}
return (
<form onSubmit={submit}>
Title:<br></br>
<input type="text" ref={title => _titleInput = title} /><br></br>{/* Note the ref attribute */}
Content:<br></br>
<input type="text" ref={content => _contentInput = content} />
<button className="square">Add a new post</button>
</form>
)
}
export default addPostForm
PostList.js
import React from 'react';
import Post from './Post';
const PostList = ({ posts=[] }) => {
return (
<div className="post-list">
{
posts.map((post, index) =>
<Post key={index} titolo={post.title} contenuto={post.content} />
)
}
</div>
)
}
export default PostList
And the result:
edit
renderPost just render the Post component in a in the HTML
state = { inputTitle: '', inputContent: '' }
addPost(title,content){
title = document.getElementById("inputTitle").value;
content = document.getElementById("inputContent").value;
console.log(title, content)
this.renderPost(title,content);
}
addPost should take the input value and use renderPost to render the Post
component with that title and content
render(){
return(
<div>
{this.renderPost("testTitle","testContent")}
<form>
Title:<br></br>
<input type="text" value={this.inputTitle} onChnage={event => setState({ inputTitle: event.target.value }) }><br></br>
Content:<br></br>
<input type="text" value={this.inputContent} onChnage={event => setState({ inputContent: event.target.value }) } />
</form><br></br>
<button className="square"
on click function
onClick={() =>
this.addPost(this.inputTitle,this.inputContent)
Add Post!
</button>
</div>
);
}
}

My filter search list in react doesn't display the filtered list and runs into the error

This is Owner component(Owner.js) where I am implementing a search filter which should display restaurant with the same name as in search. But when I try to use the restaurant and implement filter search It goes to error. I should get the list of restaurant from my pre-existing restaurant list in owner dashboard.
import React, { Component } from "react";
import Restaurant from "../customer/Restaurant";
export default class Owner extends Component {
constructor() {
super();
this.state = {
search: ""
};
}
updateSearch(event) {
this.setState({
search: event.target.value.substr(0, 25)
});
}
render() {
let filteredRestaurants = this.props.restaurants;
return (
<div>
<h1> Welcome Owner </h1> <br />
<ul>
{" "}
{filteredRestaurants.map(res => {
return <restaurants res={<Restaurant />} key={res.name} />;
})}
</ul>{" "}
<input
type="text"
Value={this.state.search}
onChange={this.updateSearch.bind(this)}
/>{" "}
<Restaurant />
</div>
);
}
}
This is Restaurant.js which display the restaurant details in Owner.js.
import React, { Component } from "react";
import { connect } from "react-redux";
import PropTypes from "prop-types";
import { getRestaurant } from "../../actions/getRestaurants";
export class Restaurant extends Component {
static propTypes = {
// restaurants: PropTypes.array.isRequired,
getRestaurantName: PropTypes.func
};
componentDidMount() {
this.props.getRestaurant();
}
render() {
const contentKeys = Object.keys(this.props.restaurants);
// console.log(JSON.parse(this.props.restaurants))
return (
<div className="row">
{contentKeys.map(t =>
[this.props.restaurants[t]].map(res => (
<div className="col-md">
<div className="card" style={cardWidth}>
<img className="card-img-top" src="..." alt="Card image cap" />
<div className="card-body">
<h5 className="card-title">{res.Name}</h5>
<p className="card-text">
<h6>{res.Address}</h6>
<h6>{res.City}</h6>
<h6>{res.zipcode}</h6>
<h6>Open:{res.Hours.Open}</h6>
<h6>Close:{res.Hours.Close}</h6>
</p>
<a href="#" className="btn btn-primary">
Go somewhere
</a>
</div>
</div>
</div>
))
)}
</div>
);
}
}
const cardWidth = {
width: "18rem"
};
const mapStateToProps = state => ({
restaurants: state.restaurantReducer.restaurants
});
export default connect(
mapStateToProps,
{ getRestaurant }
)(Restaurant);
//export default Restaurant;
I am having an error in filtering restaurant from owner dashboard. As return statement does not return any value and runs into error.
{filteredRestaurants.map(res => {
return <restaurants res={<Restaurant />} key={res.name} />;
})}
You're storing search query in a state, but don't use it when rendering filteredRestaurants.
You should filter them before mapping. Something like that:
const filteredRestaurants = this.props.restaurants.filter(res => res.name.includes(this.state.search);
// ...
filteredRestaurants.map(res => <Restaurant key={res.name} />)
Also, you can make it much easier with React Hooks API, if you're using 16.8+ React.

TypeError: props.handleToggle is not a function

I write my first Todo app on React.
I get TypeError: props.handleToggle is not a function
and don't understand why?(another handlers are working).
Why this function can't be a function? What I'm doing wrong and what I need to read?
App.js
import React, { Component } from 'react';
import {TodoForm, TodoList} from './components/todo'
import {addTodo, generateId, findById, toggleTodo, updateTodo} from './lib/todoHelpers'
class App extends Component {
state = {
todos: [
{id: 1, name: 'Hello buddy', isComplete: true},
{id: 2, name: 'Hello rarrih', isComplete: false}
],
currentTodo: ''
}
handleToggle = (id) => {
const todo = findById(id, this.state.todos)
const toggled = toggleTodo(todo)
const updatedTodos = updateTodo(this.state.todos, toggled)
this.setState({todos: updatedTodos})
}
render(){
const submitHandler = this.state.currentTodo ? this.handleSubmit : this.handleEmptySubmit
return (
<div className="App">
<div className="App-header">
<img src={logo} className="App-logo" alt="logo" />
<h1 className="App-title">Todo</h1>
</div>
<div className="Todo-App">
{this.state.errorMessage && <span className='error'>{this.state.errorMessage}</span>}
<TodoForm handleInputChange={this.handleInputChange}
currentTodo={this.state.currentTodo}
handleSubmit={submitHandler}/>
<TodoList handleToggle={this.handleToggle} todos={this.state.todos}/>
</div>
</div>
);
}
}
export default App;
TodoList.js
import React from 'react'
import {TodoItem} from './TodoItem'
import PropTypes from 'prop-types'
export const TodoList = (props) => {
return (
<div className="Todo=List">
<ul>
{props.todos.map(todo => <TodoItem handleToggle={props.handleTooggle} key={todo.id} {...todo}/>)}
</ul>
</div>
)
}
TodoItem.js
//TodoItem
export const TodoItem = (props) => {
const handleToggle = props.handleToggle(props.id)
return (
<li>
<input type="checkbox" onChange={handleToggle}
checked={props.isComplete}/> {props.name}
</li>
)
}
I get TypeError: props.handleToggle is not a function
and don't understand why?(another handlers are working).
Why this function can't be a function? What I'm doing wrong and what I need to read?
You have mistake in TodoList.js, you pass on it prop name handleToggle, and tries to use handleTooggle.
This is a correct variant:
export const TodoList = (props) => {
return (
<div className="Todo=List">
<ul>
{props.todos.map(todo => <TodoItem handleToggle={props.handleToggle} key={todo.id} {...todo}/>)}
</ul>
</div>
)
}

Categories

Resources