I have got a parent file and 2 child files,
the Parent file(App.js). it contains the showGraph state.
function App() {
const [data, setData] = useState([]);
const [showGraph, setShowGraph] = useState(false);
return (
<div className="App">
<Search setData={setData} setShowGraph={setShowGraph}/>
<Api data={data} showGraph={showGraph}/>
the Api.js file. It contains the chart, which I wanna show on search Item click.
const Api = ({ data: records }, { showGraph }) => {return (
<>
{showGraph? (
<div>
<div className="show">
<Areachart width="50%" height={400}>
...
</Areachart>
</div>
</div>
) : (
false
)}
</>
);
};
And The Search.js file. It's the search file where the searched data shown, and by clicking them, the state should change.
const Search = ({ setData }, { setShowGraph }) => {
searchTerm.length !== 0 && (
<div className="dataResult">
{searchTerm.map((record, key) => {
return (
<h3
key={key}
onClick={() => {
setShowGraph(true)
}}
>
{record.symbol}
</h3>
);}
So, the thing is, I want to implement that, Initially there will be no chart displayed. But whenever I click on the searced Item, the chart should display. what have I done wrong?
You are destructuring the props wrong.
The
const Api = ({ data: records }, { showGraph }) => {return (
should be
const Api = ({ data: records, showGraph }) => {return (
And the
const Search = ({ setData }, { setShowGraph }) => {
should be
const Search = ({ setData, setShowGraph }) => {
Related
I've a problem with my react app.The navbar worked correctly before the ItemDetailContainer, and i want to show in the DOM an specific product. When adding the ItemDetailContainer to app.js, the page doesn't show anything (including the navbar) . Here's my code:
ItemDetailContainer.jsx:
const {products} = require('../utils/data');
const ItemDetailContainer = () => {
const [arrayList, SetArrayList] = useState({});
useEffect(() => {
customFetch(2000, products[0])
.then(result => SetArrayList(result))
.catch(err => console.log(err))
}, [])
return (
<div>
<ItemDetail products={arrayList}/>
</div>
);
}
here's my array in data.js that i want to access:
const products = [
{
id: 1,
image: "https://baltimore.com.ar/img/articulos/4188.png",
title: "Vino Abras Malbec 750cc",
price: 2.500,
stock: 8,
initial: 1
}
ItemDetail.jsx:
const ItemDetail = ({product}) => {
return(
<>
{product.image}
{product.title}
</>
)
}
CustomFetch.js:
let is_ok = true
let customFetch = (time, array) => {
return new Promise((resolve, reject) => {
setTimeout(() => {
if (is_ok) {
resolve(array)
} else {
reject("error")
}
}, time)
})
}
and my app.js:
function App() {
return (
<div>
<Navbar/>
<ItemDetailContainer/>
</div>
);
}
I've done all the imports and exports, but I dont' know what's the problem.
Since you are passing a product prop in <ItemDetail product={arrayList}/> within ItemDetailContainer make sure you destructure your props correctly in ItemDetail component,like so :
const ItemDetail = ({product}) => {
return(
<>
{product.image}
{product.title}
</>
)
}
Which is basically like this :
const ItemDetail = (props) => {
return (
<>
{props.product.image}
{props.product.title}
</>
);
};
check it out in code sandbox
I'm passing an array of objects to a child as props, and I wanted the child component to re-render when the aray changes, but it doesn't:
parent:
export default function App() {
const [items, setItems] = useState([]);
const buttonCallback = () => {
setItems([...items, { text: "new item" }]);
};
return (
<div className="App">
<h1>Items list should update when I add item</h1>
<button onClick={buttonCallback}>Add Item</button>
<Items />
</div>
);
}
child:
const Items = ({ itemlist = [] }) => {
useEffect(() => {
console.log("Items changed!"); // This gets called, so the props is changing
}, [itemlist]);
return (
<div className="items-column">
{itemlist?.length
? itemlist.map((item, i) => <Item key={i} text={item.text + i} />)
: "No items"}
<br />
{`Itemlist size: ${itemlist.length}`}
</div>
);
};
I found this question with the same issue, but it's for class components, so the solution doesn't apply to my case.
Sandbox demo
<Items propsname={data} />
const buttonCallback = () => {
setItems([...items, { text: "new item" }]);
};
but you should put it as:
const buttonCallback = () => {
setItems([...items, { text: "new item", id: Date.now() }]);
};
Because is not recommended to use index as a key for react children. Instead, you can use the actual date with that function. That is the key for React to know the children has changed.
itemlist.map((item) => <Item key={item.id} text={item.text} />)
Try below:
you are adding array as a dependency, to recognise change in variable, you should do deep copy or any other thing which will tell that useEffect obj is really different.
`
const Items = ({ itemlist = [] }) => {
const [someState,someState]=useState(itemlist);
useEffect(() => {
someState(itemlist)
}, [JSON.stringify(itemlist)]);
return (
<div className="items-column">
{someState?.length
? someState.map((item, i) => <Item key={i} text={item.text
+ i}
/>)
: "No items"}
<br />
{`Itemlist size: ${someState.length}`}
</div>
);
};
I have two components.
First is called: BucketTabs
Second is called:BucketForms
To have a better idea. Below pictures illustrate it.
When I switching tab, different form will be showed below.
Q: Whenever I switch from one tab to other tab, and then switch back, the content in the previous BucketForms will be gone. But, gone data are supposed to be stored into a state of that BucketForms.
In fact, I've memo the BucketForms already, so I've expected the content(data) would not be gone.
What's the problem and how could I prevent the data to be gone after switching tab.
My BucketTabs:
import { BucketForms } from '~components/BucketForms/BuckForms'
export const BucketTabs: React.FC = () => {
const items = useMemo<ContentTabsItem[]>((): ContentTabsItem[] => {
return [
{
title: '1',
renderContent: () => <BucketForms key="1" bucketCategory="1" />,
},
{
title: '2',
renderContent: () => <BucketForms key="2" bucketCategory="2" />,
},
]
}, [])
return (
<div className="row">
<div className="col">
<ContentTabs items={tabs} kind="tabs" />
</div>
</div>
)
}
BucketForms
function PropsAreEqual(prev, next) {
const result = prev.bucketCategory === next.bucketCategory;
return result;
}
interface IData {
portfolioValue?: number
}
export const BucketForms: React.FC<IProps> = React.memo(props => {
const { bucketCategory } = props
const [data, setData] = useState<IData>({
})
const view = ({
portfolioValue,
}: IData) => {
return (
<>
<div className="row portfolio">
<FormNumericInput
key="input-portfolio-value"
name="portfolioValue"
required
value={portfolioValue}
/>
</div>
</>
)
}
return (
<Form
onChange={e => {
setData({ ...data, ...e, })
}}
>
{view(data)}
</Form>
)
}, PropsAreEqual)
I am working with 2 APIs. The data of the Api's is almost identical except for the name of the theatre and the price to see a movie. I'm currently mapping through and sending the props to my MovieComponent, but the issue is that the prop names are the same and I need to get another value for the {Price} for one cinema. Currently it is providing data from filmWorld and I need to display the cinemaWorld {Price} also. I'm wondering how to separate these two props so I can get the data on cinemaWorld Price.
I can access this data from App.js by logging cinemaWorld.Movies[0].Price (Although I'll need to map through to get different Prices depending on the movie).
App.js:
function App() {
const [filmWorld, setFilmWorld] = useState([]);
const [cinemaWorld, setCinemaWorld] = useState([]);
useEffect(() => {
const theaters = ["cinema", "film"];
theaters.forEach((theater) => {
async function getTheater() {
const data = await api.getMoviesData(theater);
if (data.Provider === "Film World") {
setFilmWorld(data);
} else {
setCinemaWorld(data);
}
}
getTheater();
});
}, []);
const movies = useMemo(() => {
if (!filmWorld.Provider) return [];
return filmWorld.Movies.map((movie, index) => ({
...movie,
cinemaWorldPrice:
cinemaWorld.Provider && cinemaWorld.Movies[index]?.Price,
}));
}, [filmWorld, cinemaWorld]);
return (
<Container>
<Header>
<AppName>
<MovieImage src={movieicon1} />
Prince's Theatre
</AppName>
</Header>
<MovieListContainer>
{movies.map((movie, index) => (
<MovieComponent key={index} movie={movie} />
))}
</MovieListContainer>
</Container>
);
}
and my movieComponent:
const MovieComponent = (props) => {
const { Poster, Price, Title } = props.movie;
return (
<MovieContainer>
<CoverImage src={Poster} alt="" />
<MovieName>{Title}</MovieName>
<InfoColumn>
<MovieInfo>FilmWorld: ${Price}</MovieInfo>
<MovieInfo>CinemaWorld: ${Price}</MovieInfo>
</InfoColumn>
</MovieContainer>
);
};
I'm trying to filter through some posts based on their category if a button is clicked. For example I have a button that when clicked the only posts that show up are related to software projects.
I have set up a function called searchHandler that I've passed through to my SidebarOptions component, which has the onclick event. But when I pass it through nothing happens.
Here is the code in the (parent) Home Component where the searchHandler is:
function Home() {
const [posts, setPosts] = useState([]);
const [filteredPosts, setFilteredPosts] = useState(null);
const searchHandler = (event) => {
const { value } = event.target;
setFilteredPosts(
value
? posts.filter(
(post) =>
post.question.question.includes(value)
)
: null
);
};
useEffect(() => {
db.collection("questions")
.orderBy("timestamp", "desc")
.onSnapshot((snapshot) =>
setPosts(
snapshot.docs.map((doc) => ({
id: doc.id,
question: doc.data(),
}))
)
);
}, []);
return (
<div className="home">
<div></div>
<Header searchHandler={searchHandler} />
<div className="home__content">
<Sidebar searchHandler={searchHandler} />
<Feed posts={filteredPosts || posts} />
<Widget />
</div>
</div>
);
}
Here is the (child) Sidebar component that receives it:
import React from "react";
import "../Style/Sidebar.css";
import SidebarOptions from "./SidebarOptions";
function Sidebar({ searchHandler }) {
return (
<div className="sidebar">
<SidebarOptions searchHandler={searchHandler} />
</div>
);
}
export default Sidebar;
And here is the (grandchild)SidebarOptions that the function is finally sent to:
function SidebarOptions({ searchHandler }) {
return (
<div className="sidebarOptions">
<div className="sidebarOption" onChange={() => searchHandler}>
<img
src="https://c.pxhere.com/photos/7b/1a/code_coding_computer_developer_developing_development_macbook_notebook-913320.jpg!d"
srcset="https://c.pxhere.com/photos/7b/1a/code_coding_computer_developer_developing_development_macbook_notebook-913320.jpg!d"
alt="Software Projects"
/>
<p>Software Projects</p>
</div>
);
};
I think you need to revisit your SideBarOptions component. I wonder if the onChange handler makes sense on a div. I think it should be input rather than a div if you want your user to type. Also, you need to call your handler with the value that is typed, here you are not calling the handler (notice the missing () after searchHandler in your code for SideBarOptions). Also, it will be better to add something like a debounce so that the filter is not triggered for every character that a user types. It should ideally be triggered once a user stops typing, debounce is precisely that.
Putting some code snippet below based on my guess about how it might work.
const SideBarOptions = ({ searchHandler }) => {
const [filterText, setFilterText] = useState("");
const handleFilter = () => {
searchHandler(filterText);
}
return (
<div className="sidebarOptions">
<input name="filterText" value={filterText} onChange={(e) => setFilterText(e.target.value)} />
<div className="sidebarOption" onChange={() => searchHandler}>
<img src="https://c.pxhere.com/photos/7b/1a/code_coding_computer_developer_developing_development_macbook_notebook-913320.jpg!d" srcset="https://c.pxhere.com/photos/7b/1a/code_coding_computer_developer_developing_development_macbook_notebook-913320.jpg!d"
alt="Software Projects"
/>
<p>Software Projects</p>
<button onClick={handleFilter}>Filter</button>
</div>
</div>
);
}
So I was able to solve this by making a new function called categoryfilter in the Home component that went through the options and looked for the category of the posts in the database:
const categoryFilter = (category = "All") => {
const filtered =
category === "All"
? posts
: posts.filter(({ question }) => question.option === category);
setFilteredPosts(filtered);
};
I then passed that code as a prop to the sidebarOptions div after cleaning up the code a bit and used it to filter the posts based on the category name:
function SidebarOptions({ categoryFilter }) {
const categories = [
//Add all projects
{
name: "All",
imgUrl: "",
},
{
name: "Software Project",
imgUrl:
"https://c.pxhere.com/photos/7b/1a/code_coding_computer_developer_developing_development_macbook_notebook-913320.jpg!d",
},
{
name: "Engineering Project",
imgUrl:
"https://c.pxhere.com/photos/a7/72/gears_cogs_machine_machinery_mechanical_printing_press_gears_and_cogs_technology-818429.jpg!d",
},
];
return (
<div className="sidebarOptions">
{categories.map((category) => (
<div
className="sidebarOption"
onClick={() => categoryFilter(category.name)}
>
{category.imgUrl && (
<img
src={category.imgUrl}
srcSet={category.imgUrl}
alt={category.name}
/>
)}
<p>{category.name}</p>
</div>
))}
<div className="sidebarOption">
<Add />
<p>Suggest Project Space</p>
</div>
</div>
);
}
export default SidebarOptions;