Reactjs Sort By Price - reactjs

I want to sort my product after I click the text "Sort by Log To High",
I put "product.sort((a,b) => a.productPrice > b.productPrice ? 1 : -1)" in a onClick function but it does not work. Now it works only if I put in the const displayProduct.
Any tutorial or video may I refer to? Thanks for helping.
export const Product = () =>{
const [product, setProduct] = useState([]);
const [pageNumber, setPageNumber] = useState(0)
const productPerPage = 12
const pagesVisited = pageNumber * productPerPage
const displayProduct = product
.slice(pagesVisited,pagesVisited + productPerPage)
.map(product => {
return(
<div className='imageContainer ' key={product.id}>
<img src={PopularOne} className="image"/>
<div className='productName'>
<Link style={{ textDecoration:'none' }} to="/productsDetails" state={{ product:product }}>{product.productName}</Link>
</div>
<div className='productPrice'>
<h3 >RM{product.productPrice}</h3>
</div>
</div>
)
})
//product.sort((a,b) => a.productPrice > b.productPrice ? 1 : -1)
const pageCount = Math.ceil(product.length/ productPerPage)
const changePage = ({selected}) =>{
setPageNumber(selected)
}
useEffect(() => {
const fetchData = async () => {
try {
const res = await axios.get(`/routes/getProduct`);
console.log(res)
setProduct(res.data);
} catch (err) {
console.log(err);
}
};
fetchData();
}, []);
return(
<div className='product'>
<div>
<button><h3>Sort By Low to High</h3></button>
<h3>Sort By High to Low</h3>
</div>
<div className='productContainer'>
{displayProduct}
</div>
<ReactPaginate
previousLabel={""}
nextLabel={""}
breakLabel="..."
pageRangeDisplayed={5}
pageCount={pageCount}
onPageChange={changePage}
containerClassName={"pagination"}
breakClassName={"break"}
pageClassName={"page-item"} //li
pageLinkClassName={"page-link"} //a
activeLinkClassName={"page-link-active"}
/>
<Footer/>
</div>
)
}

When you use the useState function provided by React it returns 2 things, first is the state variable, and second is the updater function. In your case the state is product and the updater is setProduct.
It doesn't work because you are trying to modify the state variable, just use the updater function, and it will work.
For example:
setProduct(prevState => {
let newState = [...prevState];
newState.sort((a, b) => a.productPrice > b.productPrice ? 1 : -1);
return newState;
});
Updater function provides the previous state, in this case it's named prevState.
Shallow clone the array and store it in the newState
variable.
Mutate the newState array via the sort method.
Return the newState. By returning here we tell React to update the state to the value of newState.

Related

I cannot filter the items in redux with reselect

I am working on a react-redux project. My problem is that I cannot filter the items that come from an API according to the user input. I used reselect library but did not work.
Here is my SearchComponent:
function SearchComponent({ onClose, isOpen }) {
const dispatch = useDispatch()
const searchAnimes = useSelector(inputItems)
const filtered = createSelector(inputItems, (items, e) => {
const filterText = e.target.value.toLowerCase()
const filterWords = filterText.split("")
items.filter((item) => {
return filterWords.every((word) =>
item.title.toLowerCase().includes(word)
)
})
})
useEffect(() => {
dispatch(fetchInputData())
}, [dispatch])
return (
"... Some code"
<input
type="text"
placeholder="You can search for `Kyoukai no Kanata` for example"
onChange={filtered}
/>
{searchAnimes.map((el, id) => (
<div className="searchInput" key={id}>
<img src={el.images.jpg.small_image_url} alt="" />
<p>{el.title}</p>
</div>
))}
</div>
</Modal>
)
}
Here is my slice:
export const fetchInputData = createAsyncThunk(
"anime/fetchInputData",
async () => {
const response = await axios.get(`${process.env.REACT_APP_API_KEY}?limit=5`)
// console.log(response.data.data)
return response.data.data
}
)
Btw, there is no problem with selector. Thanks in advance!
const filterWords = filterText.split("")
will break up your string by characters, not by words. I believe you want:
const filterWords = filterText.split(" ")

