I am currently using Agents.tsx as a parent function and StickyFilter.tsx as the child. I am setting a state with an initial value of an empty string in Agents.tsx like so:
const Agents = (props: AgentsProps): ReactElement => {
const { agentsMode, recruitClient, recruitGlobals, setRecruitClient, setShowLoading } = props;
const [savedSearchName, setSavedSearchName] = useState(''); // set state here
I then am passing that setSavedSearchName as a prop to StickyFilter.tsx:
const StickyFilter = (props: StickyFilterProps): ReactElement => {
const { otherProps,
setSavedSearchName
} = props;
later down in StickFilter.tsx I am calling on that state function like so:
const setSavedSearch = (savedSearch: RecruitAgentSearch): void => {
let revisedSearches: AgentSearch[] = [];
// Redacted code -- bunch of logic
// Value gets set here
setSavedSearchName(revisedSearchDetails.criteria.searchName || '');
};
Once that value gets set in StickyFilter.tsx I am able to have it console log in the parent component just fine. I have a table that renders and once I click on a certain item it fires off the saveNewNote function, but for some reason the function is still pulling in the empty string but whenever I click on the item a SECOND time, it'll pull in the right value. What am I missing here? I tried inputting a useEffect hook like so:
useEffect(() => {
console.log(savedSearchName)
}, [savedSearchName]);
but it will not console log the correct value until I do the action on the table a second time.
Any advice is appreciated.
Edit: Here is the saveNewNote function in Agents.tsx
const saveNewNote = async (agent: Agent, agentsMode: AgentsMode): Promise<void> => {
// just trying to console log here after the state is set
console.log(savedSearchName); // returning empty string
console.log(recruitStoreApi.savedSearchName);
console.log(stateApi);
// Simple logic redacted
}
I ended up changing the data structure that injects into the table. I could not find a way to get the latest state.
Related
I am trying to find an item from a collection, from the code below, in order to update my react component, the propertState object isnt empty, it contains a list which i have console logged, however I seem to get an underfined object when i console log the value returned from my findProperty function... I am trying update my localState with that value so that my component can render the right data.
const PropertyComponent = () => {
const { propertyId } = useParams();
const propertyState: IPropertiesState = useSelector(
propertiesStateSelector
);
const[property, setProperty] = useState()
const findProperty = (propertyId, properties) => {
let propertyReturn;
for (var i=0; i < properties.length; i++) {
if (properties[i].propertyId === propertyId) {
propertyToReturn = properties[i];
break;
}
}
setProperty(propertyReturn)
return propertyReturn;
}
const foundProperty = findProperty(propertyId, propertyState.properties);
return (<>{property.propertyName}</>)
}
export default PropertyComponent
There are a few things that you shall consider when you are finding data and updating states based on external sources of data --useParams--
I will try to explain the solution by dividing your code in small pieces
const PropertyComponent = () => {
const { propertyId } = useParams();
Piece A: Consider that useParams is a hook connected to the router, that means that you component might be reactive and will change every time that a param changes in the URL. Your param might be undefined or an string depending if the param is present in your URL
const propertyState: IPropertiesState = useSelector(
propertiesStateSelector
);
Piece B: useSelector is other property that will make your component reactive to changes related to that selector. Your selector might return undefined or something based on your selection logic.
const[property, setProperty] = useState()
Piece C: Your state that starts as undefined in the first render.
So far we have just discovered 3 pieces of code that might start as undefined or not.
const findProperty = (propertyId, properties) => {
let propertyReturn;
for (var i=0; i < properties.length; i++) {
if (properties[i].propertyId === propertyId) {
propertyToReturn = properties[i];
break;
}
}
setProperty(propertyReturn)
return propertyReturn;
}
const foundProperty = findProperty(propertyId, propertyState.properties);
Piece D: Here is where more problems start appearing, you are telling your code that in every render a function findProperty will be created and inside of it you are calling the setter of your state --setProperty--, generating an internal dependency.
I would suggest to think about the actions that you want to do in simple steps and then you can understand where each piece of code belongs to where.
Let's subdivide this last piece of code --Piece D-- but in steps, you want to:
Find something.
The find should happen if you have an array where to find and a property.
With the result I want to notify my component that something was found.
Step 1 and 2 can happen in a function defined outside of your component:
const findProperty = (propertyId, properties) => properties.find((property) => property.propertyId === propertyId)
NOTE: I took the liberty of modify your code by simplifying a little
bit your find function.
Now we need to do the most important step, make your component react at the right time
const findProperty = (propertyId, properties) => properties.find((property) => property.propertyId === propertyId)
const PropertyComponent = () => {
const { propertyId } = useParams();
const propertyState: IPropertiesState = useSelector(
propertiesStateSelector
);
const[property, setProperty] = useState({ propertyName: '' }); // I suggest to add default values to have more predictable returns in your component
/**
* Here is where the magic begins and we try to mix all of our values in a consistent way (thinking on the previous pieces and the potential "undefined" values) We need to tell react "do something when the data is ready", for that reason we will use an effect
*/
useEffect(() => {
// This effect will run every time that the dependencies --second argument-- changes, then you react afterwards.
if(propertyId, propertyState.properties) {
const propertyFound = findProperty(propertyId, propertyState.properties);
if(propertyFound){ // Only if we have a result we will update our state.
setProperty(propertyFound);
}
}
}, [propertyId, propertyState.properties])
return (<>{property.propertyName}</>)
}
export default PropertyComponent
I think that in this way your intention might be more direct, but for sure there are other ways to do this. Depending of your intentions your code should be different, for instance I have a question:
What is it the purpose of this component? If its just for getting the property you could do a derived state, a little bit more complex selector. E.G.
function propertySelectorById(id) {
return function(store) {
const allProperties = propertiesStateSelector(store);
const foundProperty = findProperty(id, allProperties);
if( foundProperty ) {
return foundProperty;
} else {
return null; // Or empty object, up to you
}
}
}
Then you can use it in any component that uses the useParam, or just create a simple hook. E.G.
function usePropertySelectorHook() {
const { propertyId } = useParams();
const property = useSelector(propertySelectorById(propertyId));
return property;
}
And afterwards you can use this in any component
functon AnyComponent() {
const property = usePropertySelectorHook();
return <div> Magic {property}</div>
}
NOTE: I didn't test all the code, I wrote it directly in the comment but I think that should work.
Like this I think that there are even more ways to solve this, but its enough for now, hope that this helped you.
do you try this:
const found = propertyState.properties.find(element => element.propertyId === propertyId);
setProperty(found);
instead of all function findProperty
After selecting two values from a list, I must make a call to an api that returns a Json with the information
In this way I call the api that responds with the JSON
export default class ProductsService {
async getProducts(channel, category){
const aux = await axios.post('https://appshop.dapducasse.cl/ducasse-api/api/Qrs?channel='+encodeURIComponent(channel)+'&category='+encodeURIComponent(category)).then(data =>data.data).catch(e => [])
return aux
}
}
In this way I make the call from the react component
const chanelService = new ChanelService();
const categoryService = new CategoryService();
const productsService = new ProductsService();
useEffect(() => {
chanelService.getChanels().then(data => setChanels(data))
categoryService.getCategories().then(data => setCateries(data))
}, []);
const testChargue = async ( ) =>{
console.log(selectedChannel)
console.log(selectedCategory)
const a = await productsService.getProducts(selectedChannel, selectedCategory).then(data => setProducts(data));
console.log(products)
}
When I press the button, the function should be executed where the call is made with the Channel and the selected product.
I don't get the json on the first click of the button, but on the second, I think this happens because the execution ends before setProducts defines the state of products.
I assume products and setProducts refer to local state in your component?
In this case products in the last line will always be the "old" value inside your handler function, because setProducts will not update that variable. The variable will only changed when the component will been re-rendered, but not during the execution of your handler function. I think that's why you see the value when pressing the button twice. On the second click the component has been re-rendered, so the value also in your handler function has been updated.
More on "stale" state in the react docs: https://reactjs.org/docs/hooks-faq.html#why-am-i-seeing-stale-props-or-state-inside-my-function
I've tried almost every solution similar to my problem, yet none is working. I have a simple state and changing the value of this state in a function as like below, handleOnClick is calling in a button's onClick event. I'm also using Router(if it's change something);
import { useState} from "react"
import { BrowserRouter as Router, Route, Link, useHistory} from "react-router-dom";
const Buton = () => {
let x = "";
const [lowerState, setLower] = useState("")
const history = useHistory();
const handleOnClick = () => {
x = document.getElementById("my_input").value.toLowerCase();
setLower(x)
console.log(x) //this prints the current value
console.log(lowerState) //this DOES NOT prints the current value, but
// when I put another text into the input and click
// to button, it prints the first value I put here
history.push('/test', {params : lowerState})
};
.
.
.
return (...)
}
export default Buton
Now x is a value that returns from an input HTML element. When I set this value as a state and console log, it doesn't print the value first, when I put something in input again, then it prints the first value. So it's like it's coming 1 step behind.
I've used useEffect() , I did put a second parameter to setLower as console.log(lowerState) and other things on the internet that people suggested, but none is working. Every time, the state is coming 1 step behind. How can I make this state changes immediately?
If you want to use the value of an input in a user event function, the best way (and least buggy) is to bind your input value to local state and then just reference that state in your callback function.
Please try to avoid imperatively pulling values from the DOM using getElementById etc. Here's what I mean:
const [value, setValue] = useState('');
// This will keep everything updated until you need to use it
handleChange(event) {
setValue(event.target.value);
}
// Then just grab whatever is in local state
handleClick() {
history.push('/test', {params : value});
}
return (
<input value={value} onChange={handleChange} />
// Your button is here too
)
This is because when you call setLower(x) it is not an async call. So it doesn't wait. That's why you get the 1 step before value in your state right after setting the value.
Official doc - https://reactjs.org/docs/state-and-lifecycle.html#state-updates-may-be-asynchronous
When you call setLower(x), it doesn't immediately update the lowerState. The new value will be available the next time it renders. Because of that the console.log(x) "works" (since it uses the new value that you gain as a parameter) but console.log(lowerState) uses the old value that hasn't updated to the state yet at that point.
If you want history.push('/test', {params : lowerState}) to work, then you need to use the x there instead of lowerState. Or call it within a useEffect with the lowerState and having lowerState as a dependency parameter to the hook.
This is expected behaviour since React is updating state in a batch
Which mean that the state only gets an update after an eventHandler/function is finished
If you want to do some condition, wrap your logic inside a useEffect
useEffect(() => {
if (lowerState === "your-condition-value") {
history.push("/test", { params: lowerState });
}
}, [lowerState]);
Or in your case, just use the variable directly:
const handleOnClick = () => {
x = document.getElementById("my_input").value.toLowerCase();
history.push("/test", { params: x });
};
You should not worry about that since your app still working as expected
So i would like to suggest that use useRef if need for reference only object which may not causing rerendering. also using let x= "" is not correct, you should write code immutable way
const Buton = () => {
const lowerCaseRef = useRef("")
const history = useHistory();
const handleOnClick = () => {
lowerCaseRef.current =
document.querySelector("#my_input").value.toLowerCase();
console.log(lowerCaseRef.current) //this DOES NOT prints the current value, but
// when I put another text into the input and click
// to button, it prints the first value I put here
history.push('/test', {params : lowerCaseRef.current})
};
return (...)
}
I have a parent component that having some props passing from grandparent component and I am using one prop (object) and pass the value of that object to children component as props. I also pass a function to child component in order to get the updated value back from child component.
ParentComponent.js
const ParentComponent = props => {
const { record, saveRecord } = props;
const editedRecord = {...record}
const handleRecordValues = (name, value) => {
editedRecord[name] = value;
};
...
const content = <div>
<ChildComponent name={record.name} value={record.value} setValue={handleRecordValues} />
<Button onClick={() => saveRecord(editedRecord)} />
</div>
return content;
}
ChildrenComponent.js
const ChildComponent = props => {
const { name, value, setValue } = props;
const [input, setInput] = useState(value);
const handleChange = (e, text) => {
setInput(text);
setValue(name, value);
}
return <TextField value={input} onChange={handleChange}/>
}
Above are the sample components I have. The issue is when I pass the editedRecord to saveRecord func to grandparent component the editedRecord is always the same as record as it is copied from record and value is not updated for that variable. I expect the editedRecord being updated by the handleRecordValues func.
For example, the record that I get is {}. And I create a new const editedRecord which is also {}.
After I input some value from ChildComponent the editedRecord should be updated to {name: value}. However when I click on Button in ParentComponent the editedRecord parameter is still {}.
Updated
Instead of using const I use
const [editedRecord, setEditedRecord] = useState(record);
const handleRecordValues = (name, value) => {
const newRecord = {
...editedRecord
};
newRecord[name] = value;
setEditedRecord(newRecord);
};
Now the editedRecord value got updated but another issue came up:
when I have multiple components as child components it only update the last one entry I have entered.
Your setValue/handleRecordValues function changes a variable ... but React has no way of knowing when that variable changes.
To let React know, you have to call saveRecord(editedRecord) after you make the change, or in other words you have to invoke a state-setting function, so that React knows about the change.
In general in React, if you don't change context/state/props (and for context/state, that means doing so using the appropriate React functions), React can't know to re-render your components in response. This means that any data that your components depend on to render needs to be changed via one of those three mechanisms, not just via ordinary Javascript, ie. a.b = c.
EDIT: To clarify a point in the comments. When you make a state variable:
const [myState, myStateSetter] = useState('');
there is nothing "magic" about myState; it's just another JS variable. Javascript doesn't give React any way to know when that variable changes, so if you just do:
myState = 4;
React has no idea that you did so. It only knows that it changed if you tell it that it changed ... ie. if you call:
myStateSetter(4);
Here's how I would alter the parent component to make everything work with react. The main issue you were having is that react needs to know that a change has occurred, so we need to set up the values as state/set state.
const ParentComponent = props => {
const { record, saveRecord } = props;
const [editedRecord,setEditedRecord] = useState(record);
useEffect(()=>{
//This sets the record straight whenever the parent passes a new record.
//You'd need to make sure the record is referentially stable when it isn't being updated, though
setEditedRecord(record);
},[record])
const handleRecordValues = (name, value) => {
setEditedRecord(record=>{...record,[name]:value});
};
...
const content = <div>
<ChildComponent name={editedRecord.name} value={editedRecord.value} setValue={handleRecordValues} />
<Button onClick={() => saveRecord(editedRecord)} />
</div>
return content;
}
So I have this Display() function which fetches events from the Google Calendar via an API and store each event's name via element.summary into the events set. And then once the events set is populated, I iterate through the set via for (let item of events) and create a new <a> tag for each event/item in the set using the name as the text via <a>{item}</a> (for e.g. <a>call<a>, then I push each <a> tag into a new array called tabs and then finally return the tabs array. The events set contains three items and when I console.log, I see the correct items ("call", "kist", & "go") in the set. However, once I console.log the tabs array, it only contains one <a> tag whose value is null whereas it is supposed to contain three <a> tags since it iterates through the events set which has three items and is supposed to create an <a> tag for each. Also, I get the error that item is not defined for the line for (let item of events), somehow I cannot iterate through the events set. See console output here.
function Display() {
let events = new Set()
let tabs = []
ApiCalendar.listUpcomingEvents(10)
.then(({result}: any) => {
result.items.forEach(element => {
console.log(element.summary)
events.add(element.summary)
}
);
console.log(events)
for (let item of events)
console.log(item)
tabs.push(<a>{item}</a>)
console.log(tabs)
return tabs
});
}
This is the class that I made in the same file as the above function, which basically renders a 'Log In' button if user is not logged in to their calendar, or renders the array of <a> tags returned by the Display() function if user is already logged in. However, even though the Display() function above does return something (i.e. an array of <a> tags) and the render() function inside the class also returns a <div> element with the corresponding component inside the div, I get the error Uncaught Error: Display(...): Nothing was returned from render. This usually means a return statement is missing. Or, to render nothing, return null. I am new to JavaScript and have no idea what I'm doing wrong. Any help is greatly appreciated and thank you in advance.
export default class LoginControl extends React.Component {
constructor(props) {
super(props);
this.state = {
sign: ApiCalendar.sign,
};
}
render() {
const isLoggedIn = this.state.sign;
let ele;
if (isLoggedIn) {
ele = <Display/>;
} else {
ele = <Button>'Sign In'</Button>;
}
return (
<div>
{ele}
</div>
);
}
}
Your Display function calls an async method and returns nothing. You will need to utilize state and effect inside Display to render returned data. But then, you will encounter errors if user navigates away from page before data is fetched.
Best solution for this problem would be to utilize redux and redux-thunk
Caution, untested code below
If you feel like you don't need redux, try this approach
async function fetchItems() {
const result = await ApiCalendar.listUpcomingEvents(10);
return result.result.items.map(({summary}) => summary);
}
function Display() {
const [items, saveItems] = useState([]);
const isMounted = useRef(true);
useEffect(() => {
return () => {
isMounted.current = false;
};
}, []);
useEffect(() => {
(async () => {
const items = await fetchItems();
//Do not update state if component is unmounted
if (isMounted.current) {
saveItems(items);
}
})();
}, []);
return <>{items.map(item => <a key={item}>{item}</a>)}</>
}
If you want to render more than summary, you can do it like this
async function fetchItems() {
const result = await ApiCalendar.listUpcomingEvents(10);
return result.result.items.map(({summary, somethingElse}) => ({summary, somethingElse}));
//can be replaced with return [...result.result.items]; to get all props
}
function Display() {
//... Main logic of Display component is the same,
//so I wouldn't duplicate it here
return <>{items.map(item => <div key={item.summary}>{item.summary} {item.somethingElse}</div>)}</>
}
It seems that you are not returning anything on Display component.
You can't return a promise on a component so you need to make it inside useEffect using react hooks or component lifecycle - and no, you don't need redux just to achieve this.
function Display() {
let events = new Set()
let tabs = [];
const [items, setItems] = useState([]);
const getList = async () => {
const res = await ApiCalendar.listUpcomingEvents(10);
setItems(res.items);
}
useEffect(async () => {
getList();
}, []);
return items.map(item => <div>{item}</div>);
}