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

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

Related

React router v6 access route params and pass as props

In react router v6, how can I pass route params to a component without the need to use useParams() in the component?
This is what I want to do:
<Route
path='/'
element={ProfileComponent username={'thedefault'}
/>
<Route
exact
path='/u/:username/'
render={(props) =>
<ProfileComponent username={props.match.params.username}/>
}
/>
I don't want to put useParams() in the component because this tightly couples it to the URL. For example, what if I wanted to render another ProfileComponent elsewhere, with a different username to that in the URL. It seems to violate best practice for unit testing unless I can do it like my example.
I don't want to put useParams() in the component because this
tightly couples it to the URL. For example, what if I wanted to render
another ProfileComponent elsewhere, with a different username to that
in the URL. It seems to violate best practice for unit testing unless
I can do it like my example.
Any route using a username route match param would still be accessible via the useParams hook, but I think I understand what you are after. If I understand your question correctly you are asking how to map a route match param to a component prop in a generic way.
For this you can simply use a wrapper component to "sip" the route match param and pass it along to your component on any specific prop.
const ProfileComponentWrapper = () => {
const { username } = useParams();
return <ProfileComponent username={username} />;
};
...
<Route
path='/u/:username/'
element={<ProfileComponentWrapper />}
/>
In the docs, it is clearly specified that it is not possible
Normally in React you'd pass this as a prop: , but you don't control that information because it comes from the
URL.
https://reactrouter.com/docs/en/v6/getting-started/tutorial#reading-url-params
So, you have to use useParams in the component

ContextProvider doesn't mount when page refreshes

Please see the full code on codesandbox.
I have two Route components for my countries api app-
<>
<Header/>
<Router>
<Switch>
<CountriesDataProvider>
<Route path='/' exact component={HomeRoute} />
<Route path='/detail/:countryName' component={DetailsRoute} />
</CountriesDataProvider>
</Switch>
</Router>
</>
The DetailsRoute for each country shows up with all information perfectly when I click on a country on the HomeRoute. But when a direct http request is made or the DetailsRoute is refreshed, I see this error-
/src/comps/Details/DetailsRoute.jsx
Cannot read property 'borders' of undefined
It occurs on the line-
const promises = country[0].borders.map(fetchNeighborName);
country[0] is extracted from countriesData which seems to be undefined-
const {countriesData} = React.useContext(CountriesDataContext);
This is not an issue with BrowserRouter, because the DetailsRoute component does render but with missing info.
I don't kow what logical error is causing it to not work when a direct url request is sent to the DetailsRoute component? Please let me know the fix!
The problem is that when you reload the details page the response in CountriesDataContext hasn't come back yet, so country is empty. An easy way to solve the problem (and is generally good practice) is to add a check to make sure the data is there:
async function resolveBorders() {
// Add a condition to make sure you have the data you expect.
if (country.length) {
const promises = country[0].borders.map(fetchNeighborName);
setBorderCountries(await Promise.all(promises));
}
}
Be sure to also add country to the effect's dependency array so that the effect runs again when the response comes back:
React.useEffect(() => {
async function resolveBorders() {
...
}
resolveBorders();
}, [borderCountries, country]); // <-- Add `country` here

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>
)
}

POST request from react child component in express

