i send a usestate thro AppContexet.Provider to AppContexet.Consumer,
i can see at the components - the data arived but when i tray to map the state i get an erorr.
what m i doing wrong?
this is the perent :
import {useState} from 'react'
import Ex53Child from './Ex53Child'
import AppContexet from './Appcontext'
import React, {Component} from 'react';
const Ex53perent = ()=>
{
const [name,setName] = useState('')
const [age,setAge] = useState()
const [users,setUsers] = useState ([])
const [user,setUser] = useState ({name : '' , age : ''})
return(<AppContexet.Provider value = {{...users}}>
<div>
<h1> Ex53perent comp </h1>
Name: <input type = "text" onChange = {e=>setUser({...user ,name : e.target.value})}/><br/>
Age : <input type = "text" onChange = {e=>setUser({...user ,age : e.target.value})}/><br/>
<input type = "button" value ="Add" onClick ={e=>setUsers([...users,user])}/>
<Ex53Child/>
</div>
</AppContexet.Provider>
)
}
export default Ex53perent;
this is the child :
import {useState} from 'react'
import AppContexet from './Appcontext'
import Ex53GrenChild from './Ex53GrenChild'
const Ex53Child = ()=>
{
const [myUesr,setMyUser] = useState([])
return(<AppContexet.Consumer>
{
dataContext =>
(
<div>
<h1> Ex53Child comp </h1>
{
dataContext.users.map((item,index)=>
{
return<il key = {index}>
<li>{item.name}</li>
<li>{item.age}</li>
</il>
})
}
<Ex53GrenChild/>
</div>
)
}
</AppContexet.Consumer>
)
}
export default Ex53Child;
and this is the appcontext file:
import React from 'react'
const AppContexet = React.createContext();
export default AppContexet;
a singel value work just fine
but i cant map the arry from some resen
Issue
The users state is an array:
const [users, setUsers] = useState([]);
And you are spreading the array into an object:
<AppContexet.Provider value={{ ...users }}>
...
</AppContexet.Provider>
This makes the context value an object where the array indices are now object keys and the array values are object values.
Solutions
Convert context value to array
You could keep it this way, and then you'll need to convert the context value back to an array in the child:
<AppContexet.Consumer>
{dataContext => (
<div>
<h1>Ex53Child comp</h1>
{Object.values(dataContext).map((item, index) => {
return (
<il key={index}>
<li>{item.name}</li>
<li>{item.age}</li>
</il>
);
})
}
<Ex53GrenChild/>
</div>
)}
</AppContexet.Consumer>
Fix context value, keep it an array
It's better to just keep the users and context values consistently an array. Don't spread the users state into the context value.
<AppContexet.Provider value={{ users }}>
...
</AppContexet.Provider>
Now's the context value is an object with a single users property that is the users state.
<AppContexet.Consumer>
{dataContext => (
<div>
<h1>Ex53Child comp</h1>
{dataContext.users.map((item, index) => {
return (
<il key={index}>
<li>{item.name}</li>
<li>{item.age}</li>
</il>
);
})
}
<Ex53GrenChild/>
</div>
)}
</AppContexet.Consumer>
Use useContext hook
Since Ex53Child is a function component it's a bit silly to use the AppContexet.Consumer component render function. Use the useContext hook instead.
const Ex53Child = () => {
const [myUesr, setMyUser] = useState([]);
const { users } = useContext(AppContexet);
return (
<div>
<h1>Ex53Child comp</h1>
{users.map((item, index) => (
<il key={index}>
<li>{item.name}</li>
<li>{item.age}</li>
</il>
))}
<Ex53GrenChild />
</div>
);
}
Just pass users as a prop
Additionally, since Ex53perent is directly rendering Ex53Child, using the context is unnecessary complexity when the users state could just simply be passed as a prop.
const Ex53perent = () => {
const [name, setName] = useState('');
const [age, setAge] = useState();
const [users, setUsers] = useState([]);
const [user, setUser] = useState({ name: '', age: '' });
return (
<div>
<h1> Ex53perent comp </h1>
Name:{" "}
<input
type="text"
onChange={e => setUser({ ...user, name: e.target.value })}
/>
<br/>
Age:{" "}
<input
type="text"
onChange={e => setUser({ ...user, age: e.target.value })}
/>
<br/>
<input
type="button"
value="Add"
onClick={e => setUsers([...users, user])}
/>
<Ex53Child users={users} /> // <-- pass users as prop to child
</div>
);
};
...
const Ex53Child = ({ users }) => { // <-- access users from props
const [myUesr, setMyUser] = useState([]);
return (
<div>
<h1>Ex53Child comp</h1>
{users.map((item, index) => (
<il key={index}>
<li>{item.name}</li>
<li>{item.age}</li>
</il>
))}
<Ex53GrenChild />
</div>
);
}
Related
I'm trying to pass data to the parent component Top.js using props from a child component TagsInput.js where I can add tags but
I don't understand what is causing the error...
What I want to achieve
I want to pass "tags" to the parent component Top.js from TagsInput.js in the child component with props.
I got the error like
props.setTagsinput is not a function
TagsInput.js
import React from "react";
const TagsInput = (props) => {
//1, Define the tags variable to store the entered tags. (I want to pass the value of the tags variable to the parent component Top.js)
const [tags, setTags] = React.useState([]);
//2, Put formText in the function received from the parent component and return it.
props.setTagsinput(tags);
console.log(props)
let tag_list = []
tag_list.push(tags);
const addTags = event => {
if (event.key === "Enter" && event.target.value !== "") {
setTags([...tags, event.target.value]);
event.target.value = "";
}
};
const removeTags = index => {
setTags([...tags.filter(tag => tags.indexOf(tag) !== index)]);
};
return (
<div className="tags-input">
<div className="tags_section">
{tags.map((tag, index) => (
<div className="tag tag-flex" key={index}>
<p className="tag-p">{tag}</p>
</div>
))}
</div>
<input
type="text"
onKeyUp={event => addTags(event)}
placeholder="Press enter to add tags"
/>
</div>
);
};
export default TagsInput;
Top.js
import React, {useState, useEffect} from 'react';
import axios from 'axios';
import Student from './Student';
import TagsInput from "./TagsInput";
const Top = () => {
const [ posts, setPosts] = useState([]);
const [ allPosts, setAllPosts] = useState([]);
let tag_list = []
const [searchKeyword, setSearchKeyword] = React.useState("");
const [searchTagKeyword, setTagSearchKeyword] = React.useState("");
console.log(searchKeyword)
const[tags_from_tagsinput, setTagsinput]= useState("");
console.log(tags_from_tagsinput);
useEffect(() => {
axios.get('xxx.com')
.then(result => {
setPosts(result.data.students);
setAllPosts(result.data.students);
if (searchKeyword) {
getSearchResult()
}
})},
[searchKeyword]);
const getSearchResult = () => {
console.log(searchKeyword)
const result = allPosts.filter((output, index) => {
return output.firstName.toLowerCase().includes(searchKeyword.toLowerCase())||output.lastName.toLowerCase().includes(searchKeyword.toLowerCase());
});
console.log(result)
setPosts(result);
};
const getTagSearchResult = () => {
console.log(searchTagKeyword)
const result = allPosts.filter((output, index) => {
return output.lastName.toLowerCase().includes(searchTagKeyword.toLowerCase());
});
console.log(result)
setPosts(result);
};
return (
<div>
<TagsInput setTagsinput={setTagsinput}/>
<div>
<input className="search-box" placeholder="" value={searchKeyword} onChange={(e) => setSearchKeyword(e.target.value)}/>
</div>
<div>
<input className="search-box" placeholder="" value={searchTagKeyword} onChange={(e) => setSearchKeyword(e.target.value)}/>
</div>
<div>
{searchKeyword &&
<p>{searchKeyword} Search</p>
}
{posts ?
<>
{posts.map((data, i) =>
<Student data={data} />
)}
</>
:
<div>
<p>Not Found!</p>
</div>
}
</div>
</div>
);
}
export default Top;
Student.js
import React, {useState} from 'react';
import TagsInput from './TagsInput';
const Student = (props) => {
const [show, setShow] = useState(false)
const gradesAverage = (grades) => {
let sum = 0;
grades.forEach(function(score) {
sum += Number(score);
});
let ave = sum / grades.length
return ave;
};
return (
<div className="flex">
<div className="image">
<img src={props.data.pic} className="profile" />
</div>
<div>
<p className="name">{props.data.firstName} {props.data.lastName}</p>
<button className="button" onClick={() => setShow(!show)}>
{show? <div className="button_p">-</div>:<div className="button_p">+</div>}
</button>
<div className="info">
<p>Email: {props.data.email}</p>
<p>Company: {props.data.company}</p>
<p>Skill: {props.data.skill}</p>
<p>Average Grade: {gradesAverage(props.data.grades)}%</p>
{show &&
<>
<p>Test 1: {props.data.grades[0]}%</p>
<p>Test 2: {props.data.grades[1]}%</p>
<p>Test 3: {props.data.grades[2]}%</p>
<p>Test 4: {props.data.grades[3]}%</p>
<p>Test 5: {props.data.grades[4]}%</p>
<p>Test 6: {props.data.grades[5]}%</p>
<p>Test 7: {props.data.grades[6]}%</p>
<p>Test 8: {props.data.grades[7]}%</p>
</>
}
<TagsInput />
</div>
</div>
</div>
);
}
export default Student;
You can not directly use one component hook declaration in another component, you need to have a callback function to update that state. I modified your code to use the top page setTagsinput in student tag input
Top.js
import React, { useState, useEffect } from "react";
import axios from "axios";
import Student from "./Student";
import TagsInput from "./TagsInput";
const Top = () => {
const [posts, setPosts] = useState([]);
const [allPosts, setAllPosts] = useState([]);
let tag_list = [];
const [searchKeyword, setSearchKeyword] = React.useState("");
const [searchTagKeyword, setTagSearchKeyword] = React.useState("");
console.log(searchKeyword);
const [tags_from_tagsinput, setTagsinput] = useState("");
console.log(tags_from_tagsinput);
useEffect(() => {
axios.get("xxx.com").then((result) => {
setPosts(result.data.students);
setAllPosts(result.data.students);
if (searchKeyword) {
getSearchResult();
}
});
}, [searchKeyword]);
const getSearchResult = () => {
console.log(searchKeyword);
const result = allPosts.filter((output, index) => {
return (
output.firstName.toLowerCase().includes(searchKeyword.toLowerCase()) ||
output.lastName.toLowerCase().includes(searchKeyword.toLowerCase())
);
});
console.log(result);
setPosts(result);
};
const getTagSearchResult = () => {
console.log(searchTagKeyword);
const result = allPosts.filter((output, index) => {
return output.lastName
.toLowerCase()
.includes(searchTagKeyword.toLowerCase());
});
console.log(result);
setPosts(result);
};
const setTagsFromStudent = (tags) => {
setTagsinput(tags);
};
return (
<div>
<div>
<input
className="search-box"
placeholder=""
value={searchKeyword}
onChange={(e) => setSearchKeyword(e.target.value)}
/>
</div>
<div>
<input
className="search-box"
placeholder=""
value={searchTagKeyword}
onChange={(e) => setSearchKeyword(e.target.value)}
/>
</div>
<div>
{searchKeyword && <p>{searchKeyword} Search</p>}
{posts ? (
<>
{posts.map((data, i) => (
<Student data={data} setStudentTags={setTagsFromStudent} />
))}
</>
) : (
<div>
<p>Not Found!</p>
</div>
)}
</div>
</div>
);
};
export default Top;
Student.js
import React, { useState } from "react";
import TagsInput from "./TagsInput";
const Student = (props) => {
const [show, setShow] = useState(false);
const gradesAverage = (grades) => {
let sum = 0;
grades.forEach(function (score) {
sum += Number(score);
});
let ave = sum / grades.length;
return ave;
};
return (
<div className="flex">
<div className="image">
<img src={props.data.pic} className="profile" />
</div>
<div>
<p className="name">
{props.data.firstName} {props.data.lastName}
</p>
<button className="button" onClick={() => setShow(!show)}>
{show ? (
<div className="button_p">-</div>
) : (
<div className="button_p">+</div>
)}
</button>
<div className="info">
<p>Email: {props.data.email}</p>
<p>Company: {props.data.company}</p>
<p>Skill: {props.data.skill}</p>
<p>Average Grade: {gradesAverage(props.data.grades)}%</p>
{show && (
<>
<p>Test 1: {props.data.grades[0]}%</p>
<p>Test 2: {props.data.grades[1]}%</p>
<p>Test 3: {props.data.grades[2]}%</p>
<p>Test 4: {props.data.grades[3]}%</p>
<p>Test 5: {props.data.grades[4]}%</p>
<p>Test 6: {props.data.grades[5]}%</p>
<p>Test 7: {props.data.grades[6]}%</p>
<p>Test 8: {props.data.grades[7]}%</p>
</>
)}
{/*pass settag from topTag component*/}
<TagsInput setStudentTags={props.setStudentTags} />
</div>
</div>
</div>
);
};
export default Student;
TagsInput.js
import React from "react";
const TagsInput = (props) => {
const [tags, setTags] = React.useState([]);
let tag_list = [];
tag_list.push(tags);
const addTags = (event) => {
if (event.key === "Enter" && event.target.value !== "") {
setTags([...tags, event.target.value]);
// call function pass down from toptag
props.setStudentTags(tags);
event.target.value = "";
}
};
const removeTags = (index) => {
setTags([...tags.filter((tag) => tags.indexOf(tag) !== index)]);
};
return (
<div className="tags-input">
<div className="tags_section">
{tags.map((tag, index) => (
<div className="tag tag-flex" key={index}>
<p className="tag-p">{tag}</p>
</div>
))}
</div>
<input
type="text"
onKeyUp={(event) => addTags(event)}
placeholder="Press enter to add tags"
/>
</div>
);
};
export default TagsInput;
You should consider exploring React context -https://reactjs.org/docs/context.html, its built exactly for something like this.
You are getting this error because, like you mentioned, TagsInput component is used in Student component but it doesn’t pass the state setter setTagsInput function to the TagsInput component.
Now, assuming you need tags created inside Student and displayed in Top, also assuming that both are rendered in the same parent component, you can create a state for tags in the parent component. This component will pass a state setter function to Student which passes the setter to TagsInput and the state itself to Top to use the list of tags.
Something like:
const App = () => {
const [tags,setTags] = useState([]);
return (<div>
<Top tags={tags} />
<Student setTags={setTags} />
</div>);
}
Your Student component can then pass it to TagsInput like:
const Student = (props) => {
return (<div>
{/* everything else */}
<TagsInput setTagsinput={props.setTags} />
</div>)
}
In your Top component you can create a function that updates your tags_from_tagsinput hook then pass it as props to the child component
import TagsInput from "./TagsInput";
const Top = () => {
const[tags_from_tagsinput, setTagsinput]= useState("");
console.log(tags_from_tagsinput);
const getTag = (value) => {
setTagsinput(value);
};
return (
<div>
<TagsInput getTag={getTag} />
</div>
);
}
export default Top;
Now from your TagsInput component you can call this function to update tags_from_tagsinput of Top, let's suppose that you want to updated when the user click on a button
import React from "react";
const TagsInput = (props) => {
return (
<div className="tags-input">
...
<button onClick={()=>{props.getTag(tags)}}>updated parent component</button>
</div>
);
};
export default TagsInput;
Code:
export default function App() {
const [name,setName] = useState("");
var myArray = [];
const handleAdd = () => {
myArray = [...myArray,name]
setName("")
}
return (
<div className="App">
<input placeholder="type a name" onChange={(e) => setName(e.target.value)}/>
<button onClick={handleAdd}>add</button>
<button onClick={() => console.log(myArray)}>test</button>
{myArray.map((n) => {
return <h2>{n}</h2>
})}
</div>
);
}
OnClick it isn't adding the name to the array.
this is how you "push" to an array with useState
const [array, setArray] = useState([])
setArray(previous => [...previuous, newItem])
You should use a state for your array and set that state to see the changes reflected:
export default function App() {
const [name, setName] = useState('');
const [myArray, setMyArray] = useState([]);
const handleAdd = () => {
setMyArray([...myArray, name]);
setName('');
};
return (
<div className="App">
<input
placeholder="type a name"
onChange={(e) => setName(e.target.value)}
/>
<button onClick={handleAdd}>add</button>
<button onClick={() => console.log(myArray)}>test</button>
{myArray.map((n) => {
return <h2>{n}</h2>;
})}
</div>
);
}
We can also set the state of myArr to be an empty array initially, making it easier to manipulate the subsequent state of that array. The onClick event handler does not fire the handleAdd function, for some reason, it only resets the form and does not provide any state. To submit the form and materialize the state, we can also use the onSubmit event handler instead of onClick. In the same way, we can use the name state as a value/prop for the name input, which will be used by the onChange handler.
import React, { useState } from 'react'
const App = () => {
const [name, setName] = useState('')
const [myArr, setMyArr] = useState([])
const submit = (event) => {
event.preventDefault()
setMyArr(myArr.concat(name))
setName('')
}
//console.log(myArr)
return (
<div className="App">
<form onSubmit={submit}>
<div>
<label htmlFor="name">Name</label>
<input
placeholder="type a name"
type="text"
value={name}
onChange={({ target }) => setName(target.value)}
/>
</div>
<div>
<button type="submit">Add</button>
</div>
</form>
<div>
{myArr.map((arr, index) => (
<div key={index}>
<p>{arr}</p>
</div>
))}
</div>
</div>
)
}
export default App
I have a proclivity of inserting items on an array using concat.
import React, { useState } from 'react'
// ...
const App = () => {
// ...
const [myArr, setMyArr] = useState([])
// somewhere on your event handler e.g. Submit handler
setMyArr(myArr.concat(name))
// ...
}
I am beginner and practicing on Library Management System in react. So I have components named BookDetails.js, BookList.js. BookDetails contains the form for entering Title and Description. So How can I pass the data entered from BookDetails to BookList and to dispaly from App.
import React, { useState } from 'react'
import BookList from './BookList'
const BookDetails = (props) => {
const [bookdetails, setbookDetails] = useState('')
const [desc, setDesc] = useState('')
const titleChangehandler = (e) => {
setbookDetails(e.target.value)
}
const descriptionChangehandler = (e) => {
setDesc(e.target.value)
}
const submitHandler = (e) => {
e.preventDefault()
return (
<div className='bookdetails'>
<form className='form_bookdetails' onSubmit={submitHandler}>
<div>
<label>Enter Title:</label>
<input type='text' value={bookdetails} onChange={titleChangehandler}></input>
</div>
<div>
<label>Enter Description:</label>
<input type='text' value={desc} onChange={descriptionChangehandler}></input>
</div>
<div>
<button type='submit'>Add Details</button>
</div>
</form>
</div>
)
}
}
export default BookDetails
BookList.js
import React from 'react'
import './BookList.css'
import BookDetails from './BookDetails'
const BookList = () => {
return (
<div className="booklist">
<header>BookList</header>
<BookDetails />
</div>
)
}
export default BookList
You need to use props. BookList state will have an update function that it will pass to the BookDetail via props. Example (CodeSandbox) with Todo with title & description.
BookDetail will invoke this method on every save which then would update the original list.
TodoList.js
export default function TodoList() {
const [todo, setTodo] = React.useState(null);
const [todoList, setTodoList] = React.useState([]);
React.useEffect(() => {
getTodos();
}, []);
function getTodos() {
console.log("===> fetch all todos!!");
fetchTodos().then((todos) => {
setTodoList(todos);
});
}
function editTodo(todo) {
console.log("===> set todo => ", todo);
setTodo(todo);
}
function handleUpdate(updatedTodo) {
// update Todo
const updatedTodos = todoList.map((el) =>
el.id === updatedTodo.id ? updatedTodo : el
);
setTodoList(updatedTodos);
setTodo(null);
}
return (
<div>
<ul>
{todoList.map((item) => (
<li key={item.id}>
{item.title}, {item.description}
<button onClick={() => editTodo(item)}>edit</button>
</li>
))}
</ul>
{todo && <TodoDetail todo={todo} updateTodo={handleUpdate} />}
</div>
);
}
TodoDetail.js
import React from "react";
export default function TodoDetail(props) {
const [todo, setTodo] = React.useState(props.todo);
console.log("todo =>", todo);
function handleChange(key, value) {
console.log("===> todo changed!");
setTodo({
...todo,
[key]: value
});
}
function handleSubmit() {
// api PUT on todo
console.log("===> todo edit submit!!");
props.updateTodo(todo);
}
return (
<div>
<form onSubmit={handleSubmit}>
<label htmlFor="title">
<input
value={todo.title}
onChange={(e) => handleChange("title", e.target.value)}
/>
<input
value={todo.description}
onChange={(e) => handleChange("description", e.target.value)}
/>
</label>
<button type="submit">submit</button>
</form>
</div>
);
}
You can store the list of books in your BookList component like
const [bookList, setBookList] = useState([])
This way your BookList component has access to the books. You can then create a function to add books to the list
function addBook(book) {
setBookList([...bookList, book])
}
Then pass the addBook() function to the BookDetails component to use it on submit.
<BookDetails addBook={addBook}
Now BookDetails can access the function as a prop
props.addBook("pass new book here")
I am Creating a Todo-List using typescript-react and my code doesn't seem to work.
App.tsx:
import { useState } from "react";
import "./App.css";
export default function App() {
let id = 0;
const [todoInput, setTodoInput] = useState("");
let todos: string[] = ["Arbit"];
const handleSubmit = (e: any) => {
e.preventDefault();
todos.push(todoInput);
console.log(todos)
setTodoInput("");
id += 1;
// console.log(todos);
// console.log(todos[id])
// console.log(e.target[0].value);
// todos.push(e.target[0].value);
// console.log(e)
};
return (
<div className='App'>
<form onSubmit={handleSubmit}>
<input
type='text'
value={todoInput}
onChange={(e) => setTodoInput(e.target.value)}
/>
<button>Submit</button>
</form>
<ul>
{todos.map((todo) => (
<li key={id}>{todo}</li>
))}
</ul>
</div>
);
}
The todos array doesn't push the todoInput. Instead, it omits todos[1].
I am creating my app in a single file.
In react functions you have to use hooks in order to retain the state during re-renders. In this case, you need to turn your todos array into a const [todos, setTodos] = React.useState() and update it using the setTodos function in a functional way.
const [todos, setTodos] = React.useState([]);
// also use useState for ids
const [id, setId] = React.useState(0);
const handleSubmit = (e: any) => {
e.preventDefault();
setTodos([...todos, todoInput]);
console.log(todos)
setTodoInput("");
setId(id + 1);
};
I am not sure, but I think you need to make the array a state too:
import { useState } from "react";
import "./App.css";
export default function App() {
let id = 0;
const [todoInput, setTodoInput] = useState("");
const [todos, setTodos] = useState(["Arbit"]);
const handleSubmit = (e: any) => {
e.preventDefault();
todos.push(todoInput);
console.log(todos)
setTodoInput("");
setTodos(todos);
id += 1;
// console.log(todos);
// console.log(todos[id])
// console.log(e.target[0].value);
// todos.push(e.target[0].value);
// console.log(e)
};
return (
<div className='App'>
<form onSubmit={handleSubmit}>
<input
type='text'
value={todoInput}
onChange={(e) => setTodoInput(e.target.value)}
/>
<button>Submit</button>
</form>
<ul>
{todos.map((todo) => (
<li key={id}>{todo}</li>
))}
</ul>
</div>
);
}
I'm sorry if it doesn't work.
I want to add new input tags to my web page when I click on Add input button.
Attempt:
import React, { useState } from "react";
import "./styles.css";
export default function App() {
const [addInput, setAddInput] = useState(false);
const handleClick = () => setAddInput(true);
return (
<div className="App">
<button onClick={handleClick}>Add Input</button>
{addInput ? <input type="text" /> : ""}
</div>
);
}
How can I make the above code work to dynamically add new inputs?
One way to do want you want.
const [state, setState] = useState([]);
const handleClick = () => {
let inputs = Object.assign([], state);
inputs.push(<input type='text' key={inputs.length} />);
setState(inputs);
}
return (
<div className="App">
<button onClick={handleClick}>Add Input</button>
{state}
</div>
);
You should know, added inputs are Uncontrolled Components.
In order to help you to learn more about the possible solutions, here is another approach:
const [state, setState] = useState(0);
const handleClick = () => setState(state + 1);
const generateInputs = () => {
const inputs = [];
for (let i = 0; i < state; i++) inputs.push(<input key={i} type='text' />);
return inputs;
}
return (
<div className="App">
<button onClick={handleClick}>Add Input</button>
{generateInputs()}
</div>
);