I'm using Redux Toolkit for the first time. Data is successfully available in the console, but when i try to render data in the UI, i'm getting undefined JSON path {${weather[0].description} ${weather[0].main}} Maybe i need to check something with if() statement but i don't know how and where. My own if() solution didn't do the trick in App.js
JSON data
description: "broken clouds"
icon: "04n"
id: 803
main: "Clouds"
[[Prototype]]: Object
length: 1
[[Prototype]]: Array(0)
App.js side
import { useDispatch, useSelector } from 'react-redux';
import { fetchWeatherAction } from './redux/slices/weatherSlices';
function App() {
const dispatch = useDispatch();
useEffect(() => {
dispatch(fetchWeatherAction('Seoul'));
}, []);
const state = useSelector(state => state.weather);
const { loading, weather, error } = state || {};
if(!weather){
return null
}
console.log(weather);
return (
<div className="App">
<header className="App-header">
{weather.map((weather, index) => (
<div key={index}>
<div>{`${weather[0].description} ${weather[0].main}`}</div>
</div>
))}
</header>
</div>
);
}
export default App;```
Redux Toolkit side
``` import { createAsyncThunk, createSlice } from '#reduxjs/toolkit';
import axios from 'axios';
export const fetchWeatherAction = createAsyncThunk(
'weather/fetch',
async (payload, {rejectWithValue, getState, dispatch})=>{
try{
const {data} = await axios.get(`http://api.openweathermap.org/data/2.5/weather?q=${payload}&appid=7469e38d322111e34a7027db2eee39c3`);
return data;
}catch(error){
if(!error?.response){
throw error
}
return rejectWithValue(error?.response?.data);
}
}
);
const weatherSlice = createSlice({
name: 'weather',
initialState: {},
extraReducers: builder => {
builder.addCase(fetchWeatherAction.pending, (state, action) => {
state.loading = true;
});
builder.addCase(fetchWeatherAction.fulfilled, (state, action) => {
state.weather = action?.payload;
state.loading = false;
state.error = undefined;
});
builder.addCase(fetchWeatherAction.rejected, (state, action) => {
state.loading = false;
state.weather = undefined;
state.error = action?.payload;
})
},
});
export default weatherSlice.reducer;```
It appears that you are mapping weather, which looks like an array of objects, and then trying to index into that object as e.g. weather[0].... If weather inside the map operation is in fact an object and not an array, this will not work. I think what you want is something like the following. Note that I've changed the name of the interior variable to weatherItem for clarity:
{weather.map((weatherItem, index) => (
<div key={index}>
<div>{`${weatherItem.description} ${weatherItem.main}`}</div>
</div>
))}
Related
How to pass data inside single product page ??
const store = configureStore({
reducer: {
cart: CartReducer,
product: ProductReducer,
},
});
productSlice
import { createSlice } from "#reduxjs/toolkit";
import { STATUS } from "./StatusSlice";
const ProductSlice = createSlice({
name: "product",
initialState: {
data: [],
status: STATUS.IDLE,
},
reducers: {
setProducts(state, action) {
state.data = action.payload;
},
setStatus(state, action) {
state.status = action.payload;
},
},
});
export const { setProducts, setStatus } = ProductSlice.actions;
export default ProductSlice.reducer;
export function fetchProducts() {
return async function fetchProduct(dispatch) {
dispatch(setStatus(STATUS.LOADING));
try {
const res = await fetch("https://fakestoreapi.com/products#");
const data = await res.json();
dispatch(setProducts(data));
dispatch(setStatus(STATUS.IDLE));
} catch (error) {
console.log(error);
dispatch(setStatus(STATUS.ERROR));
}
};
}
here is product page
const Products = () => {
const dispatch = useDispatch();
const { data: products, status } = useSelector((state) => state.product);
useEffect(() => {
dispatch(fetchProducts());
}, []);
if (status === STATUS.LOADING) {
return <h1>Loading......!!</h1>;
}
if (status === STATUS.ERROR) {
return <h1>Something went wrong</h1>;
}
return (
<>
<div className={style.wrapper}>
{products.map((product) => {
return (
<Link to={`/products/${product.id}`} key={product.id}>
// rest of the codes
</Link>
);
})}
</div>
</>
);
};
i'm not able to understand how get data in single product page
const SingleProduct = () => {
return (
// details
);
};
i had two option,
i can fetch api data in products page, single product page and wherever i want to or
i can fetch api data in one page (productSlice) and use everywhere that's why redux has been made.
Need Solution :
You are passing the product id as part of the URL path:
{products.map((product) => {
return (
<Link to={`/products/${product.id}`} key={product.id}>
// rest of the codes
</Link>
);
})}
Assuming you have a router and a route for path="/products/:productId" rendering the SingleProduct component:
<Routes>
...
<Route path="/products/:productId" element={<SingleProduct />} />
...
</Routes>
Then the SingleProduct component can use the useParams hook to access the productId route path parameter value and use this to filter the products array selected from the redux store.
import { useSelector } from 'react-redux';
import { useParams } from 'react-router-dom';
const SingleProduct = () => {
const products = useSelector(state => state.product.data);
const { productId } = useParams();
const product = products.find(product => String(product.id) === productId);
if (!product) {
return "Sorry, no matching product found.";
}
return (
// render product details
);
};
I'm learning react context and while developing a todo application using useContext, I'm facing an issue where on submitting one task, the same task gets added two times to an array. The output component would loop through this array and display the results. While debugging I observed that, although the submit of task add only one entry into the array, not sure why and how, the consumer component gets the array with duplicate entry. Please let me know, what I'm missing.
Here is my code of index file that maintains context
import { createContext, useReducer } from "react";
import ContextReducer, { initialState } from "./ContextReducer";
const taskContext = createContext();
const ContextProvider = (props) => {
const [state, dispatch] = useReducer(ContextReducer, initialState);
const setTaskInput = (taskInput) => {
dispatch({
type: "SET_TASKINPUT",
payload: taskInput,
});
};
const addTask = (task) => {
dispatch({
type: "ADD_TASK",
payload: task,
});
};
const deleteTask = (id) => {
dispatch({
type: "DELETE_TASK",
payload: id,
});
};
const todoContext = {
todo: state.todo,
taskInput: state.taskInput,
setTaskInput,
addTask,
deleteTask,
};
return (
<taskContext.Provider value={todoContext}>
{props.children}
</taskContext.Provider>
);
};
export { taskContext };
export default ContextProvider;
This is the code for reducer
const initialState = {
todo: [],
taskInput: "",
};
const ContextReducer = (state = initialState, action) => {
if (action.type === "SET_TASKINPUT") {
state.taskInput = action.payload;
return {
todo: state.todo,
taskInput: state.taskInput,
};
}
if (action.type === "ADD_TASK") {
state.todo = [...state.todo, action.payload];
return {
todo: state.todo,
taskInput: state.taskInput,
};
}
if (action.type === "DELETE_TASK") {
state.todo = state.todo.filter((todo) => todo.id !== action.payload);
return {
todo: state.todo,
taskInput: state.taskInput,
};
}
return state;
};
export { initialState };
export default ContextReducer;
This is the code of output component or say, consumer component
import React, { Fragment, useContext } from "react";
import { taskContext } from "../../Context";
import styles from "./Content.module.css";
const Output = () => {
const { todo, deleteTask } = useContext(taskContext);
const deleteHandler = (e) => {
deleteTask(+e.target.parentElement.parentElement.id);
};
return (
<Fragment>
{todo.length > 0 && (
<div className={styles.outputDiv}>
<ul>
{todo.map((task) => {
return (
<li key={task.id} id={task.id}>
<div className={styles.row1}>{task.task}</div>
<div className={styles.row2}>
<button className={styles.edit}>Edit</button>
<button className={styles.delete} onClick={deleteHandler}>
Delete
</button>
</div>
</li>
);
})}
</ul>
</div>
)}
</Fragment>
);
};
export default Output;
I am getting an error when I try to filter the results of data I pulled from an API.
The error message comes when I use my searchBar component to filter the redux data immediately when I type anything.
Below is the error message:
"Error: [Immer] An immer producer returned a new value and modified its draft. Either return a new value or modify the draft."
What must I do to filter the data and return the new data?
Below are the components and the Redux TK slice I am using.
Home.js Component
import React, {useState, useEffect} from 'react';
import { Col, Container, Row } from 'react-bootstrap';
import { useDispatch, useSelector } from 'react-redux';
import { getCountries } from '../redux/search';
// import LazyLoading from 'react-list-lazy-load';
// import { updateText } from '../redux/searchTerm';
import SearchBar from '../components/SearchBar';
import CardComponent from '../components/Card';
import DropdownMenu from '../components/DropdownMenu';
const Home = () => {
const dispatch = useDispatch();
// const [ countries, setCountries ] = useState(null);
const countries = useSelector((state) => state.search);
// const filterCountry = (searchCountry) => {
// countries.list.payload.filter(country => country.name.toLowerCase() == searchCountry.toLowerCase())
// }
useEffect(() => {
dispatch(getCountries());
console.log(countries);
}, [dispatch]);
// console.log(countries.filter(country => country.region.toLowerCase() === 'africa'))
return (
<>
<Container className="home-container">
<Row>
<Col sm={12} md={6} className="left">
<SearchBar />
</Col>
<Col sm={12} md={6} className="right">
<DropdownMenu/>
</Col>
</Row>
<Row className="countryRow">
{ countries.status == 'success'
?<>
{countries.list.map((country) => {
return <CardComponent key={country.name}
title={country.name}
image={country.flags[0]}
population={country.population}
region={country.region}
capital={country.capital}/>
})}
</>
:<div>Loading.....</div>
}
</Row>
</Container>
</>
)
}
export default Home;
SearchBar.js
import React from 'react';
import {useSelector, useDispatch} from 'react-redux';
// import { updateText } from '../redux/searchTerm';
import { searchTerm } from '../redux/search';
const SearchBar = () => {
const query = useSelector((state) => state.searchDefault);
const dispatch = useDispatch();
return (
<>
<form>
<input
className="search"
type="search"
placeholder="Search for a country"
value={query}
onChange={(e) => dispatch(searchTerm(e.target.value))}/>
</form>
</>
)
}
export default SearchBar;
search.js slice
import { createSlice, createAsyncThunk } from '#reduxjs/toolkit';
import axios from 'axios';
export const getCountries = createAsyncThunk(
'searchDefault/getCountries', async () => {
try {
const resp = await axios.get('https://restcountries.com/v2/all');
return await resp.data;
} catch (error) {
console.log(error.message);
}
}
)
const searchSlice = createSlice({
name: 'searchDefault',
initialState: {
list: [],
status: null,
value: ''
},
reducers: {
searchTerm: (state, action) => {
// console.log(state.value);
state.value = action.payload
// console.log(state.value);
state.list.filter( country => country.name == state.value);
return state.list;
// console.log(state.list);
}
},
extraReducers: {
[getCountries.pending]: (state, action) => {
state.status = 'loading'
},
[getCountries.fulfilled]: (state, payload) => {
console.log(payload)
state.list = payload.payload
state.status = 'success'
},
[getCountries.rejected]: (state, action) => {
state.status = 'failed'
}
}
})
export const { searchTerm } = searchSlice.actions;
export default searchSlice.reducer;
According to the docs from redux-toolkit:
Redux Toolkit's createReducer API uses Immer internally automatically. So, it's already safe to "mutate" state inside of any case reducer function that is passed to createReducer:
And Immer docs:
It is also allowed to return arbitrarily other data from the producer function. But only if you didn't modify the draft
In your reducer, you are using immer API to mutate the value (state.value = action.payload) and return the result. However Immer only allows you do one of the 2 things, not both. So either you mutate the state:
searchTerm: (state, action) => {
state.value = action.payload; // notify redux that only the value property is dirty
}
Or replace the new state completely:
searchTerm: (state, action) => {
return { ...state, value: action.payload }; // replace the whole state
// useful when you need to reset all state to the default value
}
Most of the time, you only need to tell redux that a specific property of the slice is changed, and redux will then notify only the components subscribed to that property (via useSelector) to re-render. So remove the return statement in your reducer and your code should be working again:
searchTerm: (state, action) => {
state.value = action.payload;
state.list = state.list.filter( country => country.name == state.value);
// remove the return statement
}
Currently I have a BooksList component and I'm passing down props to my BooksDetails component when a title is clicked. How do I use an Apollo hook to only query on props change?
I'm not sure how to do this using hooks. I've looked through the UseQuery documentation from Apollo. I couldn't find documentation on UseLazyQuery.
I've tried the following, but it always returns undefined:
const { loading, error, data } = useQuery(getBookQuery, {
options: (props) => {
return {
variables: {
id: props.bookId
}
}
}
})
BookList:
const BookList = () => {
const {loading, error, data} = useQuery(getBooksQuery)
const [selectedId, setId] = useState('');
return (
<div id='main'>
<ul id='book-list'>
{data && data.books.map(book => (
<li onClick={() => setId(book.id)} key={book.id}>{book.name}</li>
)) }
</ul>
<BookDetails bookId={selectedId} />
</div>
);
};
export default BookList;
BookDetails:
const BookDetails = (props) => {
const { loading, error, data } = useQuery(getBookQuery, {
options: (props) => {
return {
variables: {
id: props.bookId
}
}
}
})
console.log(data)
return (
<div id='book-details'>
<p>Output Book Details here</p>
</div>
);
};
export default BookDetails;
EDIT - I forgot to add that my GetBookQuery has a parameter of ID so an example would be getBookQuery(123).
Use the useLazyQuery like this instead:
const [getBook, { loading, error, data }] = useLazyQuery(getBooksQuery);
Full example:
import React from 'react';
import { useLazyQuery } from '#apollo/react-hooks';
const BookList = () => {
const [getBook, { loading, error, data }] = useLazyQuery(getBooksQuery);
return (
<div id='main'>
<ul id='book-list'>
{data && data.books.map(book => (
<li onClick={() => getBook({ variables: { id: book.id } })}} key={book.id}>{book.name}</li>
)) }
</ul>
<BookDetails data={data} />
</div>
);
};
export default BookList;
Examples and documentation can be found in Apollo's GraphQL documentation
https://www.apollographql.com/docs/react/data/queries/
I was also following along the same example and I came up here with the same question. I tried doing the following way. This might be helpful for someone.
BookDetails.js:
function BookDetails({ bookId }) {
const [loadDetails, { loading, error, data }] = useLazyQuery(getBook);
useEffect(() => {
if (bookId) {
loadDetails({ variables: { id: bookId } });
}
}, [bookId, loadDetails]);
if (!bookId) return null;
if (loading) return <p>Loading...</p>;
if (error) return <p>Error!</p>;
// for example purpose
return <div>{JSON.stringify(data)}</div>;
}
export default BookDetails;
I want to implement an action which gets item by id, so I've created fetchItemAction(), as follows:
export const fetchItemAction = () => (dispatch) => {
dispatch({
type: FETCH_ITEM_REQUEST,
});
return axios.get(`${url}/notes/5d4724cd62087b0e141f75a4`)
.then(({ data }) => {
console.log(data);
dispatch({
type: FETCH_ITEM_SUCCESS,
data,
});
})
.catch((error) => {
dispatch({
type: FETCH_ITEM_FAILURE,
});
});
};
Then, I try to set item field in State in my reducer:
const initialState = {
isAuthenticated: false,
user: {},
};
const reducer = (state = initialState, action) => {
switch (action.type) {
case FETCH_ITEM_REQUEST:
return {
...state,
isLoading: true,
};
case FETCH_ITEM_SUCCESS:
return {
...state,
item: action.data,
isLoading: false,
};
}
};
Then, I try to get those data in Details component:
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { fetchItemAction } from 'actions/actions';
class Details extends Component {
componentDidMount() {
const { fetchItem } = this.props;
fetchItem();
}
render() {
const { item, isLoading } = this.props;
return (
<>
{console.log(item)}
{/* <p>{item.title}</p> */}
</>
);
}
}
const mapStateToProps = ({ item, isLoading }) => ({ item, isLoading });
const mapDispatchToProps = dispatch => ({
fetchItem: () => dispatch(fetchItemAction()),
});
export default connect(mapStateToProps, mapDispatchToProps)(Details);
As a result, I'm getting following data in console:
Apart from two undefinded the result looks good because there is correct response from my backend.
But, when I try to uncomment <p>item.title</p> line in Details.js, the app crash:
TypeError: Cannot read property 'title' of undefined
I also implemented correctly fetchItemsAction(), addItemAction() and deleteItemAction() which are very similar but I have no idea what is wrong in fetchItemAction().
This is an asynchronous issue. componentDidMount is called when the component is mounted. Then, you're calling fetch. So on your first render, item is undefined. Once the data is returned, the render is triggered again with item data.
So, just check if item is defined:
render() {
const { item, isLoading } = this.props;
return (
<>
{console.log(item)}
{item && <p>{item.title}</p>}
</>
);
}