Running a child component with a function in parent component - reactjs

so i have two components a Child "Board.js" that bring an array from backend and build a Board it looks like this:
const Board = (props) => {
const [data, setData] = useState([]);
const newBoard = async (e) => {
e.preventDefault();
const data = await fetch('http://localhost:8080/viergewinnt/newboard')
.then(response => response.json())
.then(data => {
// console.log(data);
return data
})
const arr =[];
data.map(d => arr.push([...d]))
console.log(arr);
setData(arr);
}
return (
<div className='center'>
<div className='board'>
{ data.map((d) => d.map((v, index) => (<BoardTile key ={index}/>)))}
</div>
</div>
)
and a parent component "NewGame" which has a button, this button when clicked must build that board in child component. The Parent component looks like this:
const NewGame = (props) => {
const onClickHandler = () =>{
return (<Board></Board>)
}
return (
<div className="App">
<div>
<button className='btn' onClick={onClickHandler}>Start new game</button>
</div>
</div>
);
PS. the Board was built successfully when all the code was in one Component.

import Board from './example.js';
import { useState } from 'react';
const NewGame = (props) => {
const [boardIsVisible, setBoardIsVisible] = useState(false);
return (
<div className="App">
<div>
<button
className='btn'
onClick={() => setBoardIsVisible(true)}>
Start new game
</button>
{boardIsVisible && <Board />}
</div>
</div>
);
}

Related

How to pass const useState("") between functions

I would like to pull the const ChatLog out of the main function Chats and insert it as a component or outside of the Chats function for now. The Problem is that the ChatLog needs the useState variables [msg, sendMsg] (..) that are called in the Chats function. How could I do this anyway? Am new to react.
function Chats() {
const [msg, sendMsg] = useState("");
const [msgs1, sendMsgAll] = useState([]);
useEffect(() => {
onValue(ref(database), (snapshot) => {
sendMsgAll([]);
const data = snapshot.val();
if (data !== null) {
Object.values(data).map((msg) => {
sendMsgAll((oldArray) => [...oldArray, msg]);
});
}
});
}, [])
const ChatLog = () => {
return (
<div>
{msgs1.map((msg) => (
<div className="chat-log">
<p align = {checkSide(msg.usr)}>
<h2>{msg.msg}</h2>
<h4>User: {msg.usr}</h4>
<h4>Time: {convertUnix(msg.time)}</h4>
<button>update</button>
<button>delete</button>
</p>
</div>
))}
</div>
)
}
return (
<div className="ChatView">
<p><ChatLog /></p>
<p>{ChatInput()}</p>
</div>
)
};
You can add props to ChatLog component. Check this...
const ChatLog = (props) => {
const {msg1} = props
return (
<div>
{msgs1.map((msg) => (
<div className="chat-log">
<p align = {checkSide(msg.usr)}>
<h2>{msg.msg}</h2>
<h4>User: {msg.usr}</h4>
<h4>Time: {convertUnix(msg.time)}</h4>
<button>update</button>
<button>delete</button>
</p>
</div>
))}
</div>
)
}
However,you need add props to component when you use it. Something like this...
return (
<div className="ChatView">
<p><ChatLog msg1={msg1} /></p>
<p>{ChatInput()}</p>
</div>
)
One more thing, when you declaring a component, it has to be declared of another component. Like this
//this is ChatLog component
const ChatLog = (props)=>{
return <div/>
}
//this is Chats component
const Chats = ()=>{
return (
<div>
<ChatLog {...props}/>
</div>
)
}

React app with api. Delete item for list doesn't work

When I click on the Delete button, my code does not work. There could be a problem in the function handleRemove.
import React, { useState, useEffect } from 'react'
import axios from 'axios'
// API endPoint - Punk API
const API_URL = 'https://api.punkapi.com/v2/beers'
const List = () => {
const [drinks, setDrinks] = useState([])
const [searchTerm, setSearchTerm] = useState('')
const fetchData = async () => {
const { data } = await axios.get(API_URL)
setDrinks(data)
}
useEffect(() => {
fetchData()
}, [])
const handleRemove = (id) => {
let groupd = drinks
const newList = groupd.filter(group => group.id !== id)
setDrinks(newList)
}
return (
<div>
<div className="wrapper">
<div className="search__main">
<input type='text' placeholder="search..." onChange={e => {setSearchTerm(e.target.value)}}/>
</div>
</div>
<div className="wrapper">
<div className="search__box">
{drinks.filter((val) => {
if(searchTerm === ""){
return val
} else if(val.name.toLowerCase().includes(searchTerm.toLowerCase()) || val.description.toLowerCase().includes(searchTerm.toLowerCase())){
return val
}
}).map((drink, key) => {
return(
<div key={key} className="search__mini__box">
<div >
<img src={drink.image_url} alt="drink" className="search__img"/>
</div>
<h4>{drink.name}</h4>
<p>{drink.description}</p>
<button type="button" onClick={handleRemove(drink.id)}>
delete
</button>
</div>
)
})}
</div>
</div>
</div>
)
}
export default List
Since your handleRemove function call is within a return statement, you need to call the function like so:
onClick={() => handleRemove(drink.id)}
What happens is, the function is called immediately on render if done the way you've proposed in your question. We want the function to be called only when the button is clicked.

TypeError: data.comments is undefined

I'm fetching two things. An item by id and then the item comments by item id. I when npm start I get
TypeError: data.comments is undefined
But if I comment out
<Comment data={itemComments} />
And then run npm start, the item data loads and if I uncomment the comment tag after the item data has already loaded comments shows until I refresh or reload again, it's only when I try to load them simultaneously I get the error.
Item.js
import React, { useEffect, useState } from "react";
import Comment from "./Comment";
import axios from "axios";
const Item = () => {
const itemId = "6019afbce548e33e7c2f4e56";
const [item, setItem] = useState([]);
const [itemComments, setItemComments] = useState([]);
const fetchData = () => {
const item = `http://localhost:3000/api/v1/items/${itemId}`;
const itemComments = `http://localhost:3000/api/v1/items/${itemId}/comments`;
const getItem = axios.get(item);
const getItemComments = axios.get(itemComments);
axios.all([getItem, getItemComments]).then(
axios.spread((...allData) => {
const allItemData = allData[0].data;
const allItemCommentsData = allData[1].data;
setItem(allItemData);
setItemComments(allItemCommentsData);
})
);
};
useEffect(() => {
fetchData();
}, []);
return (
<div>
<div>
<div>
<h3>{item.title}</h3>
</div>
<div>
<p>Price</p>
<p>${item.price}</p>
</div>
<div>
<h3>Offers & Comments</h3>
<Comment data={itemComments} />
</div>
</div>
</div>
);
};
export default Item;
ItemComments.js
import React from "react";
const Message = (props) => {
const { data } = props;
console.log(data);
return (
<>
{data &&
data.comments.map((comment, i) => (
<div key={i}>
<div>
<div>
<p>{comment.comment}</p>
</div>
</div>
</div>
))}
</>
);
};
export default Message;
After first render react try to access comments inside itemComments when its just an empty array, and you just check if its not undefined in your children component:
{data && data.comments.map((comment, i) => (
<div key={i}>
<div>
<div>
<p>{comment.comment}</p>
</div>
</div>
</div>
))}
so change your initial state to this:
const [itemComments, setItemComments] = useState({comments:[]});
name your comment state by its initial name like this "useState({comments:[]})" and make sure that data.comment is not empty and also try to make fetch data asynchronously and let me know the result, please
const fetchData = async () => {
const item = `http://localhost:3000/api/v1/items/${itemId}`;
const itemComments = `http://localhost:3000/api/v1/items/${itemId}/comments`;
const getItem = await axios.get(item);
const getItemComments = await axios.get(itemComments);
const allData=await axios.all([getItem, getItemComments])
const allItemData= await axios.spread((...allData) => allData[0].data)
const allItemCommentsData= await axios.spread((...allData) => allData[1].data)
setItem(allItemData);
setItemComments(allItemCommentsData);
};

React: How to update state for just one element, rather than batch update

I am a beginner with React. I have a project I'm working on with some sample travel tours. I would like to use a "read more/show less" feature for the description of each tour. The read more/show less button is toggling, but it's showing more or less description for all of the tours when clicked, when I want it to just toggle the tour that's clicked. In other words, it's updating the state for ALL tours, rather than just the one that's clicked. Hopefully that makes sense. Please help! Thanks in advance.
import React, { useState, useEffect } from 'react';
import './index.css';
const url = 'https://course-api.com/react-tours-project';
const Tour = () => {
const [tourItem, setTourItem] = useState('');
const removeItem = (id) => {
let newList = tourItems.filter((item) => item.id !== id);
setTourItem(newList);
};
const [fetchingData, setFetchingData] = useState(true);
useEffect(() => {
const abortController = new AbortController();
const fetchUrl = async () => {
try {
const response = await fetch(url, {
signal: abortController.signal,
});
if (fetchingData) {
const data = await response.json();
setTourItem(data);
}
setFetchingData(false);
} catch (e) {
console.log(e);
}
};
fetchUrl();
return () => {
//cleanup!
abortController.abort();
};
});
const tourItems = Object.values(tourItem);
const [readMore, setReadMore] = useState(false);
return (
<>
{tourItems.map((item) => {
return (
<div key={item.id}>
<article className='single-tour'>
<img src={item.image} alt={item.name} />
<footer>
<div className='tour-info'>
<h4>{item.name}</h4>
<h4 className='tour-price'>
${item.price}
</h4>
</div>
{readMore ? (
<p>
{item.info}
<button
onClick={() => setReadMore(false)}
>
Show Less
</button>
</p>
) : (
<p>
{item.info.slice(0, 450) + '...'}
<button
onClick={() => setReadMore(true)}
>
Read More
</button>
</p>
)}
<button
className='delete-btn'
onClick={() => removeItem(item.id)}
>
Not Interested
</button>
</footer>
</article>
</div>
);
})}
</>
);
};
export default Tour;
Good question! It happened because you share the readMore state with all of the tour items. You can fix this by encapsulating the tour items into a component.
It should look something like this;
The component that encapsulates each tour items
import React, {useState} from "react";
import "./index.css";
const SpecificTourItems = ({item, removeItem}) => {
const [readMore, setReadMore] = useState(false);
return (
<div key={item.id}>
<article className="single-tour">
<img src={item.image} alt={item.name} />
<footer>
<div className="tour-info">
<h4>{item.name}</h4>
<h4 className="tour-price">${item.price}</h4>
</div>
{readMore ? (
<p>
{item.info}
<button onClick={() => setReadMore(false)}>Show Less</button>
</p>
) : (
<p>
{item.info.slice(0, 450) + "..."}
<button onClick={() => setReadMore(true)}>Read More</button>
</p>
)}
<button className="delete-btn" onClick={() => removeItem(item.id)}>
Not Interested
</button>
</footer>
</article>
</div>
);
};
export default SpecificTourItems;
the component that fetch & maps all the tour items (your old component :))
import React, {useState, useEffect} from "react";
import SpecificTourItems from "./SpecificTourItems";
const url = "https://course-api.com/react-tours-project";
const Tour = () => {
const [tourItem, setTourItem] = useState("");
const removeItem = (id) => {
let newList = tourItems.filter((item) => item.id !== id);
setTourItem(newList);
};
const [fetchingData, setFetchingData] = useState(true);
useEffect(() => {
const abortController = new AbortController();
const fetchUrl = async () => {
try {
const response = await fetch(url, {
signal: abortController.signal,
});
if (fetchingData) {
const data = await response.json();
setTourItem(data);
}
setFetchingData(false);
} catch (e) {
console.log(e);
}
};
fetchUrl();
return () => {
//cleanup!
abortController.abort();
};
});
const tourItems = Object.values(tourItem);
const [readMore, setReadMore] = useState(false);
return (
<>
{tourItems.map((item, key) => {
return (
<SpecificTourItems item={item} removeItem={removeItem} key={key} />
);
})}
</>
);
};
export default Tour;
I hope it helps, this is my first time answering question in Stack Overflow. Thanks & Good luck!

refresh the comment list after form submitted

I am creating a CommentBox.
I am trying to refresh the comment list after form submission.
#CommentBox.js
const CommentBox = (props) => {
return (
<div className="comment_area clearfix mb-5">
<div className="section-heading style-2 mb-5">
<h4>Comment</h4>
<div className="line"></div>
</div>
< CommentForm />
< CommentList />
</div>
);
}
As you can see I have different components for CommentForm.js and CommentList.js
#CommentForm.js
const onSubmitHandler = (e) => {
........
axios.post(......................)
................................
}
return (
<form onSubmit={onSubmitHandler}>
|
|
</form>
);
#CommentList.js
useEffect(() => {
const id = props.postId;
const fetchData = async () => {
try {
const res = await axios.get(
`.......................`
)
setComments(res.data.results);
} catch (err) {}
};
fetchData();
}, [props.postId])
return (
.................
.................
......
)
How should I write the GET Method in form onsubmitHandler().
Or I have to change some other things to make it work.
A way to solve this is to move the state to your parent component, that is, make the axio calls to the parent component CommentBox. The CommentForm notifies the parent via callback that the form has been submitted and then you link one axios call after the other, passing the GET results to CommentList.
const CommentBox = (props) => {
const [comments, setComments] = useState([]);
const onSubmitHandler = (e) => {
........
axios.post(.......)
.then(() => axios.get())
.then((res) => setComments(res.data);
}
return (
<div className="comment_area clearfix mb-5">
<div className="section-heading style-2 mb-5">
<h4>Comment</h4>
<div className="line"></div>
</div>
< CommentForm onSubmit={onSubmitHandler}/>
< CommentList comments={comments}/>
</div>
);
}

Resources