Updating state being one step behind WITHOUT using useEffect?

Note: I've seen people suggesting useEffect to this issue but I am not updating the state through useEffect here..
The problem I am having is that when a user selects id 7 for example, it triggers a function in App.tsx and filters the todo list data and update the state with the filtered list. But in the browser, it doesn't reflect the updated state immediately. It renders one step behind.
Here is a Demo
How do I fix this issue (without combining App.tsx and TodoSelect.tsx) ?
function App() {
const [todoData, setTodoData] = useState<Todo[]>([]);
const [filteredTodoList, setFilteredTodoList] = useState<Todo[]>([]);
const [selectedTodoUser, setSelectedTodoUser] = useState<string | null>(null);
const filterTodos = () => {
let filteredTodos = todoData.filter(
(todo) => todo.userId.toString() === selectedTodoUser
);
setFilteredTodoList(filteredTodos);
};
useEffect(() => {
const getTodoData = async () => {
console.log("useeffect");
try {
const response = await axios.get(
"https://jsonplaceholder.typicode.com/todos"
);
setTodoData(response.data);
setFilteredTodoList(response.data);
} catch (error) {
console.log(error);
}
};
getTodoData();
}, []);
const handleSelect = (todoUser: string) => {
setSelectedTodoUser(todoUser);
filterTodos();
};
return (
<div className="main">
<TodoSelect onSelect={handleSelect} />
<h1>Todo List</h1>
<div>
{" "}
{filteredTodoList.map((todo) => (
<div>
<div>User: {todo.userId}</div>
<div>Title: {todo.title}</div>
</div>
))}
</div>
</div>
);
}
In TodoSelect.tsx
export default function TodoSelect({ onSelect }: TodoUsers) {
const users = ["1", "2", "3", "4", "5", "6", "7", "8", "9", "10"];
return (
<div>
<span>User: </span>
<select
onChange={(e) => {
onSelect(e.target.value);
}}
>
{users.map((item) => (
<option value={item} key={item}>
{item}
</option>
))}
</select>
</div>
);
}
There's actually no need at all for the filteredTodoList since it is easily derived from the todoData state and the selectedTodoUser state. Derived state doesn't belong in state.
See Identify the Minimal but Complete Representation of UI State
Let’s go through each one and figure out which one is state. Ask three
questions about each piece of data:
Is it passed in from a parent via props? If so, it probably isn’t state.
Does it remain unchanged over time? If so, it probably isn’t state.
Can you compute it based on any other state or props in your component? If so, it isn’t state.
Filter the todoData inline when rendering state out to the UI. Don't forget to add a React key to the mapped todos. I'm assuming each todo object has an id property, but use any unique property in your data set.
Example:
function App() {
const [todoData, setTodoData] = useState<Todo[]>([]);
const [selectedTodoUser, setSelectedTodoUser] = useState<string | null>(null);
useEffect(() => {
const getTodoData = async () => {
console.log("useeffect");
try {
const response = await axios.get(
"https://jsonplaceholder.typicode.com/todos"
);
setTodoData(response.data);
} catch (error) {
console.log(error);
}
};
getTodoData();
}, []);
const handleSelect = (todoUser: string) => {
setSelectedTodoUser(todoUser);
};
return (
<div className="main">
<TodoSelect onSelect={handleSelect} />
<h1>Todo List</h1>
<div>
{filteredTodoList
.filter((todo) => todo.userId.toString() === selectedTodoUser)
.map((todo) => (
<div key={todo.id}>
<div>User: {todo.userId}</div>
<div>Title: {todo.title}</div>
</div>
))
}
</div>
</div>
);
}
State update doesn't happen synchronously! So, selectedTodoUser inside filterTodos function is not what you're expecting it to be because the state hasn't updated yet.
Make the following changes:
Pass todoUser to filterTodos:
const handleSelect = (todoUser: string) => {
setSelectedTodoUser(todoUser);
filterTodos(todoUser);
};
And then inside filterTodos compare using the passed argument and not with the state.
const filterTodos = (todoUser) => {
let filteredTodos = todoData.filter(
(todo) => todo.userId.toString() === todoUser
);
setFilteredTodoList(filteredTodos);
};
You probably won't need the selectedTodoUser state anymore!

