using context with an api call for the root level component - javascript

i am new to use the react context, but i think how the redux works like, but not getting the proper way.
so i have a component where i will get data from api once i get the data i need to show a loader once data gets load the component
but how i set the value to context and use it.
see the below snippet
class App extends Component{
componentDidMount(){
this._fetchData()
}
_fetchData = async () => {
const response = await fetch("http://www.example.com", {method: "GET"})
const data = await response.json()
// how can i use context and set the value
}
// how should i use a provider to the router so basically i need to get the data every where in the route
render(){
return(
<Router>
{ Object.keys(context.data).length > 0 ?
<Layout>
<Switch>
<Route path="/sample" exact component={SampleHomePage} />
<Route path="/sample/profile" component={SampleProfile} />
<Route path="/sample/about" component={SampleAbout}/>
</Switch>
</Layout>
: <p>Data Loading </p>}
</Router>
)
}
}
// profile.js
import React from 'react';
const profile = () => {
//how to get the contex value here
}
export default profile

Look at this codesandbox and check how i worked with context.
https://codesandbox.io/s/2p0koqr81p
The key is that you need an extra component that will work as the context.
In this case i've just saved the data from the fetch call into the context.
So you have to provide the updated data to the context so then you can consume it in any component that you want.
I agree with #Joru that maybe is unnessesary to use context in your use case, but im trying to answer the question.

Related

What are the standard patterns for using the react componentDidMount() method to fetch api data for multiple routes?

I am trying to piece together how I should be using the componentDidMount() method.
I am using react, and react router
Backend is firebase cloud functions and firestore
I have three routes including the parent component (/) - then /someLocation, and /someItem. In addition, it is possible to go to say /someLocation/someItem. Initially, I am fetching and setting the state with a get request in componentDidMount(). The issue is that when I refresh the child component, I lose state.
From my research I gather that I have two options or a combination of the two (excluding hash router)
Store data in local storage (probably ok for fetching a single record)
Make a get request every time the page is refreshed which makes sense for the parent component
What is the most common design pattern for routing in react for these cases, and what requests should be contained in the parent's componentDidMount method?
Thanks! Any direction, tips, tricks or guidance is greatly appreciated.
render() {
const { jobs } = this.state
const getJobPost = (props) => {
let slug = props.match.params.slug
let job = jobs.find((job) => job.slug === slug)
return <JobPost {...props} job={job} isLoading={this.state.isLoading} />
}
return (
<Switch>
<Route
exact
path="/"
render={(routeProps) => (
<MainJobBoard
countryFilter={this.countryFilter}
handleFilter={this.handleFilter}
filterValue={this.state.filterValue}
isLoading={this.state.isLoading}
jobs={jobs}
{...routeProps}
/>
)}
/>
<Route exact path="/:location/:slug" render={getJobPost} />
</Switch>
)
}

How to call setState with componentDidMount without causing extra calls to render?

