i can't move my code to child component so how can i solve this problem. so that i can use my api data to my combobox
async getData() {
const PROXY_URL = 'https://cors-anywhere.herokuapp.com/';
const URL = 'my-api';
const res = await axios({
method: 'post', // i get data from post response
url: PROXY_URL+URL,
data: {
id_user : this.props.user.id_user
}
})
const {data} = await res;
this.setState({ user : data.data})
}
componentDidMount = () => {
this.getData()
}
and i send my state to my combobox in child component
<ComboBox
name="pic"
label="Default PIC"
placeholder=""
refs={register({ required: true })}
error={errors.PIC}
message=""
labelFontWeight="400"
datas={this.state.user}
></ComboBox>
combobox code :
right now I just want to be able to console my index data
let ComboBox = props => {
useEffect(() => {
for (let i = 0; i < props.datas.length; i++) {
console.log(i) //this can use if using hard props or manual data
props.datas[i].selected = false;
props.datas[i].show = true;
}
setDatas(props.datas);
document.addEventListener('click', e => {
try {
if (!refDivComboBox.current.contains(e.target)) {
setIsOpen(false);
}
} catch (error) {}
});
unSelectedComboBox();
}, []);
export default ComboBox;
I think you are missing the props.datas dependency in your ComboBox component.
let ComboBox = props => {
useEffect(() => {
for (let i = 0; i < props.datas.length; i++) {
console.log(i) //this can use if using hard props or manual data
props.datas[i].selected = false;
props.datas[i].show = true;
}
setDatas(props.datas);
document.addEventListener('click', e => {
try {
if (!refDivComboBox.current.contains(e.target)) {
setIsOpen(false);
}
} catch (error) {}
});
unSelectedComboBox();
}, [props.datas]); // THIS IS THE DEPENDENCY ARRAY, try adding props.datas here
export default ComboBox;
Here is a brief explanation of useEffect.
Used as componentDidMount():
useEffect(() => {}, [])
Used as componentDidUpdate() (triggers after props.something changes):
useEffect(() => {}, [props.something])
Used as componenWillUnmount():
useEffect(() => {
return () => { //Unmount }
}, [])
This, of course, is a really simple explanation, and this can be used much better when properly learned. Take a look at some tutorials utilizing useState, try to find in particular migrations from this.state to useState - those might help you wrap your head around useState
Related
I'm trying to populate my ToDo list with all the tasks I get from my database with an API call. Unfortunately, nothing is showing up. My API call is working because the console.log(response.data) returns the 3 tasks in my database, but my view is not updating with the data that I got from my call. I get no errors.
import axios from "../axios";
import {useState, useEffect } from "react";
import {ToDoFull,ToDoInner,Id,Title,Description} from "./StyledComponents.js";
const Tasks = () => {
const [tasks, setTasks] = useState([]);
useEffect(() => {
const fetchAllItems = async () => {
try {
const response = await axios.get("/tasks/all-tasks");
if (response.data !== "") {
console.log(response.data); //Prints out my three objects in an array in my console. works great
let objects = response.data.map(JSON.stringify);
let uniqueSet = new Set(objects);
let uniqueArray = Array.from(uniqueSet).map(JSON.parse);
setTasks(uniqueArray);
} else {
}
} catch (err) {
console.log(err);
}
};
fetchAllItems();
return () => {
setItems([]);
};
}, []);
return (
<>
<ToDoFull>
{tasks.map((task) => {
<ToDoInner>
<ID>{task.taskid}</Title>
<Title>{task.title}</Title>
<Description>{task.description}</Description>
</ToDoInner>;
})}
</ToDoFull>
</>
);
};
export default Tasks;
Please provide return inside tasks.map()
<ToDoFull>
{tasks.map((task) => {
return (<ToDoInner>
<ID>{task.taskid}</Title>
<Title>{task.title}</Title>
<Description>{task.description}</Description>
</ToDoInner>);
})}
</ToDoFull>
I've tried quite a few methods but I have not been able to get onChange to work. I'm working on a search-bar component that makes a fetch call after the user has not changed the search bar input for 3 seconds, but I am having issues changing the userSearchInput state hook which fires the api call in useEffect. Here is a minimized version of the code:
import React, { useState, useEffect } from "react";
import Select from "react-select";
export default function SearchBar() {
const [userSearchInput, setUserSearchInput] = useState("");
const [searchSuggestions, setSearchSuggestions] = useState([]);
useEffect(() => {
const searchSuggestions = async (searchInput) => {
console.log("api called");
const searchSuggestions = await fetch(
'API STUFF'
)
.then((response) => response.json())
.then((data) => {
setSearchSuggestions(data.quotes);
});
};
const timer = setTimeout(() => {
if (userSearchInput !== "") {
searchSuggestions("test");
}
}, 3000);
return () => clearTimeout(timer);
}, [userSearchInput]);
const handleSearchInputChange = (event) => {
setUserSearchInput(event.target.value);
console.log("input changed");
};
return (
<Select
options={searchSuggestions}
value={userSearchInput}
placeholder="Search a ticker"
onChange={handleSearchInputChange}
/>
);
}
Any ideas on where I'm going wrong?
select has value of object containing "label" and "value"
also, react select already returning value in an argument of function so all you have to do is to use it
const handleSearchInputChange = (selectedOptionObj) => {
setUserSearchInput(selectedOptionObj);
console.log("input changed");
};
In my Typescript app, there's a class that represents some data. This class is being shared end to end (both front-and-back ends use it to structure the data). It has a property named items which is an array of numbers.
class Data {
constructor() {
this.items = [0];
}
addItem() {
this.items = [...this.items, this.items.length];
}
}
I'm trying to render those numbers in my component, but since modifying the class instance won't cause a re-render, I have to "force rerender" to make the new items values render:
const INSTANCE = new Data();
function ItemsDisplay() {
const forceUpdate = useUpdate(); // from react-use
useEffect(() => {
const interval = setInterval(() => {
INSTANCE.addItem();
forceUpdate(); // make it work
}, 2000);
return () => clearInterval(interval);
}, []);
return (
<div>
<h1>with class:</h1>
<div>{INSTANCE.items.map(item => <span>{item}</span>)}</div>
</div>
);
}
While this works, it has one major drawback: addItem() is not the only modification done to INSTANCE; This class has actually around 10 to 15 properties that represent different data parts. So, doing forceUpdate() wherever a modification happens is a nightmare. Not no mention, if this instance will be modified outside the component, I won't be able to forceUpdate() to sync the change with the component.
Using useState([]) to represent items will solve this issue, but as I said Data has a lot of properties, so as some functions. That's another nightmare.
I would like to know what's the best way of rendering data from a class instance, without rerender hacks or unpacking the whole instance into a local component state.
Thanks!
Here's a Codesandbox demo that shows the differences between using a class and a local state.
Here is an example of how you can make Data instance observable and use Effect in your components to observe changes in Data instance items:
const { useState, useEffect } = React;
class Data {
constructor() {
this.data = {
users: [],
products: [],
};
this.listeners = [];
}
addItem(type, newItem) {
this.data[type] = [...this.data[type], newItem];
//notify all listeners that something has been changed
this.notify();
}
addUser(user) {
this.addItem('users', user);
}
addProduct(product) {
this.addItem('products', product);
}
reset = () => {
this.data.users = [];
this.data.products = [];
this.notify();
};
notify() {
this.listeners.forEach((l) => l(this.data));
}
addListener = (fn) => {
this.listeners.push(fn);
//return the remove listener function
return () =>
(this.listeners = this.listeners.filter(
(l) => l !== fn
));
};
}
const instance = new Data();
let counter = 0;
setInterval(() => {
if (counter < 10) {
if (counter % 2) {
instance.addUser({ userName: counter });
} else {
instance.addProduct({ productId: counter });
}
counter++;
}
}, 500);
//custom hook to use instance
const useInstance = (instance, fn = (id) => id) => {
const [items, setItems] = useState(fn(instance.data));
useEffect(
() =>
instance.addListener((items) => setItems(fn(items))),
[instance, fn]
);
return items;
};
const getUsers = (data) => data.users;
const getProducts = (data) => data.products;
const Users = () => {
const users = useInstance(instance, getUsers);
return <pre>{JSON.stringify(users)}</pre>;
};
const Products = () => {
const products = useInstance(instance, getProducts);
return <pre>{JSON.stringify(products)}</pre>;
};
const App = () => {
const reset = () => {
instance.reset();
counter = 0;
};
return (
<div>
<button onClick={reset}>Reset</button>
<div>
users:
<Users />
</div>
<div>
products:
<Products />
</div>
</div>
);
};
ReactDOM.render(<App />, document.getElementById('root'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>
<div id="root"></div>
I'm new about reactJs.
I'm trying to concatenate results in async loop, but something is wrong.
setState not save correctly, when I print it I can see it's an empty array, I think because there is inside an async call.
How I can solve that? Please suggest me.
function App() {
const [data, setData] = useState({data:[]});
const handleChildSubmit = (data) => {
setData(data);
}
return (
<div>
<Form onChildSubmit={handleChildSubmit} />
<Results flights={data} />
</div>
);
}
const Form = ( {onChildSubmit} ) => {
const [dati, setDati] = useState([]);
const onFormSubmit = async (e) => {
e.preventDefault();
// Flights search Parameters
const data = new FormData(e.target);
const dateFrom = data.get('dateFrom');
const dateTo = data.get('dateTo');
const departureDay = data.get('day');
const placeFrom = data.get('placeFrom');
const placeTo = data.get('placeTo');
let dayDeparture;
const daysDeparture = getDayOfWeekArray(dateFrom,dateTo,departureDay);
// Loop of Fly Search in range time
for(let i=0; i < daysDeparture.length; i++){
dayDeparture = daysDeparture[i];
axios.get('https://api.skypicker.com/flights?flyFrom='+placeFrom+'&to='+placeTo+'&dateFrom='+dayDeparture+'&dateTo='+dayDeparture+'&partner=picky')
.then(res => {
setDati([...dati, res.data]);
onChildSubmit(dati);
})
.catch(function (error) {
console.log('error: '+error);
})
.finally(function () {
});
}
}
The problem is that useState here gives you the dati variable with a specific value. Then your asynchronous stuff happens, and setDati() is called multiple times, but dati is does not change until the form is re-rendered and you then call onFormSubmit again.
You have a few options.
You could add the results only once as an array.
const results = []
for(let i=0; i < daysDeparture.length; i++){
dayDeparture = daysDeparture[i];
const res = await axios.get('https://api.skypicker.com/flights?flyFrom='+placeFrom+'&to='+placeTo+'&dateFrom='+dayDeparture+'&dateTo='+dayDeparture+'&partner=picky');
results.push(res.data);
}
// Add all results when all are fetched.
setDati([...dati, ...results])
Or useReducer which reliably gives you the latest version of the state, right at the time that you change it, so you don't have stale data.
// Something like...
function reducer(state, action) {
switch(action.type) {
case 'append':
return [...state, action.payload]
}
}
function YourComponent() {
const [dati, dispatch] = useReducer(reducer, [])
const onFormSubmit = async (e) => {
for(let i=0; i < daysDeparture.length; i++){
dayDeparture = daysDeparture[i];
const res = await axios.get('https://api.skypicker.com/flights?flyFrom='+placeFrom+'&to='+placeTo+'&dateFrom='+dayDeparture+'&dateTo='+dayDeparture+'&partner=picky')
dispatch({type: 'append', payload: res.data})
}
}
}
useState provides a way to use the previous state when updating (just like the callback way when you are using a class component), could give that a shot
.then(res => {
setDati(prevDati => ([...prevDati, res.data]));
onChildSubmit(dati);
})
I am using the react autosuggest library to build auto-suggestion
import Autosuggest from "react-autosuggest";
import React, { Component } from "react";
import QueryString from "query-string";
class AutoSuggestSearch extends Component {
constructor() {
super();
this.state = {
value: "",
suggestions: []
};
this.getSuggestionValue = this.getSuggestionValue.bind(this);
this.renderSuggestion = this.renderSuggestion.bind(this);
}
onChange = (event, { newValue }) => {
this.setState({
value: newValue
});
};
getSuggestionValue = suggestion => suggestion.fullNameSuggestion;
renderSuggestion = suggestion => <div>{suggestion.name}</div>;
onSuggestionSelected = (event, { suggestion}) => {
console.log(suggestion);
this.setState({
suggestions: [],
value: suggestion.name
});
};
onSuggestionsFetchRequested = ({ value }) => {
const params = {
stationPrefixName: value
};
const queryParams = QueryString.stringify(params);
fetch(`http://localhost:8000/api/suggest?${queryParams}`)
.then(res => res.json())
.then(data => {
console.log(data);
this.setState({
suggestions: data
});
})
.catch(console.log);
};
// Autosuggest will call this function every time you need to clear suggestions.
onSuggestionsClearRequested = () => {
this.setState({
suggestions: [],
value: ""
});
};
render() {
const { value, suggestions } = this.state;
const inputProps = {
placeholder: "Search",
value,
onChange: this.onChange
};
return (
<Autosuggest
suggestions={suggestions}
onSuggestionsFetchRequested={this.onSuggestionsFetchRequested}
onSuggestionsClearRequested={this.onSuggestionsClearRequested}
onSuggestionSelected={this.onSuggestionSelected}
getSuggestionValue={this.getSuggestionValue}
renderSuggestion={this.renderSuggestion}
inputProps={inputProps}
/>
);
}
}
export default AutoSuggestSearch;
The suggestion gets rendered on typing on search box as well as the logging inside onSuggestionSelected gets logged correctly but the input search box does not update correctly.
On debugging further I found that onSuggestionsClearRequested also gets invoked after onSuggestionSelected which is causing the search input box to be empty.
I validated this by adding const string inside onSuggestionsClearRequested
onSuggestionsClearRequested = () => {
alert("clear request");
this.setState({
suggestions: [],
value: "mysearch"
});
};
Is there anyway to prevent onSuggestionsClearRequested invokation on suggestion selection?
Or updating the search query value inside onSuggestionsClearRequested is the correct way?
You can use componentDidUpdate or UseEffect if you are using it in functional component.
I have used react-autosuggest in functional component and clear suggestion works only if value doesn't matches with the suggestions:
const [clear, setClear] = useState(false);
const handleOnChange = newValue => {
setValue(newValue);
setClear(false);
};
useEffect(() => {
if (!suggestions.some(option => option === value) && clear) {
setValue('');
}
}, [clear]);
const onSuggestionsClearRequested = () => {
setClear(true);
setSuggestions([]);
};
The onSuggestionsClearRequested function gets called everytime you click outside the search input, which is the default implementation of the libary being used,
What we implement in onSuggestionsClearRequested is upto us.
you can change the implementation as follows :
Approach keep keyword inside input if available options are not selected
onSuggestionsClearRequested = () => {};
this should provide the desired implementation behaviour.
Hi you may approach with hooks. It looks good and less coding.
You may find below
https://github.com/rajmaravanthe/react-auto-suggestion