I have a Shows.jsx componente that render all the shows. I have a component ProductDetails that render the information about 1 show. And I have a Search component that is a searchbar that I want that filter by name.
This is the reducer:
const initialState = {
error: false,
products: [],
product: {},
allProducts: [],
filteredProducts: [],
};
const rootReducer = (state = initialState, action) => {
const { type, payload } = action;
switch (type) {
case GET_PRODUCTS:
return {
...state,
products: action.payload,
allProducts: action.payload,
filteredProducts: action.payload,
};
case SEARCH:
console.log("search", action.payload);
return {
...state,
products: action.payload,
};
This is action:
export const search = (name) => {
return async function (dispatch) {
try {
let info = await axios.get("http://localhost:3001/products/?name=" + name); // ############ ACA VA LA RUTA PARA SOLICITAR EL GET
//let searchRes = info.filter((e) => e.name.includes(name));
console.log('info.data en action', info.data);
return dispatch({
type: "SEARCH",
payload: info.data,
});
} catch (error) {
return "No pudimos encontrar ese producto";
}
};
}
This is Search:
import React, { useState } from "react";
import "./SearchBar.css";
import { useDispatch, useSelector } from "react-redux";
import { useHistory, Redirect } from "react-router-dom";
import * as actions from "../../redux/actions";
import Swal from 'sweetalert2';
import Error_Search from './Error_Search.jpg'
function Search() {
const [suggestions, setSuggestions] = useState([]);
const [name, setName] = useState("");
const dispatch = useDispatch();
const history = useHistory();
const products = useSelector(state => state.products)
const showAlertNoEnter=()=> {
Swal.fire({
//icon:'warning',
imageUrl: Error_Search,
imageHeight: 150,
imageWidth: 200,
imageAlt: 'Hubo un error en la búsqueda.',
title: 'Buscador de Yazz',
html:'<h3>Por favor, ingresá un nombre</p>',
footer:'<p>Probá de nuevo.</p>'
}
)
}
const showAlertNoName=()=> {
Swal.fire({
//icon:'warning',
imageUrl: Error_Search,
imageHeight: 150,
imageWidth: 200,
imageAlt: 'Hubo un error en la búsqueda.',
title: 'Buscador de Yazz',
html:'<h3>Esa banda no tiene ningún show programado</p>',
footer:'<p>Probá con otra banda.</p>'
}
)
}
function handleInputChange(e) {
//setea el name con lo que va escribiendo el usuario
// e.preventDefault();
setName(e.target.value);
let filtered = products.filter(
(p) => p.name.toLowerCase().includes(e.target.value.toLowerCase())
);
setSuggestions(filtered);
}
function handleSearch(e) {
e.preventDefault();
if (!name) {
showAlertNoEnter();
return;
}
dispatch(actions.search(name));
setName("");//vacia el input
setSuggestions([]);
history.push("/shows");
//<Redirect to='/shows' />
}
function handleSuggestionClick(name) {
//history.push(`/product/${id}`);
// history.push("/shows");
dispatch(actions.search(name));
setName("");//vacia el input
setSuggestions([]);
history.push("/shows");
}
return (
<div className="searchContainer">
<div className="search_inputSuggest">
<input
id="search"
className="searchBar"
type="text"
placeholder="Buscar por nombre"
onChange={(e) => handleInputChange(e)}
value={name}
/>
<div className="search_suggestion_div">
<datalist className="suggestionsList">
{suggestions.slice(0, 10).map(s => ( //shows just 10 suggestions
<option className="suggestionsList_item" key={s.id} onClick={() => handleSuggestionClick(s.name)}>
{s.name}
</option>
))}
</datalist>
</div>
</div>
<button className="btnSearch" onClick={(e) => handleSearch(e)}>
Buscar
</button>
</div>
);
}
export default Search;
The Search is working ok when we use it on the Shows page. However, if we use it on a Detail page it doesn't filter and render all the shows.
How can I make it work from everywhere?
From what it looks like, it seems that on the first render of Search component you don't get an initial products data, therefore it will be empty when you try to filter it in the handleInputChange function, or perhaps if you've searched for something already so in the products you will have all the values that the search returned you might which is a semi not fully list of products that have the name that you've serached.
And I'm guessing that in /shows page you have a function that gets all shows and store them in the redux state and that's why you have a full search and filtering that is working properly.
So a good way to fix this search issue is on the Search Component or the initial redux state get the array of all the shows.
Related
I'm having problems filtering info consummed by an api. it is actually the nasa api.
What I Want
Look photos by
Rover's Name (input)
by camera name (input)
by date
I successfully completed the last task but the first input is not bringing the image if I look by the rovers name
Search filter
import React, { useState } from "react";
import { useDispatch } from "react-redux";
import { getPhotos } from "../../redux/actions/getPhotos";
import { ErrorAlert } from "./Alerts";
import './SecondFinder.css';
export default function SecondFinder() {
const dispatch = useDispatch();
const [term, setSearchTerm] = useState("");
const [alertERR, setAlertERR] = useState(false);
const submitHandler = (e) => {
e.preventDefault();
dispatch(getPhotos(term));
}
return (
<form onSubmit={submitHandler}>
<div className="searchPhotosByRover">
<label>Search by Rovers Name</label>
<input type="text" placeholder="Search by Rover" value={term} onChange={(e) => setSearchTerm(e.target.value)} />
<button className="btn-primary">Search</button>
{alertERR ? <ErrorAlert /> : ""}
</div>
</form>
)
}
Component:
import React from "react";
import { useSelector } from "react-redux";
import { LoadingAlert, PhotoNotFoundAlert } from "./Alerts";
import Photos from "./Photos";
export default function GridPhotos() {
const state = useSelector((state) => state.result);
const renderPhotos = () => {
if (state.loading) {
return <LoadingAlert />;
} if (state.photos.length === 0) {
return <PhotoNotFoundAlert />;
}
return state.photos.map((photo, index) => {
return <Photos key={index} photo={photo} index={index} />;
});
};
return <div className="gridPhotos">{renderPhotos()}</div>;
}
Actions:
import axios from "axios";
export const getPhotos = (date, term) => async (dispatch, getState) => {
dispatch({
type: "FETCH_PHOTOS_REQUEST"
})
try {
const response = await axios.get(`https://api.nasa.gov/mars-photos/api/v1/rovers/curiosity/photos?earth_date=${date}&name=${term}&api_key=${process.env.REACT_APP_API_KEY}&page=1`)
dispatch({
type: "FETCH_PHOTOS_SUCCESS",
payload: response.data.photos
})
} catch (error) {
dispatch({
type: "FETCH_PHOTOS_FAILURE",
error
})
}
}
Reducer
const initialState = {
photos: [],
loading: false,
error: null
}
export const getPhotosReducer = (state = initialState, action) => {
console.log("action", action)
switch (action.type) {
case "FETCH_PHOTOS_REQUEST":
return {
...state,
loading: true,
error: null
}
case "FETCH_PHOTOS_SUCCESS":
return {
...state,
loading: false,
photos: action.payload
}
case "FETCH_PHOTOS_FAILURE":
return {
...state,
loading: false,
error: action.error,
photos: []
}
default:
return state
}
}
I just want to look by the rovers name and bring a picture based on this name
enter image description here
I need to make an update method in my application. But I'm not able to filter the specific id of the task to put as a parameter of the request. If I use filter/map it returns the two indices of the array. But through my click, I want to filter the id that I clicked. How can I do this?
import Modal from 'react-modal'
import { useMutation } from 'react-query'
import ReactQuill from 'react-quill'
import { toast } from 'react-toastify'
import { useForm } from '../../hooks/useForm'
import { useTasks } from '../../hooks/useTasks'
import { api } from '../../services/api'
import { queryClient } from '../../services/queryClient'
import { modules } from '../../utils/modules'
import { ButtonContainer, CancelButton, SaveButton } from './styles'
type UpdateNoteModalProps = {
isOpen: boolean
onRequestClose: () => void
}
type UpdateNoteData = {
id: string
form?: { [key: string]: string | number }
}
export const UpdateNoteModal = ({
isOpen,
onRequestClose
}: UpdateNoteModalProps) => {
const { data } = useTasks()
const { form, handleInputChange } = useForm({
initialState: {
description: ''
}
})
const id = data?.filter((note: any) => note._id)
// .filter((note: any) => note.id === note.id)
// const id = findId.find((note: any) => note.id === note.id)
console.log({ id })
const updateNote = useMutation(
async ({ id, form }: UpdateNoteData) => {
const response = await api.put(`/task/${id}`, form)
const token = localStorage.getItem('token')
if (token) {
api.defaults.headers.common.Authorization = token
}
return response.data
},
{
onSuccess: () => {
queryClient.invalidateQueries('task')
onRequestClose()
toast.success('🦄 Sua nota foi atualizada com sucesso!', {
position: 'top-center',
autoClose: 5000
})
},
onError: () => {
toast.error('🦄 Ocorreu um erro, tente novamente mais tarde!', {
position: 'top-center',
autoClose: 5000
})
}
}
)
const handleSubmit = async () => {
// event.preventDefault()
await updateNote.mutateAsync({ id, form })
}
return (
<Modal
isOpen={isOpen}
onRequestClose={onRequestClose}
className="react-modal-content"
overlayClassName="react-modal-overlay"
>
<h2>Editar</h2>
<ReactQuill
modules={modules}
theme="snow"
className="toolbar"
onChange={handleInputChange}
/>
{/* <input
type="text"
name="description"
value={form.description}
onChange={handleInputChange}
/> */}
<ButtonContainer>
<div onClick={onRequestClose}>
<CancelButton>Cancelar</CancelButton>
</div>
<div>
<SaveButton onClick={() => handleSubmit()}>Salvar</SaveButton>
</div>
</ButtonContainer>
</Modal>
)
}
Maybe you can do something like:
const index = data.map(x => x.id).indexOf(x === id)
And then you can use that index to get the data piece you want e.g. const foundItem = data[index]
I have a little problem.
I have diferent reducers in different files using a combine reducer, but when i try to use the "different"
INITIAL STATES on these reducers it doesnt apear
For example
Product Reducer -> This is the state that i have to take
const INITIAL_STATE = {
productosInventario: [],
loading: false,
error: ''
Category Reducer -> this is the state for these reducer
const INITIAL_STATE = {
categorias: [],
categoriaActual: '',
loading: false,
error: ''
}
The idea is use both on these component:
Component:
import React, { Component } from 'react'
/* Components */
import { connect } from 'react-redux'
import { Link } from 'react-router-dom'
import CardItemInventario from '../components/inventario/CardItemInventario'
import * as ProductoActions from '../actions/ProductoActions'
import * as CategoriasActions from '../actions/CategoriasActions'
/* Styles */
import Spinner from '../components/Spinner'
import Fatal from '../components/Fatal'
import '../assets/styles/Containers/Inventario.scss'
class Inventario extends Component {
async componentDidMount() {
await this.props.traerTodosLosProductos();
}
handleChangeCategoria = (e) => {
this.props.cambioCategoriaInventario(e.target.value)
this.props.traerProductosPorCategoriaInventario(e.target.value)
}
/* Mapea todas las categorias disponibles en base de datos */
traerCategoriasInventario = () => this.props.categoriasInventario.map(category => {
let categori = category.categoria
return (
<option
value={categori}
>
{categori}
</option>
)
})
ponerContenido = () => {
if (this.props.loading) {
return (
<Spinner />
)
}
if (this.props.error) {
return (
<Fatal
error={this.props.error} />
)
}
return (
<>
<div className="button-add__cont">
<h1 className="button-add__title">
Inventario
</h1>
<Link to='/agregarinventario' className="button-add__cont--link">
Agregar a Inventario
</Link>
</div>
<select
name="categoriaSelect"
id=""
onChange={this.handleChangeCategoria}
className="selector-categoria"
>
<option value='' defaultValue> - Categoria -</option>
{this.traerCategoriasInventario()}
</select>
<div className="inventario-cont">
{this.imprimirProductos()}
</div>
</>
)
}
imprimirProductos = () => this.props.productosInventario.map(Productos =>
<CardItemInventario
nombre={Productos.nombre}
marca={Productos.marca}
cantidad={Productos.cantidad}
distribuidor={Productos.distribuidor}
precio={Productos.precio}
/>
)
render() {
console.log(this.props)
return (
<>
{this.ponerContenido()}
</>
)
}
}
const mapStateToProps = (reducers) => {
return (
reducers.ProductoReducer,
reducers.CategoriasReducer
)
}
const mapDispatchToProps = {
...ProductoActions,
...CategoriasActions
}
export default connect(mapStateToProps, mapDispatchToProps)(Inventario);
actions ->
productoActions:
import axios from 'axios'
import {
TRAER_TODOS_LOS_PRODUCTOS
} from '../types/ProductoTypes'
import { host_name, port_redux } from '../../../config'
import { CARGANDO, ERROR } from '../types/GlobalTypes'
const axiosConf = {
baseURL: `http://${host_name}:${port_redux}`
}
export const traerTodosLosProductos = () => async (dispatch) => {
dispatch({
type: CARGANDO
})
try {
const res = await axios.get(`/api/productos/get/listar`, axiosConf)
dispatch({
type: TRAER_TODOS_LOS_PRODUCTOS,
payload: res.data
})
} catch (error) {
console.log("Error: " + error)
dispatch({
type: ERROR,
payload: error.message
})
}
}
export const traerProductosPorCategoriaInventario = (categoria) => async (dispatch) => {
try {
const res = await axios.get(`/api/cotizacion/get/productosporcategoria/${categoria}`, axiosConf)
dispatch({
type: TRAER_TODOS_LOS_PRODUCTOS,
payload: res.data
})
} catch (error) {
console.log("Error: " + error)
dispatch({
type: ERROR,
payload: error.message
})
}
}
categoryActions_ >
import axios from 'axios'
import { host_name, port_redux } from '../../../config'
import { CARGANDO, ERROR } from '../types/GlobalTypes'
import {
LISTAR_CATEGORIAS,
CATEGORIA_ACTUAL
} from '../types/CategoriasTypes'
const axiosConf = {
baseURL: `http://${host_name}:${port_redux}`
}
export const traerCategoriasInventario = () => (dispatch) => {
const res = axios.get(`/api/categorias/get/listar`, axiosConf)
console.log(res)
dispatch({
type: LISTAR_CATEGORIAS,
payload: res.data.data
})
}
export const cambioCategoriaInventario = (categoria) => async (dispatch) => {
try {
dispatch({
type: CATEGORIA_ACTUAL,
payload: categoria
})
} catch (error) {
console.log("Error: " + error)
dispatch({
type: ERROR,
payload: error.message
})
}
}
const mapStateToProps = (reducers) => {
return (
reducers.ProductoReducer,
reducers.CategoriasReducer
)
}
It seems like you are having some confusion between state and reducer. The state is the object which contains all of your data. It is just a plain javascript object. The reducer is a function which takes the state object and an action and returns a new state object.
Your setup should look something like this:
const productoReducer = (state = INITIAL_PRODUCTOS, action ) => {
switch ( action.type ) {
case 'TRAER_TODOS_LOS_PRODUCTOS':
/* ... code here ... */
default:
return state;
}
}
const categoriasReducer = (state = INITIAL_CATEGORIAS, action ) => {
switch ( action.type ) {
case 'LISTAR_CATEGORIAS':
/* ... code here ... */
default:
return state;
}
}
export const reducer = combineReducers({
producto: productoReducer,
categorias: categoriasReducer,
})
Here we have two separate reducers for categories and for products, and each gets a separate initial state. We use combineReducers to put them together so now the combined state has properties producto and categorias.
Your component Inventario needs to access a bunch of values from state: categoriasInventario, productosInventario, loading, and error. Rather than passing the state into the component, we use mapStateToProps to extract these values and pass them as props.
const mapStateToProps = (state) => {
return {
categoriasInventario: state.categorias.categorias,
productosInventario: state.productos.productosInventario,
loading: state.categorias.loading || state.productos.loading,
error: state.categorias.error || state.productos.error,
}
}
I've been trying to create this search app where I can display the items in a table and delete items using react redux. However, on the initial load, the app shows a table but there is no data in the table. It's an empty table. If i search for another movie name which have more than one movie for that search term, then 2 tables would be shown but I want to show everything on the same table itself. The delete button is not working as well. Is there something wrong with my action and reducer files?
Action.js
import {
FETCH_MOVIE_PENDING,
FETCH_MOVIE_SUCCESS,
FETCH_MOVIE_ERROR,
DELETE_MOVIE
} from "./types";
const fetchMoviePendig = () => ({
type: FETCH_MOVIE_PENDING
});
const fetchMovieSuccess = json => ({
type: FETCH_MOVIE_SUCCESS,
payload: json
});
const fetchMovieError = error => ({
type: FETCH_MOVIE_ERROR,
payload: error
});
export const fetchMovie = name => {
return async dispatch => {
dispatch(fetchMoviePendig());
try {
const url = `https://jsonmock.hackerrank.com/api/movies/search/?Title=${name}`;
const response = await fetch(url);
const result = await response.json(response);
console.log(result);
dispatch(fetchMovieSuccess(result.data));
} catch (error) {
dispatch(fetchMovieError(error));
}
};
};
export const deleteEvent = id => async dispatch => {
try {
dispatch({
type: DELETE_MOVIE,
payload: id
});
} catch (err) {
console.log(err);
}
};
Reducer
import {
FETCH_MOVIE_PENDING,
FETCH_MOVIE_SUCCESS,
FETCH_MOVIE_ERROR,
DELETE_MOVIE
} from "../action/types";
const initialState = {
data: [],
loading: false,
error: ""
};
const moviesReducer = (state = initialState, action) => {
switch (action.type) {
case FETCH_MOVIE_PENDING:
return {
...state,
loading: true
};
case FETCH_MOVIE_SUCCESS:
return {
...state,
loading: false,
data: [...state.data, action.payload]
};
case FETCH_MOVIE_ERROR:
return {
...state,
loading: false,
error: action.payload
};
case DELETE_MOVIE:
return {
...state,
data: state.data.filter(movie => movie.id !== action.payload)
};
default:
return state;
}
};
export default moviesReducer;
App.js
import React, { Component } from "react";
import { connect } from "react-redux";
import { fetchMovie } from "./action/movieActions";
import Input from "./components/Input";
import MovieTable from "./components/MovieTable";
class App extends Component {
state = {
searchInput: "The Rain"
};
componentDidMount() {
this.props.getMovieList(this.state.searchInput);
}
_getMovie = () => {
this.props.getMovieList(this.state.searchInput);
};
_onChangeHandler = e => {
this.setState({
searchInput: e.target.value
});
console.log(this.state.searchInput);
};
render() {
const { data, loading } = this.props.movies;
return (
<div className="center">
<div>
<h2 className="center white-text">Movie Search</h2>
</div>
<div className="container">
<Input
value={this.state.searchInput}
onChange={this._onChangeHandler}
onClick={this._getMovie}
/>
<div className="row">
{loading ? (
<p>Loading</p>
) : (
data.map(item => (
<MovieTable
key={item.imdbID}
year={item.Year}
name={item.Title}
movieId={item.imdbId}
/>
))
)}
</div>
</div>
</div>
);
}
}
const mapStateToProps = state => {
return {
movies: state.movies
};
};
const mapDispatchToProps = dispatch => {
return {
getMovieList: name => dispatch(fetchMovie(name))
};
};
export default connect(
mapStateToProps,
mapDispatchToProps
)(App);
Hello please take a look at the sandbox : https://codesandbox.io/s/prod-wind-4hgq2?file=/src/App.js
I have edited
<MovieTable
data={data.map(d => ({
year: d.Year,
name: d.Title,
movieId: d.imdbId
}))}
/>
and
case FETCH_MOVIE_SUCCESS:
return {
...state,
loading: false,
data: action.payload
};
And ... Currently the delete button has no event, that's why it can't work
I see data having the following pattern:
Object {page: 1, per_page: 10, total: 1, total_pages: 1, data: Array[1]}
page: 1
per_page: 10
total: 1
total_pages: 1
data: Array[1]
0: Object
Title: "Sin in the Rain"
Year: 2006
imdbID: "tt1072449"
And you are accessing wrong properties in the component render logic, can you fix that.
Duplicate table is created the way you have written the logic.
Pass the data to MovieTable component and let it render and create the table
and fill it.
In reducer (FETCH_MOVIE_SUCCESS) you need don't need to append data you have to
replace or use the current movie data only.
I am kind of new in React. I have been able to read real-time data in json format from Firebase in a Web App, but, when I try to write info from the UI to the database I just get code type objects and not the written data. Worst than that, I am messing something else in the code because every time user writes a character, the app updates the value and sends the info to the database. I would deeply appreciate any indication for the right path and thanks in advance.
This is the main page code where the problem occurs:
import React, { Component } from "react";
import {
IonPage,
IonContent,
IonHeader,
IonToolbar,
IonButton,
IonInput,
} from "#ionic/react";
import TabContainer from "../components/TabContainer";
import firebase from '#firebase/app'
const writeUserData =(userInfo)=> {
firebase.database().ref('reservas').push({
userInfo
}).catch((error)=>{
//error callback
console.log('error ' , error)
})
}
class HomePage extends Component {
constructor() {
super();
this.state = {
onListPage: true,
username:[],
reservas:[]
};
}
componentWillUpdate () {
const readUsersData = ()=> {
const nameRef = firebase.database().ref('reservas')
nameRef.on('value', (snapshot)=> {
const state = snapshot.val()
this.state.reservas = state
})
}
readUsersData()
}
_changedTabs = e => {
if (e.currentTarget.attributes.tab.value === "tab1") {
this.setState(() => ({ onListPage: true }));
} else {
this.setState(() => ({ onListPage: false }));
}
}
render() {
const myData = this.state.reservas
const pushData = (username) => {
this.setState({username })
}
const user = this.state.username
return (
<IonPage>
<IonHeader>
<IonToolbar color="primary">
</IonToolbar>
</IonHeader>
<IonContent>
<TabContainer
history={this.props.history}
changedTabs={e => this._changedTabs(e)}
addItem={this._addItem}
showAddItemModal={this.state.showAddItemModal}
/>
</IonContent>
<IonContent> <li> {JSON.stringify({myData})} </li></IonContent>
<h1>Introduzca la reserva a confirmar:</h1>
<IonContent>
<input
onChange= {pushData}
>
</input>
<IonButton
onClick={writeUserData( JSON.stringify(user) )}
> Escribir </IonButton>
<IonButton
onClick={
send => pushData(send)
}
> Enviar </IonButton>
<IonInput/>
</IonContent>
</IonPage>
);
}
}
export default HomePage
The entire repository is in: https://github.com/progamandoconro/ReactNativeApps/tree/master/Firebase/WebAdmin/src. Thank you.
So 2 problems here...
The onChange for an input results in an event. You need to extract the value from the event.
You are pushing onChange instead of when the user is done writing. So use onBlur instead or a button press. This will only send when the leave focus on the input. That means you should add a state value to store the value as they type before sending. See below:
import React, { Component } from "react";
import {
IonPage,
IonContent,
IonHeader,
IonToolbar,
IonButton,
IonInput,
} from "#ionic/react";
import TabContainer from "../components/TabContainer";
import firebase from '#firebase/app'
const writeUserData =(userInfo)=> {
firebase.database().ref('reservas').push({
userInfo
}).catch((error)=>{
//error callback
console.log('error ' , error)
})
}
class HomePage extends Component {
constructor() {
super();
this.state = {
onListPage: true,
username:[],
reservas:[],
value: '',
};
}
componentWillUpdate () {
const readUsersData = ()=> {
const nameRef = firebase.database().ref('reservas')
nameRef.on('value', (snapshot)=> {
const state = snapshot.val()
this.state.reservas = state
})
}
readUsersData()
}
_changedTabs = e => {
if (e.currentTarget.attributes.tab.value === "tab1") {
this.setState(() => ({ onListPage: true }));
} else {
this.setState(() => ({ onListPage: false }));
}
}
render() {
const myData = this.state.reservas
const pushData = (username) => {
this.setState({username })
}
const user = this.state.username
return (
<IonPage>
<IonHeader>
<IonToolbar color="primary">
</IonToolbar>
</IonHeader>
<IonContent>
<TabContainer
history={this.props.history}
changedTabs={e => this._changedTabs(e)}
addItem={this._addItem}
showAddItemModal={this.state.showAddItemModal}
/>
</IonContent>
<IonContent> <li> {JSON.stringify({myData})} </li></IonContent>
<h1>Introduzca la reserva a confirmar:</h1>
<IonContent>
<input
onChange={e=>this.setState({value: e.target.value})}
value={this.state.value}
onBlur={()=>pushData(this.state.value)}
>
</input>
<IonButton
onClick={writeUserData( JSON.stringify(user) )}
> Escribir </IonButton>
<IonButton
onClick={
send => pushData(send)
}
> Enviar </IonButton>
<IonInput/>
</IonContent>
</IonPage>
);
}
}
export default HomePage