In class based Component:
componentDidMount() {
axios.get('https://jsonplaceholder.typicode.com/posts').then((res) => {
this.setState({
posts: res.data.slice(0, 10)
});
console.log(posts);
})
}
I tried this:
const [posts, setPosts] = useState([]);
useEffect(() => {
axios.get('https://jsonplaceholder.typicode.com/posts').then((res) => {
setPosts(res.data.slice(0, 10));
console.log(posts);
})
});
It creates an infinite loop. If I pass a []/{} as the second argument[1][2], then it blocks further call. But it also prevents the array from updating.
[1] Infinite loop in useEffect
[2] How to call loading function with React useEffect only once
Giving an empty array as second argument to useEffect to indicate that you only want the effect to run once after the initial render is the way to go. The reason why console.log(posts); is showing you an empty array is because the posts variable is still referring to the initial array, and setPosts is also asynchronous, but it will still work as you want if used in the rendering.
Example
const { useState, useEffect } = React;
function App() {
const [posts, setPosts] = useState([]);
useEffect(() => {
setTimeout(() => {
setPosts([{ id: 0, content: "foo" }, { id: 1, content: "bar" }]);
console.log(posts);
}, 1000);
}, []);
return (
<div>{posts.map(post => <div key={post.id}>{post.content}</div>)}</div>
);
}
ReactDOM.render(<App />, document.getElementById("root"));
<script src="https://unpkg.com/react#16.7.0-alpha.0/umd/react.production.min.js"></script>
<script src="https://unpkg.com/react-dom#16.7.0-alpha.0/umd/react-dom.production.min.js"></script>
<div id="root"></div>
You can check how axios-hooks is implemented.
It's super simple and uses the config object (or url) you provide to decide when to make a request, and when not to, as explained in Tholle's answer.
In addition to allowing you to use the awesome axios in your stateless functional components, it also supports server side rendering, which - it turns out - hooks make very straightforward to implement.
Disclaimer: I'm the author of that package.
I've written a Custom Hooks for Axios.js.
Here's an example:
import React, { useState } from 'react';
import useAxios from '#use-hooks/axios';
export default function App() {
const [gender, setGender] = useState('');
const {
response,
loading,
error,
query,
} = useAxios({
url: `https://randomuser.me/api/${gender === 'unknow' ? 'unknow' : ''}`,
method: 'GET',
options: {
params: { gender },
},
trigger: gender,
filter: () => !!gender,
});
const { data } = response || {};
const options = [
{ gender: 'female', title: 'Female' },
{ gender: 'male', title: 'Male' },
{ gender: 'unknow', title: 'Unknow' },
];
if (loading) return 'loading...';
return (
<div>
<h2>DEMO of <span style={{ color: '#F44336' }}>#use-hooks/axios</span></h2>
{options.map(item => (
<div key={item.gender}>
<input
type="radio"
id={item.gender}
value={item.gender}
checked={gender === item.gender}
onChange={e => setGender(e.target.value)}
/>
{item.title}
</div>
))}
<button type="button" onClick={query}>Refresh</button>
<div>
{error ? error.message || 'error' : (
<textarea cols="100" rows="30" defaultValue={JSON.stringify(data || {}, '', 2)} />
)}
</div>
</div>
);
}
You can see the result online.
Related
I am creating an e-commerce app where I am trying to apply filters e.g. filter by gender and/or brands. The filters only work individually and if I try to click both, the other filter cancels.
Please help! thank you in advance.
I am creating an e-commerce app where I am trying to apply filters e.g. filter by gender and/or brands. The filters only work individually and if I try to click both, the other filter cancels.
Please help! thank you in advance.
Sorry about the duplicate of question, Stackoverflow kept stopping me from posting because I have too much codes instead of description.
import React, { useState, useEffect } from 'react';
import './Clothing.css'
import data from '../../data/data2.json';
const Clothing = () => {
const [items, setItems] = useState([]);
const [color, setColor] = useState(null);
const types = [
{ id: 11, value: 'All' },
{ id: 22, value: 'Cap' },
{ id: 33, value: 'Sweatshirt' }
]
const genders = [
{ id: 55, value: 'Men' },
{ id: 66, value: 'Women' }
]
const brands = [
{ value: 'Graver' },
{ value: 'LMC' }
]
useEffect(() => {
setItems(data);
}, [])
const handleGender = (e) => {
const filteredItems = data.filter((d) => {
return d.gender === e.target.value
})
setItems(filteredItems)
}
const handleBrand = (e) => {
const filteredItems = data.filter((d) => {
return d.brand === e.target.value
})
setItems(filteredItems)
}
return (
<div className="bodyDiv">
<div className="leftMenu">
<div className='leftList'>
<h3>Gender</h3>
{
genders.map((g) =>
<button
id={g.id}
value={g.value}
onClick={(e) => {
handleGender(e);
handleColor(e);
}}
>{g.value}</button>
)
}
</div>
<div className='leftList'>
<h3>Brand</h3>
{
brands.map((b) =>
<button
id={b.id}
value={b.value}
onClick={(e) => {
handleBrand(e);
}}
>{b.value}</button>
)
}
</div>
<div className='itemSection'>
<div className='itemCount'>
Total {items.length}
</div>
<div className="itemLists">
{items.length === 0
? "No items found"
:
items.map((item) =>
<div className="itemCard" key={item.id}>
<img src={item.img} alt='clothes' />
<div className="itemText">
<h4>{item.brand}</h4>
<h4>{item.gender}</h4>
</div>
</div>
)}
</div>
</div>
</div>
)
}
export default Clothing
I have solved this problem. Message me if you need help.
So i have made a fetch request and stored the data into a const. I am having trouble accessing the data to display it.
const [itemDetails, setItemDetails] = useState([]);
This is the structure of data stored in itemDetails and i cant figure out how to display it.
item:
customerCount: 0
itemCatName: "Furniture - Photos/Paintings/Prints"
itemCost: 21.96
itemDescription: "Removable and repositionable with no sticky residue. Perfect for nurseries, apartments, dorm rooms, and businesses. Wall decal stickers are mess-free, no paint, no glue/paste, no residue."
itemID: 6
itemImage: "/Images/I/51vJQpTLP7L._AC_.jpg"
itemName: "Golf Cart Seniors Isolated Peel and Stick Wall Decals "
unitsSold: 0
customerList: Array(197)
[0 … 197]
0: {customerName: 'Joel Amess', email: 'immamess#gmail.com', primaryPh: '491592807', secondaryPh: null, addressLine1: '51 Prince Street', …}
1: {customerName: 'Jack Schlink', email: 'jackschlink#outlook.com', primaryPh: '0420194047', secondaryPh: null, addressLine1: '34 Round Drive', …}
2: {customerName: 'Jett Freeleagus', email: 'jettfree#gmail.com', primaryPh: '0489847578', secondaryPh: null, addressLine1: '48 Magnolia Drive', …}
3: {customerName: 'Brock Mills', email: 'bmills#gmail.com', primaryPh: '0488806371', secondaryPh: null, addressLine1: '13 Taylor Street', …}
4: {customerName: 'Koby Wiedermann', email: 'koby#gmail.com', primaryPh: '0420029236', secondaryPh: null, addressLine1: '36 Reynolds Road', …}
5: {customerName: 'Eve Coane', email: 'eevee#gmail.com', primaryPh: '0489973669', secondaryPh: null, addressLine1: '22 SWestern Australianston Street', …}
Any Help would be greatly appreciated
EDIT: This is my code im trying to run but it gives me itemDetails.map is not a function. the itemDetails has all the right data i just cant display it...
function ItemDetails() {
//variable to store data passes from handlePageChange
const location = useLocation();
const itemID = location.state.itemID;
const year = location.state.year;
const [itemDetails, setItemDetails] = useState([]);
console.log(itemDetails);
useEffect(() => {
const fetchItemDetails = async () => {
if(year != null && itemID != null){
try {
const response = await fetch(`http://localhost:56384/api/RecallDetail?itemID=${itemID}&year=${year}`);
setItemDetails(await response.json());
} catch (error) {
console.log('Failed to fetch from Amazon DB', error);
}
} else {
try {
const response = await fetch(`http://localhost:56384/api/RecallDetail?itemID=${itemID}`);
setItemDetails(await response.json());
} catch (error) {
console.log('Failed to fetch from Amazon DB', error);
}
}
};
fetchItemDetails();
}, []);
return (
<div>
<div>
<h1>Recall Information</h1>
<p>Product:</p>
<p>Unit Cost:</p>
<p>Description</p>
</div>
<div>
<table>
<thead>
<tr>
<th>Customer Name</th>
<th>Contact Details</th>
<th>Addrress Line</th>
<th>Total Cost</th>
<th>Units Sold</th>
</tr>
</thead>
<tbody>
{itemDetails.map(item => {
return (
<tr >
<td>{ item.customerList.customerName }</td>
<td>{ item.customerList.email }</td>
<td>{ item.customerList.addressLine1 }</td>
<td>${ item.customerList.totalCost }</td>
<td>{ item.customerList.unitsSold}</td>
</tr>
);
})}
</tbody>
</table>
</div>
<NavLink to="/" activeClassName="active">
Go Back
</NavLink>
</div>
);
}
You can map the itemDetails array and then return some HTML element for each item.
import { useEffect } from 'react'
const SomeComponent = () => {
const [itemDetails, setItemDetails] = useState([]);
useEffect(() => {
// Do the fetching and setItemDetails here...
});
return (
<div style={{ display: 'flex', flexDirection: 'column' }}>
{itemDetails.map((item) => (
<span>{item.customerName}</span>
))}
</div>
);
};
The map method creates an array from the items returned by the provided callback function. So here we are just directly creating an array of span elements that render inside the div.
const { useEffect, useState } = React;
const App = () => {
const [users, setUsers] = useState([]);
useEffect(() => {
const URL = "https://jsonplaceholder.typicode.com/todos";
fetch(URL)
.then((response) => response.json())
.then((json) => setUsers(json));
}, []);
return (
<div>
<h1> Test </h1>
{users.map((user) => {
return <div key={user.id}>{user.title} </div>;
})}
</div>
);
};
ReactDOM.render(<App />, document.getElementById("root"));
<script crossorigin src="https://unpkg.com/react#17/umd/react.production.min.js"></script>
<script crossorigin src="https://unpkg.com/react-dom#17/umd/react-dom.production.min.js"></script>
<div id="root"></div>
You can useState for storing a data and use useEffect hook for calling API's. You can use fetch or Axios packages for that.
Example:
import React, { useEffect, useState } from "react";
import ReactDOM from "react-dom";
const App = () => {
const [users, setUsers] = useState([]);
useEffect(() => {
const URL = "https://jsonplaceholder.typicode.com/todos";
fetch(URL)
.then((response) => response.json())
.then((json) => setUsers(json));
}, []);
return (
<div>
<h1> Test </h1>
{users.map((user) => {
return <div key={user.id}>{user.title} </div>;
})}
</div>
);
};
ReactDOM.render(<App />, document.getElementById("root"));
Dependencies argument of useEffect is useEffect(callback, dependencies)
Let's explore side effects and runs:
Not provided: the side-effect runs after every rendering.
import { useEffect } from 'react';
function MyComponent() {
useEffect(() => {
// Runs after EVERY rendering
});
}
An empty array []: the side-effect runs once after the initial rendering.
import { useEffect } from 'react';
function MyComponent() {
useEffect(() => {
// Runs ONCE after initial rendering
}, []);
}
Has props or state values [prop1, prop2, ..., state1, state2]: the side-effect runs only when any dependency value changes.
import { useEffect, useState } from 'react';
function MyComponent({ prop }) {
const [state, setState] = useState('');
useEffect(() => {
// Runs ONCE after initial rendering
// and after every rendering ONLY IF `prop` or `state` changes
}, [prop, state]);
}
If i add the dependency array "fitems" in the dependecy array like its telling me to do, then it causes infinite loop. also if i dont use the spread operator on the array then the warning doesnt show but then the state change doesnt rerender.
Sidebar.tsx
import { useState, useEffect } from "react";
import { Link, useLocation } from "react-router-dom";
import axios from "axios";
import getItems from "./../services/SidebarItems";
import { sidebarInfoUrl } from "./../services/ApiLinks";
function Sidebar() {
const fItems = getItems();
const location = useLocation();
const paths = location.pathname.split("/");
const [items, setItems] = useState(fItems);
useEffect(() => {
axios.get(sidebarInfoUrl).then((response) => {
const updatedItems = [...fItems]
updatedItems.forEach((item) => {
if (item.match === "projects") item.value = response.data.projects;
else if (item.match === "contacts") item.value = response.data.contacts;
});
setItems(updatedItems);
console.log("here")
});
}, []);
return (
<div className="sidebar shadow">
{items &&
items.map((item) => (
<Link
key={item.match}
to={item.link}
className={
paths[2] === item.match ? "sidebar-item active" : "sidebar-item"
}
>
<span>
<i className={item.icon}></i> {item.title}
</span>
{item.value && <div className="pill">{item.value}</div>}
</Link>
))}
</div>
);
}
export default Sidebar;
Here is the sidebar items i am getting from getItems().
sidebarItems.ts
const items = () => {
return [
{
title: "Dashboard",
icon: "fas fa-home",
link: "/admin/dashboard",
match: "dashboard",
value: "",
},
{
title: "Main Page",
icon: "fas fa-star",
link: "/admin/main-page",
match: "main-page",
value: "",
},
{
title: "Projects",
icon: "fab fa-product-hunt",
link: "/admin/projects",
match: "projects",
value: "00",
},
{
title: "Contacts",
icon: "fas fa-envelope",
link: "/admin/contacts",
match: "contacts",
value: "00",
},
];
};
export default items;
Thank to AKX. I found my problem. I had to use useMemo Hook so that my getItem() function doesnt cause infinte loop when i add it to dependency array.
const fItems = useMemo(() => {
return getItems();
}, []);
instead of
const fItems = getItems();
Another fix is that,
If i dont send the items from SidebarItems.ts as function but as an array then it wont cause the infinte loop even if i dont use useMemo hook.
I have this update form for a place and I fetch its data from the backend to add initial inputs in useEffect but I got this error
Can't perform a React state update on an unmounted component. This is a no-op, but it indicates a memory leak in your application. To fix, cancel all subscriptions and asynchronous tasks in a useEffect cleanup function.
I know the problem is related to unmounted the component before update the state but I try many solutions but not working. Anyone have an idea how to fix that
const UpdatePlace = () => {
const placeId = useParams().pId;
const [loadedPlace, setLoadedPlace] = useState();
// const [isLoading, setIsLoading] = useState(true);
const { error, sendRequest, clearError } = useHttpClient();
const [isLoading, formState, inputHandler, setFormData] = useForm(
{
title: {
value: "",
isValid: false,
},
description: {
value: "",
isValid: false,
},
},
true
);
useEffect(() => {
const fetchPlace = async () => {
try {
const res = await sendRequest(`/api/places/${placeId}`);
await setLoadedPlace(res.data.place);
setFormData(
{
title: {
value: res.data.place.title,
isValid: true,
},
description: {
value: res.data.place.description,
isValid: true,
},
},
true
);
} catch (err) {}
};
fetchPlace();
}, [sendRequest, placeId, setFormData]);
if (!loadedPlace && !error) {
return (
<div className="center" style={{ maxWidth: "400px", margin: "0 auto" }}>
<Card>
<h2>No place found!</h2>
</Card>
</div>
);
}
const placeUpdateSubmitHandler = (e) => {
e.preventDefault();
console.log(formState.inputs, formState.isFormValid);
};
return (
<>
{isLoading ? (
<LoadingSpinner asOverlay />
) : error ? (
<ErrorModal error={error} onClear={clearError} />
) : (
<>
<Title label="Update place" />
<form className="place-form" onSubmit={placeUpdateSubmitHandler}>
<Input
element="input"
type="text"
id="title"
label="Update title"
validators={[VALIDATOR_REQUIRE()]}
errorText="please enter valid title"
onInput={inputHandler}
initialValue={loadedPlace.title}
initialValid={true}
/>
<Input
element="textarea"
id="description"
label="Update description"
validators={[VALIDATOR_REQUIRE(), VALIDATOR_MINLENGTH(5)]}
errorText="please enter valid description (min 5 chars) "
onInput={inputHandler}
initialValue={loadedPlace.description}
initialValid={true}
/>
<Button type="submit" disabled={!formState.isFormValid}>
Update place
</Button>
</form>
</>
)}
</>
);
};
You can use useEffect with [] with cleanup function, as it will execute last one like this:
useEffect(() => {
return () => {
console.log('cleaned up');
}
},[])
This error means that your request completes after you have navigated away from that page and it tries to update a component that is already unmounted. You should use an AbortController to abort your request. Something like this should work:
useEffect(() => {
const controller = new AbortController();
const signal = controller.signal;
const fetchPlace = async () => {
try {
const res = await fetch(`/api/places/${placeId}`, { signal }).then(response => {
return response;
}).catch(e => {
console.warn(`Fetch 1 error: ${e.message}`);
});
await setLoadedPlace(res.data.place);
setFormData(
{
title: {
value: res.data.place.title,
isValid: true,
},
description: {
value: res.data.place.description,
isValid: true,
},
},
true
);
} catch (err) {}
};
fetchPlace();
return () => {
controller.abort();
};
}, [sendRequest, placeId, setFormData]);
Edit: Fix undefined obj key/value on render
The above warning will not stop your component from rendering. What would give you an undefined error and prevent your component from rendering is how you initiate the constant loadedPlace. You initiate it as null but you use it as an object inside your Input initialValue={loadedPlace.title}. When your component tries to do the first render it reads the state for that value but fails to locate the key and breaks.
Try this to fix it:
const placeObj = {
title: {
value: '',
isValid: true,
},
description: {
value: '',
isValid: true,
};
const [loadedPlace, setLoadedPlace] = useState(placeObj);
Always make sure that when you use an object you don't use undefined keys upon render.
I'm test driving a pattern I found online known as meiosis as an alternative to Redux using event streams. The concept is simple, the state is produced as a stream of update functions using the scan method to evaluate the function against the current state and return the new state. It works great in all of my test cases but when I use it with react every action is called twice. You can see the entire app and reproduce the issue at CodeSandbox.
import state$, { actions } from "./meiosis";
const App = () => {
const [todos, setTodos] = useState([]);
const [newTodo, setNewTodo] = useState({
title: "",
status: "PENDING"
});
useEffect(() => {
state$
.pipe(
map(state => {
return state.get("todos")
}),
distinctUntilChanged(),
map(state => state.toJS())
)
.subscribe(state => setTodos(state));
}, []);
useEffect(() => {
state$
.pipe(
map(state => state.get("todo")),
distinctUntilChanged(),
map(state => state.toJS())
)
.subscribe(state => setNewTodo(state));
}, []);
return (
<div className="App">
<header className="App-header">
<img src={logo} className="App-logo" alt="logo" />
{genList(todos)}
<div className="formGroup">
<input
type="text"
value={newTodo.title}
onChange={evt => actions.typeNewTodoTitle(evt.target.value)}
/>
<button
onClick = {() => {
actions.addTodo()
}}
>
Add TODO
</button>
<button
onClick={() => {
actions.undo();
}}
>UNDO</button>
</div>
</header>
</div>
);
};
Meisos
import { List, Record } from "immutable";
import { Subject } from "rxjs";
const model = {
initial: {
todo: Record({
title: "",
status: "PENDING"
})(),
todos: List([Record({ title: "Learn Meiosis", status: "PENDING" })()])
},
actions(update) {
return {
addTodo: (title, status = "PENDING") => {
update.next(state => {
console.log(title);
if (!title) {
title = state.get("todo").get("title");
}
const todo = Record({ title, status })();
return state.set("todos", state.get("todos").push(todo));
});
},
typeNewTodoTitle: (title, status = "PENDING") => {
update.next(state => {
return state.set("todo", Record({ title, status })())
});
},
resetTodo: () => {
update.next(state =>
state.set("todo", Record({ title: "", status: "PENDING" })())
);
},
removeTodo: i => {
update.next(state => state.set("todos", state.get("todos").remove(i)));
}
};
}
}
const update$ = new BehaviorSubject(state => state) // identity function to produce initial state
export const actions = model.actions(update$);
export default update$;
Solve my problem. It stemmed from a misunderstanding of how RXJS was working. An issue on the RxJS github page gave me the answer. Each subscriptions causes the observable pipeline to be re-evaluated. By adding the share operator to the pipeline it resolves this behavior.
export default update$.pipe(
scan(
(state, updater) =>
updater(state),
Record(initial)()
),
share()
);