How to handle Ajax in pure function components in react? - javascript

I'm trying to write all my components as pure functions, but now I run into trouble. I have a component that looks somewhat like below. Trouble is, the result of the ajax request causes a rerender, which causes another ajax request, and there you have your infinite loop. How to properly handle this?
const PageProductFunnel = function (props) {
const agent = ajaxagent;
var list = [];
agent.get(`https://mylist.com/${productSKU}`).then((res) => {
list = res.data;
});
return (
<div>
<div className="cell">
<article className="article">
<h1 className="article-title">{product.name}</h1>
<FunnelStep {...props} list={list} />
<ButtonAddToCart product={product} />
</article>
</div>
</div>
);
};

There are few approaches you can take:
fetch async data globaly, not inside render of the component
Do not use pure function for this component, and hook async into life cycle methods

You need to use stateful components for this.
class PageProductFunnel extends React.Component {
state = {
"list": []
}
componentWillMount () {
agent.get(`https://mylist.com/${productSKU}`).then((res) => {
this.setState({list:res.data})
});
}
render () {
return (
<div>
<div className="cell">
<article className="article">
<h1 className="article-title">{product.name}</h1>
<FunnelStep {...props} list={this.state.list} />
<ButtonAddToCart product={product} />
</article>
</div>
</div>
);
}
};