Why the Search Function for Random user api is not working?

I'm working on random user api, the fetching of user name and pagination is working fine but not the search event. Please help.
I pushed my code on stackblitz, to help you guys to debug it easily.
here's the link: https://stackblitz.com/edit/search-and-pagination-in-react-by-react-hooks?file=src/App.js
below in image you can see that the name i mentioned in search box is present in api but its not comming on first place.
Working example in here.
const App = () => {
const [myApi, setMyApi] = useState([]);
const [data, setData] = useState([]); // add your data to here
const [currentPage, setCurrentPage] = useState(1);
const [postsPerPage] = useState(10);
const [searchUser, setSearchUser] = useState("");
useEffect(() => {
fetch("https://randomuser.me/api/?results=50")
.then(data => data.json())
.then(json_result => {
setData(json_result.results); // set your data to state
let myApi = renderData(json_result.results); // render your component
setMyApi(myApi); // set it to state
});
}, []);
const renderData = (data) => {
return data.map((item, idx) => {
return (
<div key={idx}>
<img src={item.picture.thumbnail} alt="" /> {item.name.first}
<hr />
</div>
);
});
}
// get current post
const indexOfLastPost = currentPage * postsPerPage; // 1 * 10 = 10
const indexOfFirstPost = indexOfLastPost - postsPerPage; // 10 - 10 = 0
const currentPosts = myApi?.slice(indexOfFirstPost, indexOfLastPost); // 0 to 10
// search users by user input
const handleSearchInput = event => {
setSearchUser(event.target.value);
const newData = renderData(data.filter(item => item.name.first.toLowerCase().includes(event.target.value))); // render filtered data
setMyApi(newData); // and set it to state
};
const paginate = pageNumber => setCurrentPage(pageNumber);
return (
<div>
<Search onChange={handleSearchInput} />
<Pagination
postsPerPage={postsPerPage}
totalPosts={myApi?.length}
paginate={paginate}
/>
{currentPosts}
</div>
);
};
const Search = ({ onChange }) => {
return (
<div>
<input
type="text"
autoFocus={true}
placeholder="search users"
onChange={onChange}
/>
</div>
);
};
Since you're useEffect has [] (empty array) as the dependency, you're user fetching logic will only be called once i.e. on the initial rendering. You can add searchUser as useEffect's dependency so you can fetch users whenever the searchUser text changes.

how I test useEffect with isLoading state

I want to build test when the isLoading state change the component.
I know in the class component there is the way by do setState with enzyme, but I would like to know how I can do it here.
const Spacex = () => {
const [open, setOpen] = useState(false);
const [upComingLaunches, setUpComingLaunches] = useState([]);
const [Launchpad, setLaunchpad] = useState([])
const [isLoading, setIsLoading] = useState(true);
useEffect(() => {
let tempData;
SpaceXNextLaunche()
.then(data => {
setUpComingLaunches(data);
tempData = data;
return LaunchPad()
}).then(dataLaunch => {
const foundTheLaunch = dataLaunch.docs.filter((Launch, index) => {
return tempData.id === Launch.id
});
setLaunchpad(foundTheLaunch);
setIsLoading(false);
})
}, [])
if (isLoading) return <LoadingComp />
return (
<div>
<div className="upcoming-launches">
<h1 className={styles.title}>upcoming launche</h1>
<div className={styles.CountDownWarrper}>
{Object.keys(upComingLaunches).length > 0 ?
<Card className={styles.CountDownCard}>
<div className={styles.MissionName}>{upComingLaunches.name}</div>
<div className={styles.gridBadges}>
<div className={styles.CountDown}><CountDownClock upComingLaunches={upComingLaunches} /></div>
<div className={styles.badgeFlex}><img className={styles.badge} src={upComingLaunches.links["patch"]["small"]} alt="mission patch" /></div>
</div>
<GoogleMap
mapVisiblity={(e) => setOpen(!open)}
open={open}
placeName={Launchpad[0].launchpad.full_name} />
</Card>
: null}
</div>
</div>
</div>
)
}
export default Spacex;
The proper way to test functional components is to test the actual functions' behaviour, not their implementation. In your case that would be mocking the SpaceXLaunche() to return its data after some timeout, eg:
function SpaceXLauncheMock() {
return new Promise(resolve => {
setTimeout(resolve(data), 1500);
});
}
const SpaceXLaunche = jest.spyOn(SpaceXLaunche.prototype, 'SpaceXLaunche')
.mockImplementation(SpaceXLauncheMock);
then, you'd test your consequence of isLoading - the presence or absence of LoadingComp, initially, and again after the timeout (don't forget to put done as the test case's argument):
expect(component.contains(<LoadingComp />)).toBe(true);
setTimeout(() => {
expect(component.contains(<LoadingComp />)).toBe(false);
done();
}, 2000);