I have a problem with componentDidMount: everytime that i use setState in componentDidMount it calls render several times in child components, and I don't know why... look:
componentDidMount() {
const firstName = localStorage.getItem('nameLoggedUser');
const lastName = localStorage.getItem('lastNameLoggedUser');
const fullName = `${firstName} ${lastName}`.toLowerCase();
const loggedUserIs = localStorage.getItem("user-role");
const emailLoggedUser = localStorage.getItem('emailLoggedUser');
if (loggedUserIs === 'full') {
axios.get(`/api/menuDomain`)
.then(res => this.setState({
domain: res.data
}))
.catch(err => console.log(err))
}
}
It gives this:
But, if I use ComponentDidUpdate, it gives:
That is correct, but the AJAX call is not happening...
I want to make the AJAX call and not have it render several times... But I don't know how... Could someone help me? Please???
And I am using react-router too:
return (
<Router>
<Switch>
<Route path="/login" component={Login} />
<Route exact path="/" render={() =>
<Overview
{...myProps}
/>
}
/>
<Route path="/list" render={() =>
<Graphic
{...myProps}
/>
} />
</Switch>
</Router>
);
}
}
First, never wrap your routes within a stateful component. Instead, the routes should be a stateless function that just returns the same JSX route elements. For the example above, you should use a HOC that wraps your protected routes and is only concerned with authentication: https://stackoverflow.com/a/53197429/7376526
Second, you shouldn't be storing the logged in user in plain text within local storage. Instead, it and possibly the password should be stored within a JWT token (or within some sort of hashed plus salted token) or perhaps within a secured cookie and then decoded and compared against server-side. How you're currently setting it to localStorage is incredibly insecure and anyone can simply set a user's first and last name and their "user-role" and gain access to their account.
Third, each container should handle and fetch relevant data and pass it to a relevant component for display. For performance, you should compartmentalize your retrieved data to relevant data for that particular component. Since you have multiple routes, fetching everything at once is wasteful. For example, fetch Overview data inside of a FetchOverview container's componentDidMount, then pass it down to a DisplayOverview child component for display. Read this for more information on containers and components: https://medium.com/#dan_abramov/smart-and-dumb-components-7ca2f9a7c7d0

React Reuse Fetch JSON data throughout components