EDIT Please read the comments. After some more considerations, I've decided to implement Davorin Ruševljans first suggestion. My own solution works but his is better.
Thanks for the suggestions but they really don't solve the problem. All my code is pure so far, and I really want to stick with it. Making the call on a global level doesn't change anything in my case as the response would still cause a render.
I changed the code so that the list is part of the state (I'm using redux). Only if the list is empty, I perform the ajax call. Whenever I know I expect new data, I clear the old list before the page renders again.
if (props.list.length === 0) {
agent.get(`https://mylist.com/${productSKU}`).then((res) => {
props.setList(res.data);
});
}

The better life cycle hook to do an ajax call is componentDidMount(). If you are so specific to use pure component you can use in pure Component as well.
class Sample extends PureComponent{
//react implement ShouldComponentUpdate for us which does shallow comparison
componentDidMount(){
//api call here
}
}
But You can't build a react app using only pureComponent, Because if there is a case where you wan't to stop the updation life cycle by checking the state, then you can't do that using PureComponent Because Pure component does only shallow checking for us and it checks for all the states in the component.
We should balance stateful, PureComponent, Stateless among the project.

Related

Using state in other component

Hi I am learning and new to react and I want to know how to pass state from one component to other,
I have one component as
const [paneCount, setPaneCount]= useState(1);
const openPane = (paneKey) => {
setOpeningPaneKeys(oldState => {
if (!oldState.includes(paneKey)) {
return [...oldState, paneKey]
}
return oldState
})
setPaneCount(paneCount+1);
console.log(paneCount);
setFocusingPaneKey(paneKey)
}
where I want to use paneCount in App.js file
function App(props) {
const [inactive, setInactive] = useState(false);
return (
<div className="App">
<Header />
<Navbar
onCollapse={(inactive) => {
setInactive(!inactive);
}}
/>
<div class="landing-card">
<div>
<h4 class="headingStyle">Recorder Box</h4>
<h4>Count:{props.paneCount}</h4>
<img src="landing.jpg" alt="Forest" width="775" height="500"></img>
</div>
</div>
How to pass paneCount to App.js
You can't pass data upwards, only downwards. The reason for that is how the application and data flow is built. Luckily there are 3 ways to get it done.
1) Initialize in App.js
If you want to use state value in App.js but want to work with it somewhere else:
export default function App() {
const [myState, setMyState] = useState();
return <Component state={ myState } setState={ setMyState } />
}
This way you can keep all your state in 1 place and use it everywhere, but it also means that you have to pass component by component to do so.
2) Context
This improves the previous option, because you no longer need to pass data around. Instead you can keep it in a provider and use it throughout your application.
There are multiple ways to define a provider, so I will just link you the docs for that one.
Note: Provider definitions does not differ in any way. They are not practical or impractical, simply one's preference over another!
3) Redux
This is arguably the BEST option for state management (eventhough I don't like it..). I haven't used it yet and don't want to either, because the previous option does the same with less effort IMO.
Here's the docs for that one.
Instead of passing paneCount to App.js which I suppose is the parent component. You can create paneCount in App.js and then pass setPaneCount to the openPane component like this.
<openPane setPaneCount = {setPanecount} paneCount = {setPaneCount}/>
If you are not calling openPane in the App.js for some reason then go to the parent component that is calling both App and openPane and create and pass the setPaneCount and paneCount from there. If you making something complex instead of drilling the value down like this you might want to look at some stateManagement tools like Redux or Context API.

How and where to pass JSON data as a prop when creating components

I am getting some Club information from a JSON I want to use in my React component 'Club'. I created a component ClubList in which all Club components with their corresponding name should be created but I don't know where I should make the HTTP request and where to save it, so I can use it in the return statement.
I tried saving all titles in an array but I stopped at the point I had to pass the titles to each Club element. I just started working with ReactJS so I am a basically complete beginner in ReactJS (Not in JS though).
This is the ClubList class
class ClubList extends React.Component {
render() {
return (
<div className="Clublist">
<Club title="Club1" />
<Club title="Club2" />
...
...
...
</div>
)
}
}
And that is the Club class
class Club extends React.Component {
clubProp = {...}
render() {
return (
<div className="Club">
<div className="image-container">
<img src={this.clubProp.imageSrc} width="300px" height="300px"/>
</div>
<h2>{this.clubProp.name}</h2>
<div className="Business-information">
<div className="Business-address">
<p>{this.clubProp.address}</p>
<p>{this.clubProp.city}</p>
<p>{this.clubProp.zipCode}</p>
</div>
<div className="Business-reviews">
<h3>{this.clubProp.category}</h3>
<h3 className="rating">{this.clubProp.rating}</h3>
<p>90 reviews</p>
</div>
</div>
</div>
)
}
}
I will use an API to get the Club-names but I don't know how I can organize the variables to be accessible in the right places since I don't quite grasp how the scopes in React work. I already have the code for getting the JSON ready, just need to know where to put it and how to pass the values in
Your basic syntax would look like the following. Your component will maintain clubs using component state. In the componentDidMount lifecycle function, you can make your api call and then store the results in your component's state. Any time you call setState, your component will re-render.
class ClubList extends React.Component {
state = {
clubs: []
};
componentDidMount = async () => {
const clubs = await this.fetchClubs();
this.setState({ clubs });
}
render() {
const { clubs } = this.state;
return (
<div className="Clublist">
{clubs.map(club => (
<Club title={club.title} />
)}
</div>
);
}
}
Eventually you can pull out state management from all of your components and use something like redux or MobX and let your components focus solely on rendering html.
Where you should make the API request?
Ideally, we use redux-sagas or redux-thunk as middleware while making API requests. However, since, you're just getting started, you could make the API call in the componentDidMount lifecycle method of your ClubList component.
Now, I am assuming that you receive an array of Clubs. You could map over this array and render each Club component.
Where you should store this data?
Common practice is to use a state-management library like redux with react. It helps scale and maintain your app better. However, you could also use the state of the ClubList component to store the data of your API call.
I hope this was helpful.
From the docs, you make API calls in the componentDidMount life-cycle method. I'd recommend looking at the docs for examples:
https://reactjs.org/docs/faq-ajax.html
The docs use the browser's fetch method to make the request, but I'd personally recommend using axios. https://www.npmjs.com/package/axios, since it's a bit more straight forward.
React uses state, which can be passed down through the component tree. As you are using class components, a typical way of getting your JSON data into the app's state would be to fetch the data in the componentDidMount() lifecyle method of your top level component, and run this.setState({clubProp: result.data}) in the fetch/axios callback. You can pass it to children where they are available as props.
I would argue that Redux is overkill - and that it would be better to defer learning it until you have a state management problem. The new hooks implementation and context API will also change best practices for state management. The guy who created Redux says "Flux libraries are like glasses: you’ll know when you need them."
// Here is the simplified example:
class ClubList extends React.Component {
constructor(props) {
super(props);
this.state = {
ClubDetails: [],
};
}
componentDidMount() {
fetch("api link put here in quotes")
.then(results => {
return results.json();
}).then(data => {
let ClubDetails = data;
this.setState({ ClubDetails: ClubDetails });
})
}
render() {
const ClubDetails = this.state.ClubDetails;
const listItems = ClubDetails.map((clubProp,index) =>
<Club key={index} clubProp={clubProp}/>
);
return (
{listItems}
);
}
}
class Club extends React.Component {
render() {
return (
<div className="Club">
<div className="image-container">
<img src={this.clubProp.imageSrc} width="300px" height="300px"/>
</div>........
My recommendation is using fetch middleware in redux to control all datas in props(You can find in redux github examples), besides the responsive data, you can also monitor fetching status across components.

Storing methods needed in all components

I have a universal app I'm developing for learning purposes. I'm managing the state of my app with Redux, so all my data will be available there. But I want to create some methods that I'm going to use in all my components. The problem is: where should I store this methods?
Adding them to a parent component and passing the methods as props doesn't seem very useful, because this is one of the things that Redux tries to solve. And I'm pretty sure that Redux is not a place for storing methods.
I know I can create a class in a file somewhere, export it, add some methods to it, and when I want to use one method in a component I can call this file, create an instance of the class and call the needed method; but this doesn't look very react to me…
Is there a right way to create methods available for all components?
I've had some success sharing functions between components using an approach similar to the following. I'm not sure this approach will solve your specific use case with regards to cookies, however.
These functions can be stored anywhere and imported wherever required. They accept a component as their first argument, then return a function that operates on the component passed in.
Indicative, untested code follows.
// An event handler than can be shared between multiple components
const handleChange = component => event => component.setState({ value: event.target.value });
class ComponentOne extends PureComponent {
state = {};
render() {
return (
<div>
{this.state.value}
<input onChange={handleChange(this)} />
</div>
);
}
}
class ComponentTwo extends PureComponent {
state = {};
render() {
return (
<div>
{this.state.value}
<input onChange={handleChange(this)} />
</div>
);
}
}

Integrating and rendering react-swipe-cards

I am very noob with reactJs, in fact I just finished this course and am struggling with some concepts here.
I am willing to create an app for people to express their preferences with regards of subjects for a newsletter, and have grabbed a very comprehensive list of topics (2k+) and wanna make some fun way to select them, so I think that something along the lines of Tinder swipeable cards would be a perfect fit, so I am trying to implement this react module functionality into my App.
But it is not showing up anything.
I just created a Repo, in which I had a few tries with no luck.
Basically, the example provided in the module documentation says that it should start by
const data = ['Alexandre', 'Thomas', 'Lucien', 'Raphael', 'Donatello', 'Michelangelo', 'Leonardo']
const Wrapper = () => {
return (
<Cards onEnd={console.log("action('end')")} className='master-root'>
{data.map(item =>
<Card
onSwipeLeft={console.log("action('swipe left')")}
onSwipeRight={console.log("action('swipe right')")}>
<h2>{item}</h2>
</Card>
)}
</Cards>
)
}
But I am completely lost with it, I supposed that it should provide me with a React Component <Something />, but instead it generate something in the lines of a function, that returns a div, which looks a lot with a component, but I have no idea about how integrate into this example.
Note: In the repo graph, I noticed that there is another developer that made some adjustments to make it compatible with react 16.xx.beta, I'v tried it also, no lucky also.
I am almost sure, that there are some concepts I am missing here, so, any reference is more than welcome, also.
What you are looking for is a functional stateless component, the below code
const Wrapper = () => {
return (
<Cards onEnd={console.log("action('end')")} className='master-root'>
{data.map(item =>
<Card
key={item}
onSwipeLeft={() => {console.log("action('swipe left')")}}
onSwipeRight={() => {console.log("action('swipe right')")}}>
<h2>{item}</h2>
</Card>
)}
</Cards>
)
}
is a functional component.
According to documentation
Functional and Class Components
The simplest way to define a component is to write a JavaScript
function:
function Welcome(props) {
return <h1>Hello, {props.name}</h1>;
}
This function is a valid React component because it accepts a single
“props” (which stands for properties) object argument with data and
returns a React element. We call such components “functional” because
they are literally JavaScript functions.
The way to render a function component is just like you would render a normal React component
<Wrapper {...props} /> // {...props} is a spread operator syntax to just pass all the props down to wrapper you can pass selected props too
Also react-swipe-card doesn't provide you Wrapper functional component, it provides you components like Cards and Card which you used to render the card view in the Wrapper Component
import Cards, { Card } from 'react-swipe-card'
Now in your case it would look like
export default class MyCards extends Component {
render() {
return <Wrapper />;
}
}
However since you don't have a state and also you are not using lifecycle functions you could simple write the above MyCards Component as
export const MyCards= () => {
return <Wrapper />;
}
I however assume that you would eventually be writing some of the logic there and hence keep it a stateful React component. I have also include the logic whereby you would handle the state change on left or write swipe.
Check a working DEMO
P.S. I a recommendation to go through the React docs thoroughly as they have explained the concepts really well
If I understand you question as suppose. It look you have some small mistake. I download the repo and run you test on React 15.4.2
Your Card component call:
<Card
onSwipeLeft={console.log("action('swipe left')")}
onSwipeRight={console.log("action('swipe right')")}>
<h2>{item}</h2>
</Card>
My Card component call:
<Card
key={item}
onSwipeLeft={()=>{console.log("action('swipe left')")}}
onSwipeRight={() => {console.log("action('swipe right')")}}>
<h2>{item}</h2>
</Card>
We need to create scope for events handler that is why one of the solution is a arrow function. They aren’t just syntactic sugar around anonymous functions though. An arrow function does not have its own context and will instead use the same this as the context in which it was defined. Here is more detail handle-events-in-react-with-arrow-functions
Also on the MyCards you are returning something like (your code)
export default class MyCards extends Component {
constructor(props) {
super(props);
this.state = {
}
}
render() {
return Wrapper;
// return (
// <div>
// <p>Something</p>
// {Wrapper();}
// </div>
// );
}
}
But you should return a component and the way is return it have to be
export default class MyCards extends Component {
constructor(props) {
super(props);
this.state = {
}
}
render() {
return <Wrapper/>
}
}
Suggestion: If you are not going to have any state in the MyCards component you can make it a functional Component
The current top answer is correct and great about the concept of the functional component. You are actually creating a High Order Component.
However I feel your repo can actually be fixed by just making this change:
render() {
return Wrapper();
}
See that I actually executed the Wrapper function in order to get the Component as a result, otherwise you are rendering a function and not a Component.
You can refactor your code to actually extend a React.Component, and I actually recommend this as HOC are better used for another type of objective, like decorators.
See here, the only thing I changed is that: https://codesandbox.io/s/xp6pzpyoq

Is interation between two components rendered using React.render() possible?

I have a list of employees in a page. Each employee item has a check box adjacent to it. This was developed using react js. Now for every 5seconds,I need to check and show a number of employees available/unavailable status on the page based up on the check box status of the employees.
<div id="container">
<!-- List of employees will be placed here by react.js -->
</div>
<div id="status-bar">
Can you please tell me how both divs can be interacted with each other as I am rendering them separately:
React.render(<ListItems />, document.getElementById('container'));
React.render(<StatusComponent />, document.getElementById('status-bar'));
I don't believe that the two components can interact with each other without the use of a parent component. I would suggest making a component that wraps the two, something like this (written in ES6 and not at all tested... just whipped up for example):
class EmployeeModule extends React.component {
_countEmployees() {
// Magic to get employee count here..
var count = getEmployeeCount();
this.setState({numEmployees: count});
}
componentDidMount() {
// Count employees every 5 seconds
this.interval = window.setInterval(() => this._countEmployees(), 5000));
}
componentWillUnmount() {
window.clearInterval(this.interval);
}
render() {
return (
<div className="employee-module-wrapper">
<ListItems />
<StatusComponent numEmployess="{this.state.numEmployees}" />
</div>
)
}
}
Effectively what you need to do is have EmployeeModule handle the interaction between both components. You can get the information you need out of the ListItems module, and set it on the state of the parent module. Calling setState inside _countEmployees will trigger a call to the render function, which will set the property numEmployees on the StatusComponent, thus updating your Status module.
Hope this helps
I think the best and easier way to do it - create store. You can use reduxJS to create store easy. You can also include one block in other and provide your data via this.props.

Categories

Resources