I have a simple search aplication, where depending by user input, it get the result on front end.
My redux code:
import { persons } from "./persons";
import { createStore } from "redux";
//contant
export const SEARCH = {
SEARCH_PERSON: "SEARCH_PERSON"
};
//action
export const searchPersonAction = (person) => {
const personSearched = persons.filter((p) =>
p.name.toLowerCase().includes(person.toLowerCase())
);
return {
type: SEARCH.SEARCH_PERSON,
payload: personSearched
};
};
//reducer
const initialState = {
name: persons
};
export const search = (state = initialState, { type, payload }) => {
switch (type) {
case SEARCH.SEARCH_PERSON:
return {
...state,
name: payload
};
default:
return state;
}
};
//store
export const store = createStore(search);
UI component:
import React, { useEffect } from "react";
import { useDispatch, useSelector } from "react-redux";
import { searchPersonAction } from "./store";
const Search = () => {
const dispatch = useDispatch();
const selector = useSelector((s) => s);
const search = (e) => {
const txt = e.target.value;
dispatch(searchPersonAction(txt));
};
return (
<div>
<input onChange={search} placeholder="search" />
<ul>
{selector.name.map((p) => (
<li key={p.name}>{p.name}</li>
))}
</ul>
</div>
);
};
export default Search;
Now the application works properly, but i want to integrate in my application reselect library. I want to use reselect in filter logic.
Question: Which changes should i add in my application code?
demo: https://codesandbox.io/s/brave-monad-litc1?file=/src/Search.js:0-577
You can wrap the function in useSelector into createSelector from reselect which will memoise the selector values. You can do it like this:
import React, { useEffect } from "react";
import { createSelector } from "reselect";
import { useDispatch, useSelector } from "react-redux";
import { searchPersonAction } from "./store";
const memoiseSelector = createSelector(
(s) => s.name,
(name) => name
);
const Search = () => {
const dispatch = useDispatch();
const name = useSelector(memoiseSelector);
const search = (e) => {
const txt = e.target.value;
dispatch(searchPersonAction(txt));
};
return (
<div>
<input onChange={search} placeholder="search" />
<ul>
{name?.map((p) => (
<li key={p.name}>{p.name}</li>
))}
</ul>
</div>
);
};
export default Search;
Further more examples you can check here on official docs.
Related
I add product to the cart from Products and product gets added to the cart (as each product increases length of array), but when rendering the updated cart from Cart component, it doesn't render properly and console keeps giving error "Warning: Each child in a list should have a unique "key" prop.
Check the render method of Cart. See https://reactjs.org/link/warning-keys for more information.
div".
I did various methods to go over the solution, but just figured out probably the product element's (from productsinCart array) payload is coming as undefined that is what giving problem, but I may be wrong here.
Any suggestions to solve the problem?
cartSlice.js
import { createSlice } from "#reduxjs/toolkit";
const cartSlice = createSlice({
name: "cart",
initialState: [],
reducers: {
add(state, action) {
state.push(action.payload);
},
remove(state, action) {
state.splice(action.payload, 1);
},
},
});
`
export const { add, remove } = cartSlice.actions;
export default cartSlice.reducer;
store.js
import { configureStore } from "#reduxjs/toolkit";
import cartReducer from "./Slices/cartSlice";
const store = configureStore({
reducer: {
cart: cartReducer,
},
});
export default store;
Products.js
import React, { useState, useEffect } from "react";
import { useDispatch } from "react-redux";
import { add } from "../store/Slices/cartSlice";
const Products = () => {
const [products, setProducts] = useState([]);
const dispatch = useDispatch();
useEffect(() => {
const fetchProducts = async () => {
const url = "https://fakestoreapi.com/products";
try {
const response = await fetch(url);
const result = await response.json();
setProducts(result);
} catch (error) {
console.log("Sorry, there is an error");
}
};
fetchProducts();
}, []);
const handleAdd = (product) => {
dispatch(add(product));
};
return (
<div>
{products?.map((product) => {
const { id, image, price, title } = product;
return (
<div key={id}>
<img src={image} alt="" />
<h4>{title}</h4>
<h5>{price}</h5>
<button
onClick={() => {
handleAdd(add());
}}
>
Add to Cart
</button>
</div>
);
})}
</div>
);
};
export default Products;
Cart.js
import React from "react";
import { useSelector } from "react-redux";
import Layout from "../components/Layout/Layout";
const Cart = () => {
const productsinCart = useSelector((state) => {
return state.cart;
});
return (
<Layout>
<h3>Cart </h3>
<section>
<div>
{productsinCart?.map((product) => {
const { id, image, price, title } = product;
return (
<div key={id}>
<img src={image} alt="" />
<p>{title}</p>
<p>{price}</p>
<button>Remove</button>
</div>
);
})}
</div>
</section>
</Layout>
);
};
export default Cart;
Note: I only tried adding the product and rendering the updated cart.
I have problem with initialization of search and filter for ma countries api. I have stuck with problem how to implement code. I will be gratefull for any help or some advices how to do this. My files look like this:
Store.js
import { applyMiddleware, combineReducers, createStore, compose } from 'redux'
import getCountries from './getCountries'
const initialState = {
variable: {
data: [],
},
filters: {
searchPhrase: '',
continent: ''
}
}
const reducers = {
variable: getCountries, // variablesReducer
}
const storeReducer = combineReducers(reducers)
const store = createStore(
storeReducer,
initialState,
compose(applyMiddleware(), window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__()) // przed deployem usunac
)
export default store
SearchPhraseRedux.js
//SearchEngine
export const getData = ({variable}, filters) => {
if(!filters.searchPhrase){
return variable.data
}
//Logic
//export const getCountryByName = ({variable}, name) =>
//variable.data.find(e=>e.name.common === name)
}
const reducerName = 'variable'
const createActionName = (name) => `app/${reducerName}/${name}`
export const SET_DATA = createActionName("SET_DATA")
export const setData = payload => ({payload, type: SET_DATA})
export default function reducer(statePart=[], action={}){
switch(action.type) {
case SET_DATA:
return {...statePart, data: action.payload}
default:
return statePart
}
}
countryContainer.js
import { connect } from "react-redux"
import { getData, setData} from '../../redux/getCountries'
import Countries from "./countries"
const mapStateToProps = (state) => ({
data: getData(state),
})
const mapDispatchToProps = dispatch =>({
setData: (value) => dispatch(setData(value)),
})
export default connect(mapStateToProps, mapDispatchToProps)(Countries)
countries.js
import React from 'react'
import { useEffect} from 'react'
import Axios from 'axios'
import '../../../src/index.css'
import {Link} from 'react-router-dom'
const Countries = ({data,setData}) => {
const url = 'https://restcountries.com/v3.1/all'
useEffect(() => {
Axios.get(url)
.then((rest) =>{
setData(rest.data)
})
},[url, setData])
return (
<div>
<section className='grid'>
{data.map((country) => {
const {population, region, capital} = country
const {png} = country.flags
const {official, common} = country.name
return <article key={common}>
<div>
<Link classsName='link' to={`/countries/${common}`}>
<img src ={png} alt={official}/>
</Link>
<div className='details'>
<h3>{common}</h3>
<h4>Population: <span>{population}</span></h4>
<h4>Region: <span>{region}</span></h4>
<h4>Capital: <span>{capital}</span></h4>
</div>
</div>
</article>
})}
</section>
</div>
)
}
export default Countries
I have tried to get One country like this:
export const getCountryByName = ({variable}, name) => variable.data.find(e=>e.name.common === name)
but,by extending this code, it is difficult for me to make search and filter
I have a form that adds new articles. I need to create another form that triggers when I click on a created article and add a property "keyword" to the article state and display it. I tried to do something but I am kinda stuck.
Form.jsx component that adds the article/s:
import React, { useState } from 'react';
import { useDispatch } from 'react-redux';
import { v1 as uuidv1 } from 'uuid';
import { ADD_ARTICLE } from '../constants/action-types';
const Form = () => {
const [title, setTitle] = useState('');
const dispatch = useDispatch();
const handleChange = (e) => {
const { value } = e.target
setTitle(value);
}
const handleSubmit = (e) => {
e.preventDefault();
const id = uuidv1();
dispatch({ type: ADD_ARTICLE, payload: { id, title } });
setTitle('');
}
return (
<form onSubmit={handleSubmit}>
<div className='form-group'>
<label htmlFor='title'>Title</label>
<input
type='text'
className='form-control'
id='title'
value={title}
onChange={handleChange}
/>
</div>
<input className='btn btn-success btn-lg' type='submit' value='SAVE' />
</form>
);
}
export default Form;
List.jsx component where the articles are displayed:
import React, { useState,useEffect } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import KeywordForm from './KeywordForm.jsx';
import { fetchArticles } from '../thunk';
const List = () => {
const [showForm,setShowForm]=useState(false);
const articles = useSelector(state => state.articles);
const dispatch = useDispatch();
const displayForm=()=>{
setShowForm(!showForm)
}
useEffect(() => {
dispatch(fetchArticles);
}, []);
return (
<>
<ul className='list-group list-group-flush'>
{articles.map(article => (
<li className='list-group-item' key={article.id} onClick={displayForm}>
{article.title}
</li>
))}
</ul>
<div>
{showForm && (
<KeywordForm />
)}
</div>
</>
);
}
export default List;
Here i added a state that displays the KeywordForm component when I click an article.
KeywordForm.jsx component,this is the one that I created to add the keyword:
import React, { useState } from 'react';
import { useDispatch ,useSelector} from 'react-redux';
import { ADD_KEYWORD } from '../constants/action-types';
const KeywordForm = ({id,title}) => {
const [keyword,setKeyword]=useState('');
const articles = useSelector(state => state.articles);
const dispatch=useDispatch();
console.log(articles)
const handleChange = (e) => {
const { value } = e.target
setKeyword(value);
}
const handleSubmit = (e) => {
e.preventDefault();
}
return (
<form onSubmit={handleSubmit}>
<div className='form-group'>
<label htmlFor='keyword'>Keyword</label>
<input
type='text'
className='form-control'
id='keyword'
value={keyword}
onChange={handleChange}
/>
</div>
<input className='btn btn-success btn-lg' type='submit' value='SAVE' />
</form>
);
}
export default KeywordForm;
reducers.js
const initialState = {
articles: []
};
const rootReducer = (state = initialState, action) => {
const { type, payload } = action;
switch(type) {
case ADD_ARTICLE: {
return {...state,
articles: [...state.articles,payload]
};
}
case ADD_KEYWORD: {
return Object.assign({}, state, {
articles: state.articles.concat(payload)
});
}
case ARTICLES_RETRIEVED: {
return Object.assign({}, state, {
articles: state.articles.concat(payload)
});
}
default:
return state;
}
}
export default rootReducer;
actions.js
import { ADD_ARTICLE, ARTICLES_RETRIEVED,ADD_KEYWORD } from '../constants/action-types';
const addArticle = (payload) => {
return { type: ADD_ARTICLE, payload };
}
const addKeyword = (payload) => {
return { type: ADD_KEYWORD, payload };
}
const articlesRetrieved = (payload) => {
return { type: ARTICLES_RETRIEVED, payload };
}
export { addArticle, articlesRetrieved,addKeyword };
What should i add to my reducers/actions to make this work? My idea is that i have to somehow pass the id of the article clicked and then in the reducer find it's index or something and check it with the payload.id .
You want to modify an existing article in the state and a keyword to it (can there be an array of keywords, or just one?). In order to do that, your action payload will need to contain both the keyword and the id of the article that it belongs to.
Your reducer will find the article that matches the id and replace it with a copied version that has the keyword added to it.
case ADD_KEYWORD: {
return {
...state,
articles: state.articles.map(article =>
// find the article to update
article.id === payload.id ?
// update it
{ ...article, keyword: payload.keyword } :
// otherwise return the original
article
}
}
This is easier to do with the official Redux Toolkit because you can modify the draft state directly and you don't need to worry about mutations.
How to pass text value to another component using Redux in React?
I am learning Redux in React. I am trying to pass text value to another component using Redux in React.
My code is like below
Mycomponent.js
import React, { Component } from 'react';
import { connect } from 'react-redux';
class Mycomponent extends Component {
state = {
textInput: '',
}
handleChange = event => {
this.props.dispatch({ type: "add" });
}
render = () => {
return (
<div>
<input
type="text"
onChange={this.handleChange} />
</div>
);
}
}
const mapStateToProps = state => ({ nameState: state.nameState});
export default connect(mapStateToProps)(Mycomponent);
nameAction.js
export const nameAction = () => ({
type: 'add'
});
export default { nameAction };
nameReducer.js
const nameReducer = (state = {}, action) => {
switch (action.type) {
case 'add': {
return {
...state,
nameState: action.payload
};
}
default:
return state;
}
};
export default nameReducer;
Outputcomponent.js
import React, { Component } from 'react';
class Outputcomponent extends Component {
render = (props) => {
return (
<div>
<div>{this.props.nameState }</div>
</div>
);
}
}
export default Outputcomponent;
The use of redux hooks explained by Josiah is for me the best approach but you can also use mapDispatchToProps.
Even if the main problem is that you don't pass any data in your 'add' action.
nameAction.js
You call the action.payload in nameReducer.js but it does not appear in your action
export const nameAction = (text) => ({
type: 'add',
payload: text
});
Mycomponent.js
Then as for your state we can mapDispatchToProps.
(I think it's better to trigger the action with a submit button and save the input change in your textInput state, but I guess it's intentional that there is none)
import React, { Component } from 'react';
import { connect } from 'react-redux';
import {nameAction} from './nameAction'
class Mycomponent extends Component {
state = {
textInput: '',
}
handleChange = event => {
this.props.nameAction(event.target.value);
}
render = () => {
return (
<div>
<input
type="text"
onChange={this.handleChange} />
</div>
);
}
}
const mapStateToProps = state => ({ nameState: state.nameState});
const mapDispatchToProps = dispatch => ({ nameAction: (text) => dispatch(nameAction(text))});
export default connect(mapStateToProps,mapDispatchToProps)(Mycomponent);
OutputComponent.js
to get the data two possibilities either with a class using connect and mapStateToProps , or using the useSelector hook with a functional component.
with a Class
import React, { Component } from "react";
import { connect } from "react-redux";
class OutputComponent extends Component {
render = () => {
return (
<div>
<div>{this.props.nameState}</div>
</div>
);
};
}
const mapStateToProps = state => state;
export default connect(mapStateToProps)(OutputComponent);
with a functional component
import React from "react";
import { useSelector } from "react-redux";
const OutputComponent = () => {
const nameState = useSelector((state) => state.nameState);
return (
<div>
<div>{nameState}</div>
</div>
);
};
export default OutputComponent;
Of course you must not forget to create a strore and to provide it to the highest component
store.js
import { createStore } from "redux";
import nameReducer from "./nameReducer";
const store = createStore(nameReducer);
export default store;
index.js
import React from "react";
import ReactDOM from "react-dom";
import App from "./App";
import { Provider } from "react-redux";
import store from "./store";
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById("root")
);
Component
const AddTodo = () => {
const [todo, setTodo] = useState("");
const dispatch = useDispatch();
const handleChange = (e) => setTodo(e.target.value);
const handleSubmit = (e) => {
e.preventDefault();
dispatch(addTodoAction(todo));
}
return {
<form onSubmit={handleSubmit}>
<input type="text" onChange={handleChange} />
</form>
}
)
Actions
const addTodoAction = (text) => {
dispatch({
type: "ADD_TODO",
payload: text
})
}
Reducers
const addTodoReducer = (state, action) => {
switch(action.type) {
case "ADD_TODO":
return {
todo: action.payload,
}
default:
return state;
}
}
store
// some code for store.js
Accessing this todo from another component
const ComponentA = () => {
const {todo} = useSelector(state => state.todo);
return (
<p> {todo} </p>
)
}
Side Note:
Redux comes with too much boilerplate if you want to pass text from one component to another, just use props
I created a simple Redux app with following parts
import { useDispatch, useSelector } from "react-redux";
import { buyCakes } from "../redux/cake/cake.actions";
import { ICakeState } from "../redux/cake/cake.reducer";
import { changeEventType, formEventType } from "../models/events.model";
const CakeContainer: React.FC = () => {
const [buyCakeAmount, setBuyCakeAmount] = useState(1);
const numberOfCakes = useSelector<ICakeState, ICakeState["numberOfCakes"]>(
(state) => {
console.log(state);
return state.numberOfCakes;
}
);
const dispatch = useDispatch();
const changeHandler = (event: changeEventType) => {
setBuyCakeAmount(parseInt(event.target.value));
};
const submitHandler = (event: formEventType) => {
event.preventDefault();
dispatch(buyCakes(buyCakeAmount));
setBuyCakeAmount(1);
};
return (
<>
<h2>Number of cakes left in the shop: {numberOfCakes}</h2>
<form onSubmit={submitHandler}>
<input type="number" value={buyCakeAmount} onChange={changeHandler} />
<button type="submit">Buy Cakes</button>
</form>
</>
);
};
export default CakeContainer;
Reducer
import { BUY_CAKE, BUY_CAKES } from "./cake.types";
import { CakeActionTypes } from "./cake.types";
export interface ICakeState {
numberOfCakes: number;
}
const INITIAL_STATE: ICakeState = {
numberOfCakes: 10,
};
const cakeReducer = (
state: ICakeState = INITIAL_STATE,
action: CakeActionTypes
) => {
switch (action.type) {
case BUY_CAKE:
return {
...state,
numberOfCakes: state.numberOfCakes - 1,
};
case BUY_CAKES:
return {
...state,
numberOfCakes: state.numberOfCakes - action.payload,
};
default:
return state;
}
};
export default cakeReducer;
Actions
import { BUY_CAKE, BUY_CAKES } from "./cake.types";
import { CakeActionTypes } from "./cake.types";
import { numberOfCakes } from "../../models/cake.model";
export const buyCake = (): CakeActionTypes => ({
type: BUY_CAKE,
});
export const buyCakes = (numberOfCakes: numberOfCakes): CakeActionTypes => ({
type: BUY_CAKES,
payload: numberOfCakes,
});
Types
import { numberOfCakes } from "../../models/cake.model";
export const BUY_CAKE = "BUY_CAKE";
export const BUY_CAKES = "BUY_CAKES";
interface BuyCakeAction {
type: typeof BUY_CAKE;
}
interface BuyCakesAction {
type: typeof BUY_CAKES;
payload: numberOfCakes;
}
export type CakeActionTypes = BuyCakeAction | BuyCakesAction;
Store
import { combineReducers, createStore } from "redux";
import { composeWithDevTools } from "redux-devtools-extension";
import cakeReducer from "./cake/cake.reducer";
export default createStore(
combineReducers([cakeReducer]),
composeWithDevTools()
);
The log in the useSelector logs this object
{"0":{"numberOfCakes":10}}
Should it not just return the state without putting it in an object with the key '0'?
Also if I just return the state like this
state['0'].numberOfCakes
I get a Typescript error
Can someone please explain to me why this is happening and how to fix it, thanks.
First issue: useSelector is going to give you back your root state, so you are passing the wrong types to your generic here:
const numberOfCakes = useSelector<ICakeState, ICakeState["numberOfCakes"]>(
(state) => {
console.log(state);
return state.numberOfCakes;
}
);
Second issue is that combineReducers takes an object with the different reducers, including their names, not an array, so you need to change it to:
export default createStore(
combineReducers({cakeReducer}),
composeWithDevTools()
);
export type RootState = ReturnType<typeof rootReducer>
and then change:
const numberOfCakes = useSelector<RootState>(
(state) => {
console.log(state);
return state.cakeReducer.numberOfCakes;
}
);