React event in parent component and run a function from child component - javascript

I have a list of items in my Main component. It has a child component called Posts. The Posts component contains a fetch function that retrieves new data when the user scrolls down through the items. The Main component also includes a search bar with a filter and search button. I click this button in the Main component, but what I want is that this button runs fetch function inside Posts component.
The only way I find is to put the fetch function inside parent Main and pass it down to Posts. However, this way my Main will soon be full of unmanageable code? What is common practice for such tasks and how could I upgrade my code?
Main:
//...
function Main({posts, authToken, authTokenType, username, userId}) {
//...
return (
<div>
<Posts
authToken={authToken}
authTokenType={authTokenType}
username={username}
/>
Posts:
//...
function Posts({authToken,authTokenType, username}) {
//...
const [posts, setPosts] = useState([]);
const fetchData = () => {
fetch(BASE_URL + `post/all?page=${page}`)
//...
return(
<div>
<InfiniteScroll
dataLength={posts.length}
next={fetchData}
hasMore={hasMore}
loader={<h4>Loading...</h4>}
>
<Row>
{posts.map((post, index) => (
<Col lg={3}>
<Post
post = {post}
authToken={authToken}
authTokenType={authTokenType}
username={username}
/> </Col>
))}</Row>
</InfiniteScroll>

I dont know if this is the best solution but you could use the useImperativeHandle hook and forwardRef:
const Child = forwardRef((props, ref) => {
useImperativeHandle(ref, () => ({
log() {
console.log("child function");
}
}));
return <h1>Child</h1>;
});
const Parent = () => {
const ref = useRef();
return (
<div>
<Child ref={ref} />
<button onClick={() => ref.current.log()}>Click</button>
</div>
);
};

In your Post component, you can add:
useEffect(() => {
doFetch(); // Run the fetch function
}, [props.triggerFetch]);
And i your Main, add this attribute:
<Post
triggerFetch={doTriggerFetch}
post = {post}
authToken={authToken}
authTokenType={authTokenType}
username={username}
/>
When you update doTriggerFetch in the Main component, it will execute the doFetch function in the Post component.

Related

Problem displaying an item according to the url - React

I have a question. I have a component that when entering /category/:categoryId is rendered doing a fecth to "api url + categoryId". My problem is that if I change from one category to another the page only changes if the useEffect is executed infinitely which generates problems to the view as seen below. How can I make it run once and when I change from /category/1 to /category/2 the useEffect is executed correctly?
const Categories = () => {
let [producto, productos] = useState([]);
const { categoryId } = useParams();
useEffect(() => {
fetch('https://fakestoreapi.com/products/category/' + categoryId)
.then(res=>res.json())
.then(data=>productos(data))
},[]);
console.log(producto)
return(
<div className="container">
{producto.map((p) => (
<Producto
title={p.title}
price={p.price}
description={p.description}
image={p.image}
key={p.id}
id={p.id}
/>
))}
</div>
)
}
export default Categories;
My router file:
<Route path="/category/:categoryId" component={Categories} />
This is the problem that is generated, there comes a time when a fetch is made to a previously requested category and then the new requested category is executed.
See my problem in video
You can simply add categoryId to useEffect array argument. Function inside the useEffect is called, only when categoryId changes
useEffect(() => {
fetch('https://fakestoreapi.com/products/category/' + categoryId)
.then(res=>res.json())
.then(data=>productos(data))
},[categoryId]);
you can not edit producto directly, you should use productos :
const Categories = () => {
let [producto, productos] = useState([]);
const { categoryId } = useParams();
useEffect(() => {
fetch('https://fakestoreapi.com/products/category/' + categoryId)
.then(res=>res.json())
.then(data=>productos(data))
},[]);
console.log(producto)
return(
<div className="container">
{producto && producto.map((p) => (
<Producto
title={p.title}
price={p.price}
description={p.description}
image={p.image}
key={p.id}
id={p.id}
/>
))}
</div>
)
}
export default Categories;

Pass functional component from child to parent in React

Is it possible to pass a functional component from a child component to a parent component? I'm trying to do a dynamic modal that is displayed inside the parent but that the children can populate through a function from a provider, for example:
setModal(() => (
<div>content</div>)
)
And the parent receives this component:
const [modal, setModal] = useState(false)
const [modalContent, setModalContent] = useState<FunctionComponent>()
...
<Provider value={{
setModal: (content: FunctionComponent) => {
setModalContent(content); // This updates the state to hold a function component and to re-render
setModal(true); // This updates a state flag to show the overlay in which the modal is rendered
},
}}>
...
</Provider>
The content of the modal should be dynamic. I was trying to use the state of the component to hold the functional component but I don't think if that's possible or if it's a good practice.
If I understand your question correctly, you're still looking to pass a function from the parent to each child but each child should be able to change the state of a modal component that the parent also has ownership over.
For the above scenario this is something you can do:
const Provider = ({ children, updateModal }) => {
// With this, every child has the ability to call updateModal
return React.Children(children).map(child => cloneElement(child, { updateModal }));
};
const ModalComponent = ({ open, children }) => {
if (!open) return null;
return (
<dialog>
{children}
</dialog>
);
};
const ParentComponent = () => {
const [modal, setModal] = useState(false);
const [modalContent, setModalContent] = useState(null);
const updateModal = (content) => {
setModalContent(content);
setModal(true);
};
return (
<>
<Provider updateModal={updateModal}>
{...insert children here}
</Provider>
<ModalComponent open={modal}>
{modalContent}
</ModalComponent>
</>
);
};

How to pass an API response from a child component to another nested component?

I have a component MockTablein which I'm fetching an API response and storing into a variable data. I want to use the data in another component UsecasePane. May I know the correct way to pass data to UsecasePane?
MockTable is the child component of MainContent.
AddMock is the child component of MainContent.
=> MainContent.js
const MainContent = () => {
return (
<div >
<CustomerTable />
<AddMock />
<MockTable />
</div>
);
};
export default MainContent;
Now TabContent is the child of AddMock
=> AddMock.js
const AddMock = () => {
return (
<div className="row">
<Container className="mockbody">
<Header as="h3">Hierarchy</Header>
<TabContent />
</Container>
</div>
);
};
export default AddMock;
UsecasePane is nested inside the TabContent.
// TabContent.js
import React from "react";
import { Tab } from "semantic-ui-react";
import UsecasePane from "./UsecasePane";
const panes = [
{
menuItem: "Usecase",
render: () => <Tab.Pane attached={false}>{<UsecasePane />}</Tab.Pane>,
}
];
const TabContent = () => (
<Tab menu={{ secondary: true, pointing: true }} panes={panes} />
);
export default TabContent;
=> MockTable.js
import React, { useState, useEffect } from "react";
import "../assets/css/material-dashboard.css";
import axios from "axios";
import { Icon, Dimmer, Loader, Segment } from "semantic-ui-react";
let config = require("../appConfiguration");
const MockTable = () => {
const [data, setData] = useState([]);
const [loading, setLoading] = useState(false);
const fetchData = async () => {
const response = await axios
.get(config.App_URL.getAllTest, {
params: {
customHostName: "suite",
type: "routes",
},
})
.catch((error) => {
console.error(`Error in fetching the data ${error}`);
});
let list = [response.data];
setData(list);
setLoading(true);
};
useEffect(() => {
fetchData();
}, []);
return (
// rest of the code
)
};
export default MockTable;
Now I need to pass this data to UsecasePane which is nested in another component TabContent
There are multiple ways to pass data to child components.
Using props
//In Grandparent
<Parent dataToParent={someData}/>
//In Parent
<Child dataToChild={props.dataToParent}/>
//Using in Child
render(){
{props.dataToChild}
}
Render props(skipping a hierarchy)
https://reactjs.org/docs/render-props.html
//In Grandparent component
<Parent dataToParent={dataFromParent => (
<Child>{dataFromParent}</Child>
)}/>
Storing data in the central state
Context API: https://reactjs.org/docs/context.html
Third-Party Libs:
i.e. Redux https://redux.js.org/
Edit:
Thank you for updating the question. By looking at the hierarchy it seems that you are dealing with sibling components. i.e. MainContent -> MockTable is where you are fetching the data and MainContent -> AddMock -> TabContent -> UsecasePane where you want to display/pass the data. For both of these the immediate common parent is MainContent.
If you don't want to use ContextAPI or any other third-party lib. I would suggest to lift up your states to MainContent and pass a callback function to MockTable and pass state to MainContent -> AddMock -> TabContent -> UsecasePane
Consider the following example:
//MainContent.js
const MainContent = () => {
...
const [data, setData] = useState([]);
...
return (
<div >
<CustomerTable />
<AddMock setData={setData} /> {/*Passing callback function*/}
<MockTable data={data} /> {/*Passing state*/}
</div>
);
};
export default MainContent;
//AddMock.js
...
const fetchData = async () => {
// all data fetching logic remains the same.
let list = [response.data];
props.setData(list); //Calling callback function to set the state defined in MainContent.js
};
useEffect(() => {
fetchData();
}, []);
...
//AddMock.js
const AddMock = ({data}) => {
return (
<div className="row">
<Container className="mockbody">
<Header as="h3">Hierarchy</Header>
<TabContent data={data} /> {/*Passing data props*/}
</Container>
</div>
);
};
export default AddMock;
//TabContent.js
const TabContent = ({data}) => {
const panes = [
{
menuItem: "Usecase",
render: () => <Tab.Pane attached={false}>{<UsecasePane data={data} />}</Tab.Pane>,
}
];
return(
<Tab menu={{ secondary: true, pointing: true }} panes={panes} />
)
}
export default TabContent;
//
Here you can see that to pass data, some prop drilling is happening, though it's not an anti pattern but would recommend to avoid as much as possible spcially in the functional components as they don't have support for shouldComponentUpdate() or React.PureComponent otherwise you can use React.memo HOC Read more.
Of Course React.Context or Third party libs i.e. Redux(Though it'll be overkilled for small applications) are few alternatives to all of these.
Edited
Try to fetch your data in MainContent to centralize your datas and then dispatch. Use a callback (addData) to pass the new data to your parent
MainContent
const MainContent = () => {
// get datas
function addData(newData){
setData([...data, newData])
}
return (
<div>
<CustomerTable />
<AddMock addData={addData} />
<MockTable data={data} />
</div>
);
};
AddMock
const AddMock = (props) => {
// use props.addData(newData)
};
You can create a function which will return your array then you can pass an argument to that function which will be consumed in usecasepane component.
const panes = (arg) => {
// do something with arg...
return [
{
menuItem: "Usecase",
render: () => <Tab.Pane attached={false}>{<UsecasePane arg={arg} />}</Tab.Pane>,
}
];
}
const TabContent = () => (
<Tab menu={{ secondary: true, pointing: true }} panes={ panes() } />
);

Can i set state in parent from child using useEffect hook in react

I have a set of buttons in a child component where when clicked set a corresponding state value true or false. I have a useEffect hook in this child component also with dependencies on all these state values so if a button is clicked, this hook then calls setFilter which is passed down as a prop from the parent...
const Filter = ({ setFilter }) => {
const [cycling, setCycling] = useState(true);
const [diy, setDiy] = useState(true);
useEffect(() => {
setFilter({
cycling: cycling,
diy: diy
});
}, [cycling, diy]);
return (
<Fragment>
<Row>
<Col>
<Button block onClick={() => setCycling(!cycling)}>cycling</Button>
</Col>
<Col>
<Button block onClick={() => setdIY(!DIY)}>DIY</Button>
</Col>
</Row>
</Fragment>
);
};
In the parent component I display a list of items. I have two effects in the parent, one which does an initial load of items and then one which fires whenever the filter is changed. I have removed most of the code for brevity but I think the ussue I am having boils down to the fact that on render of my ItemDashboard the filter is being called twice. How can I stop this happening or is there another way I should be looking at this.
const ItemDashboard = () => {
const [filter, setFilter] = useState(null);
useEffect(() => {
console.log('on mount');
}, []);
useEffect(() => {
console.log('filter');
}, [filter]);
return (
<Container>..
<Filter setFilter={setFilter} />
</Container>
);
}
I'm guessing, you're looking for the way to lift state up to common parent.
In order to do that, you may bind event handlers of child components (passed as props) to desired callbacks within their common parent.
The following live-demo demonstrates the concept:
const { render } = ReactDOM,
{ useState } = React
const hobbies = ['cycling', 'DIY', 'hiking']
const ChildList = ({list}) => (
<ul>
{list.map((li,key) => <li {...{key}}>{li}</li>)}
</ul>
)
const ChildFilter = ({onFilter, visibleLabels}) => (
<div>
{
hobbies.map((hobby,key) => (
<label {...{key}}>{hobby}
<input
type="checkbox"
value={hobby}
checked={visibleLabels.includes(hobby)}
onChange={({target:{value,checked}}) => onFilter(value, checked)}
/>
</label>))
}
</div>
)
const Parent = () => {
const [visibleHobbies, setVisibleHobbies] = useState(hobbies),
onChangeVisibility = (hobby,visible) => {
!visible ?
setVisibleHobbies(visibleHobbies.filter(h => h != hobby)) :
setVisibleHobbies([...visibleHobbies, hobby])
}
return (
<div>
<ChildList list={visibleHobbies} />
<ChildFilter onFilter={onChangeVisibility} visibleLabels={visibleHobbies} />
</div>
)
}
render (
<Parent />,
document.getElementById('root')
)
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.12.0/umd/react.production.min.js"></script><script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.11.0/umd/react-dom.production.min.js"></script><div id="root"></div>
Yes, you can, useEffect in child component which depends on the state is also how you typically implement a component which is controlled & uncontrolled:
const NOOP = () => {};
// Filter
const Child = ({ onChange = NOOP }) => {
const [counter, setCounter] = useState(0);
useEffect(() => {
onChange(counter);
}, [counter, onChange]);
const onClick = () => setCounter(c => c + 1);
return (
<div>
<div>{counter}</div>
<button onClick={onClick}>Increase</button>
</div>
);
};
// ItemDashboard
const Parent = () => {
const [value, setState] = useState(null);
useEffect(() => {
console.log(value);
}, [value]);
return <Child onChange={setState} />;
};

How to give child component control over react hook in root component

I have an application that adds GitHub users to a list. When I put input in the form, a user is returned and added to the list. I want the user to be added to the list only if I click on the user when it shows up after the resource request. Specifically, what I want is to have a click event in the child component trigger the root component’s triggering of the hook, to add the new element to the list.
Root component,
const App = () => {
const [cards, setCards] = useState([])
const addNewCard = cardInfo => {
console.log("addNewCard called ...")
setCards([cardInfo, ...cards])
}
return (
<div className="App">
<Form onSubmit={addNewCard}/>
<CardsList cards={cards} />
</div>
)
}
export default App;
Form component,
const Form = props => {
const [username, setUsername] = useState('');
const chooseUser = (event) => {
setUsername(event.target.value)
}
const handleSubmit = event => {
event.persist();
console.log("FETCHING ...")
fetch(`http://localhost:3666/api/users/${username}`, {
})
.then(checkStatus)
.then(data => data.json())
.then(resp => {
console.log("RESULT: ", resp)
props.onSubmit(resp)
setUsername('')
})
.catch(err => console.log(err))
}
const checkStatus = response => {
console.log(response.status)
const status = response.status
if (status >= 200 && status <= 399) return response
else console.log("No results ...")
}
return (
<form onSubmit={handleSubmit}>
<input
type="text"
placeholder="Gitbub username"
value={username}
required
onChange={chooseUser}
onKeyUp={debounce(handleSubmit, 1000)}
/>
<button type="submit">Add card</button>
</form>
)
}
export default Form;
List component,
const CardsList = props => {
return (
<div>
{props.cards.map(card => (
<Card key={card.html_url} {... card}
/>
))}
</div>
)
}
export default CardsList
and the Card Component,
const Card = props => {
const [selected, selectCard] = useState(false)
return (
<div style={{margin: '1em'}}>
<img alt="avatar" src={props.avatar_url} style={{width: '70px'}} />
<div>
<div style={{fontWeight: 'bold'}}><a href={props.html_url}>{props.name}</a></div>
<div>{props.blog}</div>
</div>
</div>
)
}
export default Card
Right now, my Form component has all the control. How can I give control over the addNewCard method in App to the Card child component?
Thanks a million in advance.
One solution might be to create a removeCard method in App which is fired if the click event you want controlling addNewCard doesn't happen.
// App.js
...
const removeCard = username => {
console.log("Tried to remove card ....", username)
setCards([...cards.filter(card => card.name != username)])
}
Then you pass both removeCard and addNewCard to CardList.
// App.js
...
<CardsList remove={removeCard} cards={cards} add={addNewCard}/>
Go ahead and pass those methods on to Card in CardsList. You will also want some prop on card assigned to a boolean, like, "selected".
// CardsList.js
return (
<div>
{props.cards.map(card => (
<Card key={card.html_url} {... card}
remove={handleClick}
add={props.add}
selected={false}
/>
))}
</div>
Set up your hook and click event in the child Card component,
// Card.js
...
const [selected, selectCard] = useState(false)
...
and configure your events to trigger the hook and use the state.
// Card.js
...
return (
<div style={{margin: '1em', opacity: selected ? '1' : '0.5'}}
onMouseLeave={() => selected ? null : props.remove(props.name)}
onClick={() => selectCard(true)}
>
...
This doesn't really shift control of addNewCard from Form to Card, but it ultimately forces the UI to follow the state of the Card component.

Categories