Hi there I am newbie to React and learning about reusable functions throughout my project.
I would like to fetch my JSON data but not have to call it everytime in my component.
App.js
import React, { Component } from 'react';
import { Route, NavLink, HashRouter } from "react-router-dom";
import logo from '../assets/logo.png';
import './app.css';
import About from "../about/about";
import Services from "../services/services";
import Testimonials from "../testimonials/testimonials";
import Contact from "../contact/contact";
class App extends Component {
constructor(props) {
super(props);
this.state = {
items : []
};
}
componentDidMount(){
this.getItems();
}
getItems(){
fetch('./data/data_arr.js')
.then(results => results.json())
.then(results => this.setState({'items': results}));
}
render() {
return (
<HashRouter>
<div className="container">
<div className="header">
<div className="App-logo"><NavLink exact to="/"><img src={logo} alt="logo" /></NavLink></div>
<nav className="Nav-Desktop">
{this.state.items.map((item, index) => (
<div key={index}>
{
item.links.map((link, i) => (
<NavLink key={i} exact to={link.url}>{link.text}</NavLink>
))}
</div>
))}
{
this.state.items.map((item, index) => {
return <div key={index}></i><strong> {item.mainContact.phone}</strong></div>
})
}
</nav>
</div>
<main className="content">
<Route exact path="/" component={About}/>
<Route path="/services" component={Services}/>
<Route path="/testimonials" component={Testimonials}/>
<Route path="/contact" component={Contact}/>
</main>
{this.state.items.map((item, index) => {
return <footer key={index}>© Copyright {item.title} {(new Date().getFullYear())}</footer>
})
}
</div>
</HashRouter>
);
}
}
export default App;
I am successfully mapping my data and displaying it, but I have other files that include this snippet
constructor(props) {
super(props);
this.state = {
items : []
};
}
componentDidMount(){
this.getItems();
}
getItems(){
fetch('./data/data_arr.js')
.then(results => results.json())
.then(results => this.setState({'items': results}));
}
I have tried exporting the getItems() like so in a helper.js file and importing the file import { getItems } from '../helpers/helpers'; however the code did not work properly and got stuck at Unhandled Rejection (TypeError): Cannot read property 'setState' of undefined
export function getItems() {
fetch('./data/data_arr.js')
.then(results => results.json())
.then(results => this.setState({'items': results}));
}
If anyone can give me pointers as to the error / right way to go about this that would be helpful. Cheers
Two things you need to know when you want to reuse the data instead of calling fetch again and again
Do fetch call in top most component i.e., parent component and pass down data to all the children, children to children components but, do remember this will be hectic for you. This approach is good when you are building small application which will be like max to max 50 components. But when your application grows big this is not a recommended way of reusing the data across components.
Use Redux state management library for data reusability across components. This acts like a centralised store for your application. This is used mostly in every React app these days. With Redux you can make an action call in parent component and that action will actually fetch the data and pass it to the reducer. So now reducer will set the data in Redux store. Now the data is accessible in every component by getting the data from Redux store using state.get. So you can call redux action like this.props.getItems(); wherever you need the data in the component and the component mapStateToProps will make that data available to your component as props
How to get the data from Redux store?
Define a function mapStateToProps in component and get the data from Redux store using state.get and return it in the function. Pass the mapStateToProps to connect method. You will be connecting your component to Redux HOC component called connect. This connect method accepts actions and Redux state and make them available to the component as props.
Regarding your issue
Unhandled Rejection (TypeError): Cannot read property 'setState' of undefined
The reason you get this issue because this isn’t available inside the exported getItems function.
What you need to do to fix the issue is pass this to getItems function as a parameter
Note: Here this is a current context
import { getItems } from '../helpers/helpers';
componentDidMount(){
getItems(this);
}
helpers.js:
the below function is treated as a normal JavaScript function. This function no wr belongs the component to bind it in component constructor. But in order to play with the state of the component you need to pass the component context this. But this is old way, now a days we all use Redux to avoid these old concepts
export function getItems(this) {
fetch('./data/data_arr.js')
.then(results => results.json())
.then(results => this.setState({'items': results}));
}
This way you can reuse this function and do the setState.
But do remember these solutions will make complex when your application grows big in future. So I would argue you to go with Redux state management library from now to avoid hectic migration in future :)
Excuse me if there are any typo errors because I am answering from my mobile.
You need to pass the context to your helper function. Considering you're still importing the function from your helper, on your componentDidMount, it can be like:
componentDidMount(){
getItems.call(this);
}
So good job with the fetching, there are a lot of paths you can take with sharing data across components, the two main ones you can look into are:
Parent state holds the data, passes it through props
A Store (Mobx, Redux) holds the data, and you inject it into your components as needed
I'm not going to give you the tutorial on how to do them all but I'm assuming the first option will be best for you right now.
If your components are using the same data, they should theoretically have the same parent component in a structure similar to this:
Page Component (Overarching component, lays out the page structure)
Header
Component 1
Component 2
Footer
So you'll do this call on the mount of Page component and pass this.state.items as a prop to the other components that need it... Stores have a similar process they just save the double handling of props :)
Since you're accessing/modifying state in your getItems() method, you should bind it in your constructor like this:
constructor() {
super(props);
this.state = {
items : []
};
this.getItems = this.getItems.bind(this);
}
or you can simply declare it with arrow function so you don't have to bind it:
getItems = () => {
// some codes here
}
Another thing, the only ones who can access/modify the state of a compoenent is its own methods. If you want to use it outside of itself, import those components inside your component as children then pass your getItems() method as prop.
render() {
return (
<HashRouter>
<div className="container">
// some other codes
<ChildComponent getItems={this.getItems} />
</div>
</HashRouter>
);
}

React onClick event handler not working using Next.js

