Adding state value to JSON object or context - reactjs

I currently have a context that contains a JS object. I want to be able to add a useState value to this object so that I can later use useEffect on the context to see if the state value has changed.
Here, I have my context:
//This exported as SortContext
export default React.createContext({
sortKeys: [],
setSortKeys: () => {}
})
And here is where I'd like to use the sortKeys array:
export default function useObjectListSort (objectList, sortFunctionMap) {
const sortContext = useContext(SortContext)
useEffect(() => {
//Do something when sortKeys gets updated.
}, sortContext.sortKeys)
And this is a component I made to initialize the context:
export default function SortBlock ({children}) {
const [sortKeyList, setSortKeyList] = useState([])
const sortContextConfig = {
sortKeys: sortKeyList,
setSortKeys: (newKeys) => setSortKeyList(newKeys)
}
return (
<SortContext.Provider value={sortContextConfig}>
{children}
</SortContext.Provider>
)
}
What I would expect is that using the my SortBlock component, and calling the setSortKeys function would update the sortKeyList state, and then the useEffect statement would trigger.
For example:
<SortContext.Consumer>
{sortContext => {
<button onClick={()=>sortContext.setSortKeys(['myKey'])}> Set Keys </button>
}}
</SortContext.Consumer>
Unfortunately, the useEffect is not triggering.

I've figured out the issue. I used the useObjectSortList hook in the same component that I used to initialize the SortBlock. This means that it falls outside of the scope of my context.

Related

How to avoid warning: Outer scope values like 'userDTO' aren't valid dependencies because mutating them doesn't re-render the component

getTransactions method call need to be called only if user is logged in, but the info about the user will not be visible.
What can I do to force a reload when on other component user log in, and also, how to get rid of the warning?
Outer scope values like 'userDTO' aren't valid dependencies because mutating them doesn't re-render the component
The info comes from Redux.
import { getTransactions } from '../../tikexModule/MyTicket'
import { userDTO } from '../../tikexModule/slices/tikexAPI'
interface MyTicketListProps {}
export default function MyTicketList(props: MyTicketListProps) {
const [transactionsOrPassTickets, setTransactionsOrPassTickets] =
useState<TransactionOrPassTicket[]>()
useEffect(() => {
if (userDTO()) {
getTransactions(setTransactionsOrPassTickets, window.location.search)
}
}, [userDTO])
userDTO is in slice.ts
export const userDTOS = createSelector(
[authnResS],
(authnRes?: AuthnRes) => authnRes?.data
)
export const userDTO = () => useAppSelector(userDTOS)
Dependencies used for useEffect should be defined in inner scope, since you are importing useDto it is from outer scope so it wont be a dependency. You either remove it
Or you create a new variable in inner scope and assign it to userDto like this
export default function MyTicketList(props: MyTicketListProps) {
const _userDto=userDto
const [transactionsOrPassTickets, setTransactionsOrPassTickets] =
useState<TransactionOrPassTicket[]>()
useEffect(() => {
if (_userDTO()) {
getTransactions(setTransactionsOrPassTickets, window.location.search)
}
}, [_userDTO])
it's unclear to me what you mean by userDTO, could you elaborate ?
if it's a store value i'd suggest you use useSelector hook to automatically render when it changes.

Update React context from child component

I am not able to update my context object from child component. Here is my provider file for creating react context and that component is wrapped over whole application in root app.tsx file.
I can fetch context in child components but I am not sure how to update it.
const CorporateContext = React.createContext(undefined);
export const useCorporateContext = () => {
return useContext(CorporateContext);
};
export const CorporateProvider = ({ children }) => {
const [corporateUserData, setCorporateUserData] = useState(undefined);
useEffect(() => {
const localStorageSet = !!localStorage.getItem('corporateDetailsSet');
if (localStorageSet) {
setCorporateUserData({corporateId: localStorage.getItem('corporateId'), corporateRole: localStorage.getItem('corporateRole'), corporateAdmin: localStorage.getItem('corporateAdmin'), corporateSuperAdmin: localStorage.getItem('corporateSuperAdmin')});
} else {
(async () => {
try {
const json = await
fetchCorporateUserDetails(getClientSideJwtTokenCookie());
if (json.success !== true) {
console.log('json invalid');
return;
}
setCorporateUserData({corporateId: json.data.corporate_id, corporateRole: json.data.corporate_role, corporateAdmin: json.data.corporate_role == 'Admin', corporateSuperAdmin: json.data.corporate_super_admin});
addCorporateDetailsToLocalStorage(corporateUserData);
} catch (e) {
console.log(e.message);
}
})();
}
}, []);
return (
<CorporateContext.Provider value={corporateUserData}>
{children}
</CorporateContext.Provider>
);
};
export default CorporateProvider;
Update: Here is what I changed and context seems to be updated (at least it looks updated when I check in chrome devtools) but child components are not re-rendered and everything still stays the same:
I changed react context to:
const CorporateContext = React.createContext({
corporateContext: undefined,
setCorporateContext: (corporateContext) => {}
});
Changed useState hook in a component:
const [corporateContext, setCorporateContext] = useState(undefined);
Passed setState as property to context:
<CorporateContext.Provider value={{corporateContext , setCorporateContext}}>
{children}
</CorporateContext.Provider>
You can also pass in setCorporateUserData as a second value to the provider component as such:
//...
return (
<CorporateContext.Provider value={{corporateUserData, setCorporateUserData}}>
{children}
</CorporateContext.Provider>
);
You can then destructure it off anytime you want using the useContext hook.
How I resolved this issue:
Problem was that I have cloned corporateContext object and changed property on that cloned object and then I passed it to setCorporateContext() method. As a result of that, context was updated but react didnt noticed that change and child components were not updated.
let newCorporateContext = corporateContext;
newCorporateContext.property = newValue;
setCorporateContext(newCorporateContext);
After that I used javascript spread operator to change the property of corporateContext inside setCorporateContext() method.
setCorporateContext({...corporateContext, property: newValue);
After that, react noticed change in hook and child components are rerendered!

useState function is not working in functional component

I want to change a state (filterConsultantId) with a useState function (setFilterConsultantId) after I trigger a normal function (handleConsultantChange) and I expect the state value is changed when I use the state in another normal function (getActiveLeads). But the state didn't change. Please see my React functional component below:
const TabActiveLeads = ({
...
}) => {
const [filterConsultantId, setFilterConsultantId] = useState('');
//after this arrow function triggered,
const handleConsultantChange = (event) => {
setFilterConsultantId(event.target.value); //and the filterConsultantId should be changed here
//this getActiveLeads function is called
getActiveLeads();
};
const getActiveLeads = () => {
// but the filterConsultantId here is not changed after it should be changed inside handleConsultantChange function
console.log(filterConsultantId);
};
};
export default TabActiveLeads;
I don't understand, why filterConsultantId is not changed inside the getActiveLeads function? Previously it should be changed by calling setFilterConsultantId inside the handleConsultantChange function.
Thanks for any help..
setFilterConsultantId is the asynchronous method, so you can't get the updated value of filterConsultantId right after setFilterConsultantId.
You should get it inside useEffect with adding a dependency filterConsultantId.
useEffect(() => {
console.log(filterConsultantId);
}, [filterConsultantId]);

Can we assign a Typescript class to a useState field of a Functional Component in React?

I was going through this blog - useEffect complete guide topic and thought to use a - simple Typescript class and update states and see how useEffect behaves.
Is it ok to assign Typescript class to a useState variable ??
You can find the link to the example here in CodeSandbox.io
If JSX...For that matter, what if its just a ES6 class and not Typescript ?
You are storing the instance of a class in state but you never update it.
You must note that even though you add items to the stateHelper class but the instance is not changing and hence the useEffect will not be executed again.
However in order to store instances that remain constant throughout the scope of your component instance its better to make use of useRef instead of useState
const TypeScriptComponent: FunctionComponent = (props): JSX.Element => {
// Creating an instance from a (Typescript) Class and assigning it to useState
const stateHelper = useRef<StateHelper>(new StateHelper());
const [list, setList] = useState<string[]>([]);
// ........on button click
const handleButtonClick = async () => {
stateHelper.current.addList("Shwetha");
// Calling an async data from the Typescript class this time
const result = await stateHelper.current.fireData();
if (result) {
stateHelper.current.addList(result);
const newList = stateHelper.current.getList();
console.log("New list is: ", newList);
setList(newList);
}
};
useEffect(() => {
stateHelper.current.addList("Pramod");
const newList = stateHelper.current.getList();
setList(newList);
console.log("component did mount");
}, []);
return (
<React.Fragment>
<div>
{list.length > 0 &&
list.map((item: any, index: number) => <p key={index}>{item}</p>)}
<button onClick={handleButtonClick}>Add Me</button>
</div>
</React.Fragment>
);
};
export default TypeScriptComponent;
Thanks man. That makes sense....!
The reason I was thinking to useState is because if we could have something like this
const [customObj, setCustomObj] = useState({}); // primitive object
....why not have a fully fledged instances of a class
const customObj = useState(new CustomClass())[0]; // fully fledged instances of a class
Does that makes sense ?

React get state from Redux store within useEffect

What is the correct way to get state from the Redux store within the useEffect hook?
useEffect(() => {
const user = useSelector(state => state.user);
});
I am attempting to get the current state within useEffect but I cannot use the useSelector call because this results in an error stating:
Invariant Violation: Hooks can only be called inside the body of a function component.
I think I understand why as it breaks one of the primary rules of hooks.
From reviewing the example on the Redux docs they seem to use a selectors.js file to gather the current state but this reference the mapStateToProps which I understood was no longer necessary.
Do I need to create some kind of "getter" function which should be called within the useEffect hook?
Don't forget to add user as a dependency to useEffect otherwise your effect won't get updated value.
const user = useSelector(state => state.user);
useEffect(() => {
// do stuff
}, [user]);
You can place useSelector at the top of your component along with the other hooks:
const MyComponent = () => {
...
const user = useSelector(state => state.user);
...
}
Then you can access user inside your useEffects.
I found using two useEffects to works for me, and have useState to update the user (or in this case, currUser).
const user = useSelector(state=>state.user);
const [currUser, setCurrUser] = useState(user);
useEffect(()=>{
dispatch(loadUser());
}, [dispatch]);
useEffect(()=>{
setCurrUser(user);
}, [user]);
You have to use currUser to display and manipulate that object.
You have two choices.
1 - If you only need the value from store once or 'n' time your useEffect is called and don't want to listen for any changes that may occur to user state from redux then use this approach
//import the main store file from where you've used createStore()
import {store} from './store' // this will give you access to redux store
export default function MyComponent(){
useEffect(() =>{
const user = store.getState().user;
//...
},[])
}
2 - If you want to listen to the changes that may occur to user state then the recommended answer is the way to go about
const MyComponent = () => {
//...
const user = useSelector(state => state.user);
useEffect(() => {
//...
},[])
//...
}
const tournamentinfofromstore=useSelector(state=>state.tournamentinfo)
useEffect(() => {
console.log(tournamentinfofromstore)
}, [tournamentinfofromstore])
So the problem is that if you change the state inside the useEffect that causes a rerender and then again the useEffect gets called "&&" if that component is passing data to another component will result in infinite loops.and because you are also storing that data in the child component's state will result in rerendering and the result will be infinite loop.!!
Although it is not recommended, you can use store directly in your component, even in the useEffect.
First, you have to export store from where it is created.
import invoiceReducer from './slices/invoiceSlice';
import authReducer from './slices/authSlice';
export const store = configureStore({
reducer: {
invoices: invoicesReducer,
auth: authReducer,
},
});
Then you can import it to a React Component, or even to a function, and use it.
import React, { useEffect } from 'react';
import { store } from './store';
const MyComponent = () => {
useEffect(()=> {
const invoiceList = store.getState().invoices
console.log(invoiceList)
}, [])
return (
<div>
<h1>Hello World!</h1>
</div>
)
}
export default MyComponent
You can study the API for Store in here.
You can also see why this approach is not recommended in
here.
Or, if you are interested in using redux store outside a react component, take a look at this blog post.
To add on top of #Motomoto's reply. Sometimes you depend on store to be loaded before useEffect. In this case you can simply return in if the state is undefined. useEffect will rerender once the store is loaded
const user = useSelector(state => state.user);
useEffect(() => {
if(user === undefined){
return}else{
// do stuff
}}, [user]);
I'm having the same issue, The problem to the useSelector is that we cant call it into the hook, so I can't be able to update with the action properly. so I used the useSelector variable as a dependency to the useEffect and it solved my problem.
const finalImgData_to_be_assigned = useSelector((state) => state.userSelectedImg);
useEffect(()=>{
console.log('final data to be ready to assign tags : ', finalImgData_to_be_assigned.data);
}, [finalImgData_to_be_assigned ])

Resources