Reactjs How is the weather state in <Weather /> component is undefined even though 'promise' is fullfilled?

I am trying to create a search filter for countries. I search a country and display their information and weather of country's capital using a weather api. I am fetching the data of a country using axios but the response.data is undefined and hence its cause error.
I know the code is async. So how do I fetch data from url before I setWeather(response.data) .
const Weather = ({capital}) => {
const [weather, setWeather] = useState([])
const key = 'mykey'
const url = `http://api.weatherstack.com/current?access_key=${key}&query=${capital}`
axios.get(url)
.then(response => {
console.log('promise fullfilled')
setWeather(response.data)
})
return(
<div>
<h1>Weather in {weather.location.name}</h1>
<h2>temperature: {weather.current.temperature} </h2>
<img src = {weather.current.weather_icons} />
<h2>wind: {weather.current.wind_speed} kph direction {weather.current.wind_dir}</h2>
</div>
)
}
const PrintLanguages = ({lang}) =>{
return(
lang.map(l => <li key={l}>{l}</li>)
)
}
const View = ({country}) =>{
const lang = country.languages.map(lang => lang.name)
return(
<div>
<h1>{country.name}</h1>
<p>capital {country.capital}</p>
<p>population {country.population}</p>
<h2>languages</h2>
<ul><PrintLanguages lang={lang}/></ul>
<img src={country.flag} alt="flag photo" height="100" width="100"/>
<Weather capital={country.capital}/>
</div>
)
}
I expected this result but instead I am getting this Type Error
Please guide me on how to fix this ??
You can use the effect hook here:
const Weather = ({capital}) => {
const [weather, setWeather] = useState({location:{}, current: {}});
const key = 'mykey'
const url = `http://api.weatherstack.com/current?access_key=${key}&query=${capital}`
useEffect(() => {
axios.get(url)
.then(response => {
console.log('promise fullfilled')
setWeather(response.data)
})
}, [capital]) // Fetch the data when capital changes
return(
<div>
<h1>Weather in {weather.location.name}</h1>
<h2>temperature: {weather.current.temperature} </h2>
<img src = {weather.current.weather_icons} />
<h2>wind: {weather.current.wind_speed} kph direction {weather.current.wind_dir}</h2>
</div>
)
}
This will call the api method after the component is mounted.
Also make sure your initial state structure is the same as the one for the rendered state. In your case you set it to an empty array but when rendering it expects an object.
One more way is to use a loading state, during which you can show a loading indicator before the data fetches:
const Weather = ({capital}) => {
const [weather, setWeather] = useState({location:{}, current: {}});
const [loading, setLoading] = useState(true);
const key = 'mykey'
const url = `http://api.weatherstack.com/current?access_key=${key}&query=${capital}`
useEffect(() => {
axios.get(url)
.then(response => {
console.log('promise fullfilled')
setLoading(false);
setWeather(response.data)
})
}, [])
return loading ? <p>Loading...</p> : (
<div>
<h1>Weather in {weather.location.name}</h1>
<h2>temperature: {weather.current.temperature} </h2>
<img src = {weather.current.weather_icons} />
<h2>wind: {weather.current.wind_speed} kph direction {weather.current.wind_dir}</h2>
</div>
)
}

Resources