Children components are not re rendered on forceUpdate() in React - reactjs

I have a callback method that calls a forceUpdate() hook in parent. I expect this to re render and call ChildA with updated props. These values are updated in another component say ChildB.
While I keep a debugger at callback method, I see updated values for props and Im getting a hit to return method as well. But the child component is not getting hit at all.
const Body = FC =>{
const [state, setState] = useState<any>();
const forceUpdate = useForceUpdate();
const update = useCallback(() => forceUpdate(), []);
return (
//able to see updated state here when update() is called
//but execution is not going inside ChildA
//even use effects on updated state are not getting triggered
<ChildA
state = {state}
/>
<ChildB
update = {update}
/>
)
}
Existing hook:
const reducer = (state: boolean, _action: null): boolean => !state;
export const useForceUpdate = () => {
const [, dispatch] = useReducer(reducer, true);
// Turn dispatch(required_parameter) into dispatch().
const memoizedDispatch = useMemo(
() => () => {
dispatch(null);
},
[dispatch]
);
return memoizedDispatch;
};

When I changed the existing hook into the below format, this worked for me.
function useForceUpdate() {
const [, forceUpdate] = useReducer(x => x + 1, 0);
const memoizedUpdate = useMemo(
() => () => {
forceUpdate(0);
},
[forceUpdate]
);
return memoizedUpdate;
};

Related

A variable doesn't update when using the useState() hook