I'm making a Reddit clone and I'm using Next.js so its server-side rendered. I started without using Next.js and when I learned about it, I immediately switched to it.
I've created a custom _app.js so the header and sidebar exist on every page, and for it to act as the topmost component to hold application state. The later isn't quite working out.
Here's .project/src/pages/_app.js:
import App, { Container } from 'next/app';
// Components
import Header from '../components/Header/Header';
const MainLayout = props => (
<React.Fragment>
<Header
isSidebarOpen={props.isSidebarOpen}
sidebarToggle={props.sidebarToggle}
/>
{props.children}
</React.Fragment>
);
export default class MyApp extends App {
static async getInitialProps({ Component, router, ctx }) {
let pageProps = {};
if (Component.getInitialProps) {
pageProps = await Component.getInitialProps(ctx);
}
return { pageProps };
}
state = {
isSidebarOpen: true
};
sidebarToggle = () => {
this.setState({ isSidebarOpen: !this.state.isSidebarOpen });
};
render() {
const { Component, pageProps } = this.props;
return (
<Container>
<MainLayout
isSidebarOpen={this.state.isSidebarOpen}
sidebarToggle={this.sidebarToggle}
>
<Component {...pageProps} />
</MainLayout>
</Container>
);
}
}
The issue I'm having is, isSidebarOpen and sidebarToggle are being passed to exactly where I need them – they show up in the console – but the onClick handler doesn't activate and if I change isSidebarOpen, it doesn't take effect until I restart the server. I used this approach before using Next.js and it worked as expected.
How can I achieve what I'm trying to do? I've read the docs, searched Google and Stack Overflow. I even checked issues on their repo without luck. I suspect it to be something to do with a custom _app.js, as the props are passed down correctly.
One possibility might be to define your sidebarToggle function within the component you are using.(Maybe because calling it inside Component the code might be running in _app.js instead of your component this is a big maybe! but worth a try)
Here _app.js is a wrapper around your application and I don't think it is suited to be used at the topmost component to hold state. Better to make a simple react root Component do that!

React Router, Redux and async API call

When the user clicks in a link, React Router will display a component. The data this component will show came from an endpoint, so I'm wondering what's the best practice.
I've created a function called fetchData which uses fetch to perform a GET in an endpoint and then returns a promise. Once this promise is resolved, I would like to dispatch a Redux action to update the state.
I managed to do this with redux-thunk, but I would like to implement this without adding more libraries.
I'm trying to follow the 'container/presentational' idea and I'm using stateless functional components in React.
In a general overview, this is what I'm doing:
index.js
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root')
)
app.js
const App = () => (
<div>
<BrowserRouter>
<div>
<Header />
<Switch>
<Route exact path="/" component={Main} />
<Route exact path="/data-list" component={DataListContainer} />
</Switch>
</div>
</BrowserRouter>
</div>
)
dataListContainer.js
const mapStateToProps = (state) => {
return { data: state.data }
}
const mapDispatchToProps = (dispatch) => {
return { }
}
const DataListContainer = connect(
mapStateToProps,
mapDispatchToProps
)(DataList)
dataList.js
const DataList = (props) => {
const rows = props.data.map(data => {
return <Data data={data} />
})
return (
<div>
{rows}
</div>
)
}
And the Data component simply displays the data. I'm wondering where I should add the call to the function fetchData and where I should solve the returned promise. I imagine I will need to dispatch an action after the promise is resolved, but not sure where is the best place to do this.
Other question is: I would like to fetch the data only once, I mean, only when the user clicks the /data-list link. If it comes back to main page and then goes again to data-list, I would like to not call the endpoint again. Is there any call once feature hidden in React Route implementation?
Change dataListContainer.js to be a stateful React component, this is ok because it's the container! Don't take the code for exact, it's to just to give an idea.
import { fetchData, DataList, store } '......'
class DataListContainer extends React.Component {
componentDidMount() {
if (!this.props.data) {
store.dispatch(fetchData())
}
}
render() {
return (<DataList data={this.props.data}/>);
}
}
const mapStateToProps = (state) => {
return { data: state.data }
}
export default connect(
mapStateToProps
)(DataListContainer)
I don't think this is possible in the routes and views. When you switch pages with React Router, the component unmounts. All the data of the component is also cleared. (Believe me, this is something you want. Otherwise you might get some serieus memory issues that let your browser crash). Also, your view is only responsible for displaying stuff and should not do things with the data it receives.
Take a look for some implementation in your store. For example store the received data from the API in the store object. The next time someone is calling the fetchData function in the store, serve the stored data instead of make a new request. The store never unmounts so it can hold data in memory. Keep in mind that the user will only receive new data if the reload the entire page. So a 'hard refresh' but might be useful..
Another thing you can do is asking yourself why you don't want to call that endpoint multiple times? Is the data set to large to receive? In that case use pagination and serve it in pieces.

Categories

Resources