need to pass an effect via props or force component reload from outside the root component in preact/react - javascript

I have a situation that an item outside the component might influence the item in the backend. ex: change the value of one of the properties that are persisted, let's say the item status moves from Pending to Completed.
I know when it happens but since it is outside of a component I need to tell to the component that it is out of sync and re-fetch the data. But from outside. I know you can pass props calling the render method again. But the problem is I have a reducer and the state will pick up the last state and if I use an prop to trigger an effect I get into a loop.
Here is what I did:
useEffect(() => {
if (props.effect && !state.effect) { //this runs when the prop changes
return dispatch({ type: props.effect, });
}
if (state.effect) { // but then I get here and then back up and so on
return ModelEffect[state.effect](state?.data?.item)}, [state.status, state.effect, props.effect,]);
In short since I can't get rid of the prop the I get the first one then the second and so on in an infinite loop.
I render the root component passing the id:
render(html`<${Panel} id=${id}/>`,
document.getElementById('Panel'));
Then the idea was that I could do this to sync it:
render(html`<${Panel} id=${id} effect="RELOAD"/>`,
document.getElementById('Panel'));
any better ways to solve this?
Thanks a lot

I resolved it by passing the initialized dispatch function to a global.
function Panel (props) {
//...
const [state, dispatch,] = useReducer(RequestReducer, initialData);
//...
useEffect(() => {
//...
window.GlobalDispatch = dispatch;
//...
}, [state.status, state.effect,]);
with that I can do:
window.GlobalDispatch({type:'RELOAD'});

Related

How to reduce the number of times useEffect is called?

Google's lighthouse tool gave my app an appalling performance score so I've been doing some investigating. I have a component called Home
inside Home I have useEffect (only one) that looks like this
useEffect(() => {
console.log('rendering in here?') // called 14 times...what?!
console.log(user.data, 'uvv') // called 13 times...again, What the heck?
}, [user.data])
I know that you put the second argument of , [] to make sure useEffect is only called once the data changes but this is the main part I don't get. when I console log user.data the first 4 console logs are empty arrays. the next 9 are arrays of length 9. so in my head, it should only have called it twice? once for [] and once for [].length(9) so what on earth is going on?
I seriously need to reduce it as it must be killing my performance. let me know if there's anything else I can do to dramatically reduce these calls
this is how I get user.data
const Home = ({ ui, user }) => { // I pass it in here as a prop
const mapState = ({ user }) => ({
user,
})
and then my component is connected so I just pass it in here
To overcome this scenario, React Hooks also provides functionality called useMemo.
You can use useMemo instead useEffect because useMemo cache the instance it renders and whenever it hit for render, it first check into cache to whether any related instance has been available for given deps.. If so, then rather than run entire function it will simply return it from cache.
This is not an answer but there is too much code to fit in a comment. First you can log all actions that change user.data by replacing original root reducer temporarlily:
let lastData = {};
const logRootReducer = (state, action) => {
const newState = rootReducer(state, action);
if (newState.user.data !== lastData) {
console.log(
'action changed data:',
action,
newState.user.data,
lastData
);
lastData = newState.user.data;
}
return newState;
};
Another thing causing user.data to keep changing is when you do something like this in the reducer:
if (action.type === SOME_TYPE) {
return {
...state,
user: {
...state.user,
//here data is set to a new array every time
data: [],
},
};
}
Instead you can do something like this:
const EMPTY_DATA = [];
//... other code
data: EMPTY_DATA,
Your selector is getting user out of state and creating a new object that would cause the component to re render but the dependency of the effect is user.data so the effect will only run if data actually changed.
Redux devtools also show differences in the wrong way, if you mutate something in state the devtools will show them as changes but React won't see them as changes. When you assign a new object to something data:[] then redux won't show them as changes but React will see it as a change.

How to access current state of component from useEffect without making it a dependency?

I've got the following component (simplified) which, given a note ID, would load and display it. It would load the note in useEffect and, when a different note is loaded or when the component gets unmounted, it saves the note.
const NoteViewer = (props) => {
const [note, setNote] = useState({ title: '', hasChanged: false });
useEffect(() => {
const note = loadNote(props.noteId);
setNote(note);
return () => {
if (note.hasChanged) saveNote(note); // bug!!
}
}, [props.noteId]);
const onNoteChange = (event) => {
setNote({ ...note, title: event.target.value, hasChanged: true });
}
return (
<input value={note.title} onChange={onNoteChange}/>
);
}
The issue is that within the useEffect I use note, which is not part of the dependencies so it means I always get stale data.
However, if I put the note in the dependencies then the loading and saving code will be executed whenever the note is modified, which is not what I need.
So I'm wondering how can I access the current note, without making it a dependency? I've tried to replace the note with a ref, but it means the component no longer updates when the note is changed, and I'd rather not use references.
Any idea what would be the best way to achieve this? Maybe some special React Hooks pattern?
You can't get the current state because this component does not render on the app render that removes it. Which means your effect never runs that last time.
Using an effect cleanup function is not a good place for this sort of thing. That should really be reserved for cleaning up that effect and nothing else.
Instead, whatever logic you have in the app that changes the state to close the NoteViewer should also save the note. So in some parent component (perhaps a NoteList or something) you'd save and close like:
function NoteList() {
const [viewingNoteId, setViewingNoteId] = useState(null)
// other stuff...
function closeNote() {
if (note.hasChanged) saveNote(note)
setViewingNoteId(null)
}
return <>{/* ... */}</>
}

How to reload a react component without using any event handler?

Is it possible to reload a component in react without using the help of a function like onClick. I want my component to reload again after it just got loaded.
I tried using
window.location.reload(false); in the constructor. I even tried using useEffect() but they make the page reload infinitely.
Is there any work around?
I have seen people accomplish this by setting a dummy setState method that is strictly used to trigger a refresh. Consider this component:
export const Proof = () => {
console.log("Component re-rendered")
const [dummyState,rerender] = React.useState(1);
const onClick = () => {
rerender(dummyState + 1);
}
React.useEffect( () => {
console.log("dummyState's state has updated to: " + dummyState)
}, [dummyState])
return(
<div>
<button onClick={onClick}>reRender</button>
</div>
)
}
Whenever you want to rerender the component you just need to trigger a change in the dummyState. Clicking the button will cause the components state to change, which will cause the component to rerender (i used a console.log() for proof). It is worth noting that simply calling a method that instantly changes the state of the component will result in an infinite loop.
From reading your comment above, it sounds like you are passing a value as a prop to a child component and you would like the value to be recalculated as soon as any interaction with that component occurs. Ideally, the interaction itself should cause the recalculation. But if you would just like to quickly recalculate the value and rerender the component as soon as it renders then i think this would work:
export const Proof = () => {
console.log("Component re-rendered")
const [dummyState,rerender] = React.useState(1);
//the empty brackets will cause this useEffect
//statement to only execute once.
React.useEffect( () => {
rerender(dummyState + 1);
}, [])
return(
<div>
<p>dummyState</p>
</div>
)
}
You could also recalculate the value in the useEffect method, as it will get called as soon as the component initially renders but not on any subsequent rerenders (due to the empty brackets as the second parameter to the useEffect method)
Have you tried to enter some dependecies to your useEffect hook ?
Like :
useEffect(() => {
console.log('hello');
}, [isLoad];
Here you're component will re-render only if isLoad is changing.

Calling setState with nested object does not update state correctly

I have a React component that maintains state for several child components. Via componentDidMount() I am calling this function in the parent component from the child components:
change = (fieldset, field, data) => {
this.setState({
[fieldset]: {
...this.state[fieldset],
[field]: data,
}
})
}
Think form/fieldset/field for the usage pattern, but with fields calling the above function.
The problem I'm having is that I believe I'm confusing React by calling this function so many times in quick succession, because state is not updated for all but one or two items.
I've tried using Object.assign() to avoid mutating state, but for the most part state has not updated correctly even at the point where I begin to read current start.
Is this against React best practices? Is there a better way for child components to call setState in a parent component?
Since the way you update the state depends on the state itself you need to use a function instead of an object.
change = (fieldset, field, data) => {
this.setState(prevState => ({
[fieldset]: {
...prevState[fieldset],
[field]: data,
}
}))
}
Functions will be applied one after another. So you wont override any pending changes.
From docs
this.setState({quantity: this.state.quantity + 1})
this.setState({quantity: this.state.quantity + 1})
Subsequent calls will override values from previous calls in the same
cycle, so the quantity will only be incremented once. If the next
state depends on the previous state, we recommend using the updater
function form, instead.

Redux or setTimeout - how to watch props changes in child component?

I have App component, which loads JSON data from the server.
And after data is loaded, I update state of child component.
Now my function looks like this:
componentDidMount() {
setTimeout(()=> {
if (this.state.users.length !== this.props.users.length) {
this.setState({users: this.props.users});
this.setState({tasks: this.getTasksArray()});
}, 500);
}
I use setTimeout to wait if data is loaded and sent to child. But I'm sure, it is not the best way
May be, it's better to use redux instead of setTimeout.
Parent component loads data:
componentWillMount() {
var _url = "/api/getusers/";
fetch(_url)
.then((response) => response.json())
.then(users => {
this.setState({ users });
console.log("Loaded data:", users);
});
}
Parent sends props with:
<AllTasks users={this.state.users} />
So, my question is: what is the best way to watch changes in child component?
I mean in this particular situation.
Yes, this is not the correct way because api calls will be asynchronous and we don't know how much time it will take.
So instead of using setTimeout, use componentWillReceiveProps lifecycle method in child component, it will get called whenever you change props values (state of parent component).
Like this:
componentWillReceiveProps(newProps){
this.setState({
users: newProps.users,
tasks: this.getTasksArray()
})
}
One more thing, don't call setState multiple times within a function because setState will trigger re-rendering so first do all the calculations then do setState in the last and update all the values in one call.
As per DOC:
componentWillReceiveProps() is invoked before a mounted component
receives new props. If you need to update the state in response to
prop changes (for example, to reset it), you may compare this.props
and nextProps and perform state transitions using this.setState() in
this method.
Update:
You are calling a method from cWRP method and using the props values inside that method, this.props will have the updated values after this lifecycle method only. So you need to pass the newProps values as a parameter in this function and use that instead of this.props.
Like this:
componentWillReceiveProps(newProps){
this.setState({
users: newProps.users,
tasks: this.getTasksArray(newProps)
})
}
getTasksArray(newProps){
//here use newProps instead of this.props
}
Check this answer for more details: componentWillRecieveProps method is not working properly: ReactJS
I found the problem.
I have getTasksArray() function that depends of props and uses this.props.users.
So, when I update state like this:
this.setState({
users: newProps.users,
tasks: this.getTasksArray()
})
getTasksArray() function uses empty array.
but when I split it to 2 lines and add setTimeout(fn, 0) like this:
this.setState({users: newProps.users});
setTimeout(()=> { this.setState({ tasks: this.getTasksArray() }, 0)
getTasksArray() function uses array that is already updated.
setTimeout(fn, 0) makes getTasksArray() to run after all other (even if I set timeout to 0 ms).
Here is the screenshot of console.log's without setTimeout:
And here is the screenshot with setTimeout:

Categories