I'm doing a social networking project on React
I wanted to replace one component from class - to functional and use hooks, and a global problem appeared:
When I go to a new user, the page displays the status of the previous one
I use useState() hook, debugged everything, but for some reason when a new status component is rendered, it doesn't update
const ProfileStatus = (props) => {
const [edditMode, setEdditMode] = useState(false);
const [status, setValue] = useState(props.status || "Empty");
const onInputChange = (e) => {
setValue(e.target.value);
};
const activateMode = () => {
setEdditMode(true);
};
const deactivateMode = () => {
setEdditMode(false);
props.updateUserStatus(status);
};
I thought the problem was that the container component was still a class component, but by redoing it, nothing has changed
One way to solve this is by using the useEffect hook to trigger an update when props change. You can use the hook to do comparison between current props and previous props, then update status in the state.
Use this as reference and adapt according to your own code.
const ProfileStatus = (props) => {
const [edditMode, setEdditMode] = useState(false);
const [status, setValue] = useState(props.status || "Empty");
useEffect(() => {
setValue(props.status || "Empty");
}, [props.status]);
const onInputChange = (e) => {
setValue(e.target.value);
};
const activateMode = () => {
setEdditMode(true);
};
const deactivateMode = () => {
setEdditMode(false);
props.updateUserStatus(status);
};

React using setState within useEffect() problem [duplicate]

This question already has answers here:
The useState set method is not reflecting a change immediately
(15 answers)
Closed 5 months ago.
N.B. I got my answer here but it is not the duplicate question of this thread
I am trying to fetch data from a reusable function that has an API. Here is my code
usaData.js in another page
const useData = () => {
const [data, setData] = useState([]);
const fetchData = async (url, query, variable) => {
const queryResult = await axios.post(url, {
query: query,
variables: variable,
});
setData(queryResult.data.data);
};
return {data, fetchData}
};
I am retrieving data from this MainPage.js file
export const MainPage = props => {
const [state, setState] = useState({
pharam: 'Yes',
value: '',
});
const [field, setField] = useState([])
const {data, fetchData} = useData()
const onClick = (event) => {
setState({ ...state, pharam: '', value: event });
fetchData(url, query, event)
setField(data)
}
return (
<div>
...
<Select
placeholder='select'
>
{field.map(item => (
<Select.Option key={item.name}>
{item.name}
</Select.Option>
))}
</Select>
<Button onClick={onClick}>Change Select</Button>
...
</div>
)
}
The problem is setField(data) within onClick function is not updating immediately as it is a async call. Hence I tried to use a function as a second argument
...
setField(data, () => {
console.log(data)
})
...
It is returning the following warning in red color but the behavior is similar to earlier, not updating data immediately.
Warning: State updates from the useState() and useReducer() Hooks don't support the second callback argument. To execute a side effect after rendering, declare it in the component body with useEffect().
As per the warning then I tried to use useEffect() within the onClick function
...
const onClick = (event) => {
setState({ ...state, pharam: '', value: event });
useEffect(() => {
fetchData(url, query, event)
setField(data)
}, [data])
}
...
which is returning an error
React Hook "useEffect" is called in function "onClick" that is neither a React function component nor a custom React Hook function. React component names must start with an uppercase letter. React Hook names must start with the word "use"
Where do I have to make changes? How can I get expected behavior as the setField will update the field immediately?
My suggestion would be to not setState in your custom hook rather than return promise.
usaData.js
const useData = () => {
const fetchData = async (url, query, variable) => {
return await axios.post(url, {
query: query,
variables: variable,
});
};
return { fetchData };
};
In MainPage.js
Now when you trigger your onClick function just call your fetchData function with await or then syntax and after successfully api call you'll get back the result in the newData variable which you can use it to update your state.
Note: this will save you an extra useEffect.
export const MainPage = (props) => {
const [state, setState] = useState({
pharam: "Yes",
value: "",
});
const [field, setField] = useState([]);
const { fetchData } = useData();
const onClick = async (event) => {
setState({ ...state, pharam: "", value: event });
let newData = await fetchData(url, query, event);
console.log("===>", newData.data.data);
setField(newData.data.data);
};
return (
<div>
...
<Select placeholder="select">
{field.map((item) => (
<Select.Option key={item.name}>{item.name}</Select.Option>
))}
</Select>
<Button onClick={onClick}>Change Select</Button>
...
</div>
);
};
The problem in your case is that setField gets calls before your data is fetched.
So, you can have a useEffect which gets executed every time the data gets changed.
useEffect(() => {
if(data.length > 0) {
setField(data);
}
}, [data])
const onClick = (event) => {
setState(prev => ({ ...prev, pharam: '', value: event }));
fetchData(url, query, event);
}
As far I know, React sets its state asynchronously. So, in order to update the state Field, you need an useEffect hook. Your approch with useEffect is correct, except it neeed to be placed outside onClick (directly in the component function).
export const MainPage = () => {
...
useEffect(() => {
setField(data)
},[data])
...
}

undefined children on react parent. However when console.log state is ldefined

I'm trying to load a component using useEffect hook and axios. However, when loading the page the components are not render, but when inspecting the parent component, his children are undefined (I'm very new to react)
Component
export const BonusRows = () => {
const [state, setState] = useState([]);
const [order, setOrder] = useState("asc");
const bonusRequest = () => {
axios
.get(`http://localhost:4000/api/v1/process/creator/gutierad5`)
.then((res) => {
const bonus = res.data;
const processes = bonus.processes;
setState(processes);
console.log(processes);
});
};
useEffect(() => {
bonusRequest();
}, []);
return (
<ProcessTable
funcionality={() => {
sortByDate(state, setState, order, setOrder);
}}
>
<Accordion allowToggle allowMultiple>
{state.map((element, index) => {
<AccordionItemSlot
key={index}
proccessID={element.id}
title={element.name}
targeruser='gutierad5'
createDate={FormatDateInYYMMDD(element.createdAt)}
status={element.status}
creator={element.creator}
links={element.links}
amount={element.processBonus.amount}
updatedAt={element.updatedAt}
password={element.processBonus.password}
franchise={element.processBonus.franchise}
/>;
})}
</Accordion>
</ProcessTable>
);
};
I don't know what I'm doing wrong. My best guess is that the state is not present when the component is loaded, so thet's the reasong is undefined. However when console log the state, the data is there.

How to access state inside a listener inside a useEffect?

I understand that without dependencies you cannot access state inside useEffect. An example as such :
const [state, setState] = React.useState('')
const ws = new WebSocket(`ws://localhost:5000`)
React.useEffect(() => {
ws.onopen = () => {
console.log(state)
},[])
In this case state comes back as empty even though it has changed in other parts of the code. To solve this in principle I should add state as dependency to the useEffect hook. However, this will trigger the listener again and I do not want to have 2 active listeners on my websocket or being forced to close and reopen it again as such :
React.useEffect(() => {
ws.onopen = () => {
console.log(state)
},[state])
What is good practice when it comes to accessing state inside a listener that sits inside useEffect hook?
IF you need to re-render the component when the state changes try this:
const [state, setState] = React.useState('');
const stateRef = React.useRef(state);
React.useEffect(() => {
stateRef.current = state;
}, [state])
const ws = React.useMemo(() => {
const newWS = new WebSocket(`ws://localhost:5000`);
newWS.onopen = () => {
console.log(stateRef.current);
}
return newWS;
}, []);
This way you create the ws only once, and it will use the state reference which will be up to date because of the useEffect.
If you don't need to re-render the component when the state updates you can remove const [state, setState] = React.useState(''); and the useEffect and just update the stateRef like this when you need.
Like this:
const stateRef = React.useRef(null);
const ws = React.useMemo(() => {
const newWS = new WebSocket(`ws://localhost:5000`);
newWS.onopen = () => {
console.log(stateRef.current);
}
return newWS;
}, []);
// Update the state ref when you need:
stateRef.current = newState;
Best Practice get data in listeners.[UPDATED]!
const [socketData , setSocketData] = useState(null);
useEffect(() => {
websocet.open( (data) => {
setSocketData(data);
})
},[])
//second useEffect to check socketData
useEffect(() => {
if(socketData){
// access to data which come from websock et
}
},[socketData])

How do I get the state from useSelector after dispatch is done?

I have a FormCheck component that calls the handleChange function on the onChange event. Task: change the state of the checkbox, and send a request to the server with the new state. The problem is that the submission is done later, i.e. the getSelectedCategories function is executed before the submission.
const data = useSelector((state) => state.products);
const boxes = getBoxes(data);
const handleChange = (id) => {
dispatch(setFilterCategory(id));
const selectedCategories = getSelectedCategories(data);
console.log(selectedCategories); // This is done before the dispatch while the state changes
dispatch(fetchProducts(selectedCategories);
};
return (
{boxes.map(item => {
return <FormCheck label={item.value} checked={item.isChecked} onChange={() => handleChange(item.id)}
})}
);
By definition, callbacks defined in the body of a function component can only access state, props, and values that existed when the callback was created at the time the component rendered.
If you're dispatching an action, it's impossible for that code to access the new Redux state (or even React state) on the next line.
If you do need to access the new Redux state immediately, you can do that via a thunk, which has access to getState:
const updateCategoryAndFetch = (category) => {
return (dispatch, getState) => {
dispatch(setFilterCategory(id));
const selectedCategories = getSelectedCategories(getState());
dispatch(fetchProducts(selectedCategories)
}
}
// later, in the component:
const handleChange = (id) => {
dispatch(updateCategoryAndFetch(id));
}
This is what useEffect() is made for:
const data = useSelector((state) => state.products);
const selectedCategories = getSelectedCategories(data);
const handleChange = (id) => {
dispatch(setFilterCategory(id));
};
// is executed whenever selectedCategories changes
useEffect(() => {
dispatch(fetchProducts(selectedCategories);
}, [selectedCategories])

Resources