I am using the MERN stack (With react-router and redux) for my program.
In my application, I have a <Navbar /> component as well as a <SearchBar> component.
I used create-react-app to handle the react side of things, and as i'm sure you all know, everything is ultimately contained within App.js
Within App.js, Navbar is outside of the react router switch statement like so.
<Provider store={store}>
<Router>
<div>
<NavBarContainer />
<Switch>
<Route exact path="/" component={Homepage} />
<Route exact path="/ProfilePage" component={ProfilePageContainer} />
<Route exact path="/SearchPage" component={SearchPageContainer} />
<Route exact path="/LoginPage" component={LoginContainer} />
</Switch>
</div>
</Router>
</Provider>
My problem is that SearchBar is a child component of Navbar like so.
class Navbar extends Component {
render(){
return (
***navbar stuff
{this.props.loggedIn && <SearchBar />}
***navbar stuff
)
}
}
And when I try to make a POST request from <SearchBar /> like this:
addSearch = (event) => {
if(this.props.loggedIn){
fetch('/api/SearchPage', {
headers : {
"Accept" : "application/json",
"Content-Type" : "application/json"
},
method : 'POST',
body : JSON.stringify({
username : this.props.username,
search : this.search.value
}).then(function(value){
return value.json()})
.then(function(data){
console.log("SearchData", data)
}).bind(this)
})
}
}
Where addSearch() is called within the SearchBar with onClick(this.addSearch).
When I do this, the POST request comes from whatever page is rendered under my Navbar!
My page says:
Cannot POST /api/[Pagename}.js
Whatever page is currently rendered under Navbar with react switch will replace [Pagename]
How can I make <SearchBar /> create a POST request within itself?
Or, if that is not possible, how can I contain that post request within the <Navbar> component?
Something I'm considering, is that it has something to do with the fact hat I'm using a body parser, and the current body of the page happens to be whatever page is loaded. However, I seem to define the body: within the POST request so this doesn't make much sense.
For now, i'm simply going to put all of the code and logic of the <SearchBar> component within the <Navbar> and the search page, but I suspect that there is a much better way to do this (while leaving <SearchBar> within its own component).
Ideally, I'd like to send a POST request just from <SearchBar>.
I know I'm probably missing something common here.
Here is the endpoint in the express file (It is just set up for testing)
```
app.post('/api/SearchPage', function(req, res done){
if(err) done(err);
done(null, res.json({"Test" : "testobj"}))
}'
```
From the given code, it seems like its the url in fetch() that is the issue here. Since you are only stating '/app/SearchPage', then it is assumed to be part of the React app (which it is not, since its should hit your API endpoint).
You should try to insert the url for the endpoint. Is is sometimes running on a different port than the React application (depending on your setup). Endpoint could be something like http://localhost:3050/api/SearchPage, which should be set in the URL parameter of your fetch() method.
I took a break today but the answer seems obvious at the moment.
When I do fetch(), all react does is append the URL (in this case /API/Page) to localhost/[pageimon]
Because all of my routing is handled client side by react router, my URL is whatever page I'm on. (I did not do any routing with express).
Since the navbar is out side of all switching (on purpose), whatever is contained in there will not make a successful post request, as all fetch calls will simply read the URL.
So I have to figure out a way to either bypass this (possibly a fetch parameter, or a react middleware module).

React router: component not updating on url search param change

I have a React application that uses URL search params. Depending on the value of the search parameter v, the component is supposed to show different content.
A possible URL might look like this:
http://localhost:3000/hello?v=12345
Once the v query parameter changes in the URL, I would like my component to update.
http://localhost:3000/hello?v=6789
I'm using using react-router in my App component to display the different components depending on the route. The App component is wrapped into a BrowserRouter component.
render() {
return (
<Switch>
<Route path="/hello" component={Hello}></Route>
<Route path="/" component={Home}></Route>
</Switch>
);
}
If a user clicks on something and the URL search param v changes, I would like the component to display different content. Currently, the URL search param does change, but the component doesn't render again. Any ideas?
As #forJ correctly pointed out, the main idea is to make the component re render once the URL parameters change. I achieved it like so:
render() {
return (
<Switch>
<Route path="/hello" render={() => (<Hello key={this.props.location.key}/>)}></Route>
<Route path="/" component={Home}></Route>
</Switch>
);
}
this.props.location.key changes every time the URL changes (also if the query params change). So if you pass it as props to the component, then the component re renders on URL param changes, even though the base URL (the URL without the URL params) hasn't changed.
I also had to use the withRouter higher order component to make it work.
export default withRouter(connect(mapStateToProps, mapDispatchToProps)(Hello));
You have not provided enough information so I cannot be 100% sure where you are going wrong but I can see 1 problem right now in your code. First, you have to let the router know that you will be using parameters. So for example below code
render() {
return (
<Switch>
<Route path="/hello/:yourParam" component={Hello}></Route> // you need the colon here
<Route path="/" component={Home}></Route>
</Switch>
);
}
The above will tell the route that parameter called yourParam will be accessible from the URL
Then it has to be accessed in your component and put into render function somehow (use it to retrieve data from database to render data, just render params directly, etc).
For example below
render(){
<div>
{this.props.match.params.yourParam}
</div>
}
This way, your component will re-render everytime there is param change.
EDIT
Since you want to use query instead of parameter, you would have to retrieve it by going. this.props.match.query.v. You would still have to make sure that variable is rendered depending on query change
If you use useEffect hook make sure to have [props.match.params] instead of empty array []
useEffect(() => {
(async () => {
// Fetch your data
})();
}, [props.match.params]) // <-- By this it will get called when URL changes
of course for class components this.props.match.params

Categories

Resources