I have an application where user can search data depending by his input. In my application i try to use reselect.
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;
In my store i have an array of persons like this:
export const persons = [
{
name:"jack",
age: 2
},
{
name:"Jim",
age: 14
},
{
name:"July",
age: 92
},
{
name:"Bill",
age: 1
},
{
name:"Carl",
age: 72
},
]
Now, when user search something, in the list appears the results according to the name which was searched by the user.
Question: Is the reselect usefull (protects from to many re-renders) in my case or not? Or using useSelector, in the case above is enought?
I don't think reslect here will be necessary. You can use useMemo to achieve the same result.
import React, { useEffect, useMemo } from "react";
import { useDispatch, useSelector } from "react-redux";
import { searchPersonAction } from "./store";
const Search = () => {
const dispatch = useDispatch();
const persons = useSelector((s) => s.persons);
const [query, updateQuery] = useState('');
const searchedPersons = useMemo(() => persons.filter(p => p.name.includes(query)), [query]);
const search = (e) => {
updateQuery(e.target.value);
};
return (
<div>
<input onChange={search} placeholder="search" />
<ul>
{searchedPersons.map((p) => (
<li key={p.name}>{p.name}</li>
))}
</ul>
</div>
);
};
export default Search;
reselect will be useful if you get array or object from store for example:
store
state = {
persons: [
{
name:"Jack",
age: 2
},
{
name:"Jane",
age: 14
},
]
}
if you used selector from react-redux and if your typed 'J' in the search field and used searchPersonAction action, then it will change the array of persons in the store, but array stayed the same.
state = {
persons: [
{
name:"Jack",
age: 2
},
{
name:"Jane",
age: 14
},
]
}
then you receive rerender regardless of whether the data in the array has changed or not.
But if you use reselect and when your typed 'Ja' in the search field, it will be the same array, then you will not get a repeated render, because reselect will memoize data
Related
I have been experimenting with the Redux useDispatch hook. I made this super simple app of writing down notes as a todo list. Until now useDispatch does not seem to work for me and I get the following error:
Module build failed (from ./node_modules/babel-loader/lib/index.js):
SyntaxError: C:\Users\Louaye Mandari\Desktop\redux-todo-list\redux-todo-list\src\App.js: Unexpected token, expected "," (17:26)
15 | const handleClicked = (e) =>{
16 | e.preventDefault();
> 17 | dispatch(addTask({text.current.value}))
| ^
18 | }
19 | return (
20 | <div className="App">
App.js
import { useSelector,useDispatch } from 'react-redux';
import taskSlice from './redux/taskSlice';
import './App.css';
import { useState,useRef } from 'react';
import { addTask } from './redux/taskSlice';
function App() {
const data = useSelector(state=>state.tasks)
const dispatch = useDispatch()
const text = useRef()
const handleClicked = (e) =>{
e.preventDefault();
dispatch(addTask({text.current.value}))
}
return (
<div className="App">
<button onClick={handleClicked}>
Enter the new task
</button>
<input type='text' ref = {text} placeholder='write the task here' />
<label>{data.task[data.task.length - 1]} </label>
</div>
);
}
export default App
taskSlice.js:
import { createSlice } from '#reduxjs/toolkit';
export const taskSlice = createSlice({
name: 'tasks',
initialState: {
task: ['hi'],
},
reducers: {
addTask: (state, action) => {
state.tasks.push(action.payload);
},
},
});
export const { addTask } = taskSlice.actions;
export default taskSlice.reducer;
store.js:
import { configureStore } from "#reduxjs/toolkit";
import taskReducer from "./taskSlice";
export const store = configureStore({
reducer: {
tasks: taskReducer,
},
});
I am trying to access addtask in reducers in taskslice.js. The addtask should turn the new state and add a new element to the initial state of task array.
There might be multiple issues, but the first comes from this line
dispatch(addTask({text.current.value}))
The {text.current.value} is incorrect. It looks like you are trying to do object destructuring. Think about what should the addTask take in as an input: a string? An object? What kind of object?
To focus on the destructuring error, what you are trying to do is kind of like this:
const animal = { name: { first: "Rauli", last: "Cat" }, age: 7 }
console.log({animal.name.first})
This gives you the error Uncaught SyntaxError: Unexpected token '.'. In my example assuming I want to just log the name, I probably want to do something like this instead:
const animal = { name: { first: "Rauli", last: "Cat" }, age: 7 }
console.log(animal.name.first)
I am trying redux-thunk for the first time Hence working on a simple project the thunk uses the API and displays the data on the screen but the API is returning a JSON object ,to display the titles on the screen I need to use the .map() function to map through the object, but the object doesn't allow us to use map() function so I need to convert the JSON data to an array and the use .map() function to achieve the desired result but I don't know how to convert the JSON data to an array
I tried different approaches to deal with this but nothing seems to work for me Here is what I need
const codeReturnedFromJSONRequest ={data:{0:somedata}} //example JOSN data
what I want my code to look like :
const requiredData=[{data:{0:somedata}}] //I want the required data to be an array so that I can use .map()
If you want my actual code here it is
//ApiSlice
import { createSlice, createAsyncThunk } from "#reduxjs/toolkit";
export const getPosts = createAsyncThunk("posts/getPosts", async () => {
return fetch("https://api.jikan.moe/v4/anime?q=naruto&sfw").then((response) =>
response.json()
);
});
const postSlice = createSlice({
name: "posts",
initialState: {
posts: [],
loading: false,
},
extraReducers: {
[getPosts.pending]: (state, action) => {
state.loading = true;
},
[getPosts.fulfilled]: (state, action) => {
state.loading = false;
state.posts = action.payload;
},
[getPosts.rejected]: (state, action) => {
state.loading = false;
},
},
});
export default postSlice.reducer
//store
import { configureStore } from "#reduxjs/toolkit";
import postReducer from "./anime";
export const store =configureStore({
reducer: {
post:postReducer
}
})
//Api data
import React from "react";
import { useEffect } from "react";
import { useDispatch, useSelector } from "react-redux";
import { getPosts } from "../store/anime";
function Apidata() {
const { posts, loading } = useSelector((state) => state.post);
const dispatch = useDispatch();
useEffect(() => {
dispatch(getPosts());
}, []);
console.log(posts.data)
return (
<div>
{posts.map((item) => (
<h2>{item.data}</h2>
))}
</div>
);
}
export default Apidata;
// App.js
import { Fragment, useState } from "react";
import Apidata from "./components/Apidata";
function App() {
return (
<Fragment>
<Apidata/>
</Fragment>
)
}
export default App;
if you want create an array just wrap the response.json() in an array like that:
export const getPosts = createAsyncThunk("posts/getPosts", async () => {
return fetch("https://api.jikan.moe/v4/anime?q=naruto&sfw")
.then(response=>response.json())
.then((response) =>[response]
);
});
BUT I don't think it is a best practice. Ask to whom create the backend and get explanations!.
Hope the best for you,
Mauro
This peace of code resolved my issue
const myObj = {0 : {mal_id: 20, url: 'https://myanimelist.net/anime/20/Naruto', images: 'test', trailer: 'test', approved: true}, 1: {mal_id: 20, url: 'https://myanimelist.net/anime/20/Naruto', images: 'test', trailer: 'test', approved: true}};
const myArr = [];
for (const key in myObj) {
const arrObj = myObj[key];
arrObj['id'] = key;
myArr.push(arrObj)
}
console.log(myArr);
Click here to see the reddit post:
Solution reddit link
I am using react-redux to store a state modeData which is an array of objects. Using react-hotkeys-hook, when the user presses ctrl+l, a function runs which updates the state. In that same function, when I console.log the same state, it does not reflect the changes.
Here's the code I'm using:
import { useSelector, useDispatch } from "react-redux";
import { modeDataIncrement } from "./redux/modeSlice";
import { useHotkeys } from "react-hotkeys-hook";
import { useEffect } from "react";
//..
const modeData = useSelector((state) => state.mode.modeData); //array of objects.
const handler = (id) => {
const isActive = modeData.find((x) => x.id === id).active;
console.log(modeData); // does not show the updated state
dispatch(modeDataIncrement(id));
};
useHotkeys("ctrl+l", () => handler("Clone"));
useEffect(() => {
console.log(modeData); //working fine!
}, [modeData]);
modeSlice.js:
import { createSlice } from "#reduxjs/toolkit";
export const modeSlice = createSlice({
name: "mode",
initialState: {
modeData: [{ id: "Clone", active: false }],
},
reducers: {
modeDataIncrement: (state, action) => {
state.modeData.find(
(e) => e.id === action.payload && (e.active = !e.active)
);
},
},
});
export const { modeDataIncrement } = modeSlice.actions;
export default modeSlice.reducer;
Any thoughts on what I'm doing wrong?
Thanks!
Your code needs to use the latest version of modeData, but based on the source code of useHotkeys, it will memoize the function, and not update it automatically. So you're always using the version of the callback that existed on the first render.
To fix this, you need to pass a dependency array in to useHotkeys, so it can break the memoization:
const handler = (id) => {
const isActive = modeData.find((x) => x.id === id).active;
console.log(modeData);
dispatch(modeDataIncrement(id));
};
useHotkeys("ctrl+l", () => handler("Clone"), [modeData]);
I'm quite new to React Hooks/Context so I'd appreciate some help. Please don' t jump on me with your sharp teeth. I Checked other solutions and some ways i've done this before but can't seem to get it here with the 'pick from the list' way.
SUMMARY
I need to get the municipios list of names inside of my const 'allMunicipios'(array of objects) inside of my Search.js and then display a card with some data from the chosen municipio.
TASK
Get the data from eltiempo-net REST API.
Use Combobox async element from Elastic UI to choose from list of municipios.
Display Card (from elastic UI too) with some info of chosen municipio.
It has to be done with function components / hooks. No classes.
I'd please appreciate any help.
WHAT I'VE DONE
I've created my reducer, context and types files in a context folder to fecth all data with those and then access data from the component.
I've created my Search.js file. Then imported Search.js in App.js.
I've accesed the REST API and now have it in my Search.js
PROBLEM
Somehow I'm not beeing able to iterate through the data i got.
Basically i need to push the municipios.NOMBRE from api to the array const allMunicipios in my search.js component. But when i console log it it gives me undefined. Can;t figure out why.
I'll share down here the relevant code/components. Thanks a lot for whoever takes the time.
municipiosReducer.js
import {
SEARCH_MUNICIPIOS,
CLEAR_MUNICIPIOS,
GET_MUNICIPIO,
GET_WEATHER,
} from "./types";
export default (state, action) => {
switch (action.type) {
case SEARCH_MUNICIPIOS:
return {
...state,
municipios: action.payload,
};
case GET_MUNICIPIO:
return {
...state,
municipio: action.payload,
};
case CLEAR_MUNICIPIOS:
return {
...state,
municipios: [],
};
case GET_WEATHER: {
return {
...state,
weather: action.payload,
};
}
default:
return state;
}
};
municipiosContext.js
import { createContext } from "react";
const municipiosContext = createContext();
export default municipiosContext;
MunicipiosState.js
import React, { createContext, useReducer, Component } from "react";
import axios from "axios";
import MunicipiosContext from "./municipiosContext";
import MunicipiosReducer from "./municipiosReducer";
import {
SEARCH_MUNICIPIOS,
CLEAR_MUNICIPIOS,
GET_MUNICIPIO,
GET_WEATHER,
} from "./types";
const MunicipiosState = (props) => {
const initialState = {
municipios: [],
municipio: {},
};
const [state, dispatch] = useReducer(MunicipiosReducer, initialState);
//Search municipios
//In arrow functions 'async' goes before the parameter.
const searchMunicipios = async () => {
const res = await axios.get(
`https://www.el-tiempo.net/api/json/v2/provincias/08/municipios`
// 08 means barcelona province. This should give me the list of all its municipios
);
dispatch({
type: SEARCH_MUNICIPIOS,
payload: res.data.municipios,
});
};
//Get Municipio
const getMunicipio = async (municipio) => {
const res = await axios.get(
`https://www.el-tiempo.net/api/json/v2/provincias/08/municipios/${municipio.CODIGOINE}`
//CODIGOINE is in this REST API kind of the ID for each municipio.
//I intent to use this later to get the weather conditions from each municipio.
);
dispatch({ type: GET_MUNICIPIO, payload: res.municipio });
};
const dataMunicipiosArray = [searchMunicipios];
//Clear Municipios
const clearMunicipios = () => {
dispatch({ type: CLEAR_MUNICIPIOS });
};
return (
<MunicipiosContext.Provider
value={{
municipios: state.municipios,
municipio: state.municipio,
searchMunicipios,
getMunicipio,
clearMunicipios,
dataMunicipiosArray,
}}
>
{props.children}
</MunicipiosContext.Provider>
);
};
export default MunicipiosState;
Search.js
import "#elastic/eui/dist/eui_theme_light.css";
import "#babel/polyfill";
import MunicipiosContext from "../contexts/municipiosContext";
import MunicipiosState from "../contexts/MunicipiosState";
import { EuiComboBox, EuiText } from "#elastic/eui";
import React, { useState, useEffect, useCallback, useContext } from "react";
const Search = () => {
const municipiosContext = useContext(MunicipiosContext);
const { searchMunicipios, municipios } = MunicipiosState;
useEffect(() => {
return municipiosContext.searchMunicipios();
}, []);
const municipiosFromContext = municipiosContext.municipios;
const bringOneMunicipio = municipiosContext.municipios[0];
let municipiosNames = municipiosFromContext.map((municipio) => {
return { label: `${municipio.NOMBRE}` };
});
console.log(`municipiosFromContext`, municipiosFromContext);
console.log(`const bringOneMunicipio:`, bringOneMunicipio);
console.log(`municipiosNames:`, municipiosNames);
const allMunicipios = [
{ label: "santcugat" },
{ label: "BARCELONETA" },
{ label: "BARCE" },
];
const [selectedOptions, setSelected] = useState([]);
const [isLoading, setLoading] = useState(false);
const [options, setOptions] = useState([]);
let searchTimeout;
const onChange = (selectedOptions) => {
setSelected(selectedOptions);
};
// combo-box
const onSearchChange = useCallback((searchValue) => {
setLoading(true);
setOptions([]);
clearTimeout(searchTimeout);
// eslint-disable-next-line react-hooks/exhaustive-deps
searchTimeout = setTimeout(() => {
// Simulate a remotely-executed search.
setLoading(false);
setOptions(
municipiosNames.filter((option) =>
option.label.toLowerCase().includes(searchValue.toLowerCase())
)
);
}, 1200);
}, []);
useEffect(() => {
// Simulate initial load.
onSearchChange("");
}, [onSearchChange]);
return (
<div>
<EuiComboBox
placeholder="Search asynchronously"
async
options={options}
selectedOptions={selectedOptions}
isLoading={isLoading}
onChange={onChange}
onSearchChange={onSearchChange}
/>
<button>Lista de municipios</button>
</div>
);
};
export default Search;
also the
Home.js
import React, { useState } from "react";
import { EuiComboBox, EuiText } from "#elastic/eui";
// import { DisplayToggles } from "../form_controls/display_toggles";
import "#babel/polyfill";
import "#elastic/eui/dist/eui_theme_light.css";
import Search from "./Search";
import MunicipioCard from "./MunicipioCard";
const Home = () => {
return (
<div>
<EuiText grow={false}>
<h1>Clima en la provincia de Barcelona</h1>
<h2>Por favor seleccione un municipio</h2>
</EuiText>
<Search />
<MunicipioCard />
</div>
);
};
export default Home;
App.js
import "#babel/polyfill";
import "#elastic/eui/dist/eui_theme_light.css";
import { EuiText } from "#elastic/eui";
import React from "react";
import Home from "./components/Home";
import MunicipiosState from "./contexts/MunicipiosState";
import "./App.css";
function App() {
return (
<MunicipiosState>
<div className="App">
<EuiText>
<h1>App Component h1</h1>
</EuiText>
<Home />
</div>
</MunicipiosState>
);
}
export default App;
You are using forEach and assigning the returned value to a variable, however forEach doesn't return anything. You should instead use map
let municipiosNames = municipiosFromContext.map((municipio) => {
return `label: ${municipio.NOMBRE}`;
});
As per your comment:
you data is loaded asynchronously, so it won't be available on first render and since functional components depend on closures, you onSearchChange function takes the value from the closure at the time of creation and even if you have a setTimeout within it the updated value won't reflect
The solution here is to add municipiosFromContext as a dependency to useEffect
const onSearchChange = useCallback((searchValue) => {
setLoading(true);
setOptions([]);
clearTimeout(searchTimeout);
// eslint-disable-next-line react-hooks/exhaustive-deps
searchTimeout = setTimeout(() => {
// Simulate a remotely-executed search.
setLoading(false);
setOptions(
municipiosNames.filter((option) =>
option.label.toLowerCase().includes(searchValue.toLowerCase())
)
);
}, 1200);
}, [municipiosFromContext]);
useEffect(() => {
// Simulate initial load.
onSearchChange("");
}, [onSearchChange]);
I am trying to learn how to use API's in react. I am making a search input for country names using the Rest countires API. I am getting data from https://restcountries.eu/rest/v2/all but I do not know how to handle this data as I can not use map on an object.
import axios from "axios";
import React, { useEffect, useState } from "react";
const App = () => {
const [countries, setCountries] = useState([]);
const [searchName, setSearchName] = useState("");
useEffect(() => {
axios.get("https://restcountries.eu/rest/v2/all").then(response => {
setCountries(response.data);
});
}, []);
const handleSearch = event => {
setSearchName(event.target.value);
};
return (
<div>
<div>
find countries <input onChange={handleSearch} />
</div>
<div></div>
</div>
);
};
export default App;
Expected to list countries after typing such as : sw = Botswana, Swaziland, Sweden ...
From the question it seems like, these are requirements of your app -
1
you need to search by country name
As you type in, list of countries matching the search should be displayed.
I created this sandbox with the code you provided - https://codesandbox.io/embed/58115762-rest-countries-o638k.
It shows a pair of country name and its capital as you enter input in the search box.
This is how I changed your code:
You need to search countries? - Use search API with country name as value of text input - searchName
https://restcountries.eu/rest/v2/name/${searchName}
To display the output with countries matching your search keyword - map over countries and get appropriate keys. Pass those keys as props to your newly created Country component.
Note, I did not need to change how you handled the JSON response. The searchName and countries are the only two state variables used to render the UI.
you will need to render countries after fetching from ajax request as like :
import axios from "axios";
import React, { useEffect, useState } from "react";
const App = () => {
const [countries, setCountries] = useState([]);
const [searchName, setSearchName] = useState("");
useEffect(() => {
axios.get("https://restcountries.eu/rest/v2/all").then(response => {
setCountries(response.data);
});
}, []);
const handleSearch = event => {
setSearchName(event.target.value);
};
return (
<div>
<div>
find countries <input onChange={handleSearch} />
</div>
<ul>
{(countries.length<=0)?"":
countries.map(country=> <li>country.name</li> )
}
</ul>
</div>
);
};
export default App;
I think this is what you are looking for.
If you have got questions, dont hesitate to ask :)
import axios from "axios";
import React, { useEffect, useState } from "react";
const App = () => {
const [countries, setCountries] = useState([]);
const [searchName, setSearchName] = useState("");
useEffect(() => {
axios.get("https://restcountries.eu/rest/v2/all").then(response => {
setCountries(response.data);
});
}, []);
const handleSearch = event => {
let str = event.target.value;
let filteredCountries = countries.filter((country) => country.name.toLowerCase().includes(str.toLowerCase()));
setCountries(filteredCountries);
setSearchName(str);
};
return (
<div>
<div>
find countries <input onChange={handleSearch} />
</div>
<ul> {(countries.length <= 0) ? "" : countries.map(country => <li>{country.name}</li>) } </ul>
</div>
);
};
export default App;
data =[your array];
countryList = data.map(data=>data.name)
console.log(countryList)