Invalid hook call with nested component call - javascript

I have the following code in my App.jsx:
render() {
return (
<BrowserView>
<CreateSession /> // works just fine
<QrCode address={CreateSession(this)} /> // throws 'Error: Invalid hook call.'
</BrowserView>)
}
CreateSession returns a string, which is fed into QrCode, to generate a Qr Code. My CreateSession looks like this:
const CreateSession = (props) => {
const userVideo = useRef();
const partnerVideo = useRef();
const peerRef = useRef();
const socketRef = useRef();
const otherUser = useRef();
const userStream = useRef();
useEffect(() => {
socketRef.current = io.connect("/");
socketRef.current.emit("join session", props.match.params.roomID);
// lots of code omitted, source is: https://github.com/coding-with-chaim/native-webrtc/blob/master/client/src/routes/Room.js
return uuid();
};
export default CreateSession;
What is the correct way to call CreateSession so that it returns the uuid right into QrCode? I am aware that I could have a state property in the App.jsx that gets set to uuid, that is then passed into QrCode, but is it possible to do it this way?

You can turn your CreateSession component into a wrapper.
const CreateSession = (props) => {
const userVideo = useRef();
const partnerVideo = useRef();
const peerRef = useRef();
const socketRef = useRef();
const otherUser = useRef();
const userStream = useRef();
const [uuid, setUuid] = useState(null);
useEffect(() => {
socketRef.current = io.connect("/");
socketRef.current.emit("join session", props.match.params.roomID);
// lots of code omitted, source is: https://github.com/coding-with-chaim/native-webrtc/blob/master/client/src/routes/Room.js
setUuid(uuid());
});
if (uuid === null) {
return null;
}
return (<>{props.children(uuid)}</>)
};
export default CreateSession;
Here is the usage.
render() {
return (
<BrowserView>
<CreateSession>
{(uuid) => (<QrCode address={uuid} />)}
</CreateSession>
</BrowserView>
)
}

Related

Refactoring a class component to Functional, ReferenceError

I am trying to refactor a class component, but in the class one, there is a state with map and I tried changing it to Functional and used useState but it keeps giving me this error
ReferenceError: Cannot access 'rules' before initialization
it happens when I'm trying to refactor the State of rules(which I'm not sure how), with map, to useState. Is it even the correct way of assigning state for map and how can I fix it?
the class component :
import Rule from "./Rule";
class Game extends Component {
state = {
dices: Array(this.props.nOfDices).fill(1),
locked: Array(this.props.nOfDices).fill(false),
rotation: Array(this.props.nOfDices).fill(0),
rollsRemaining: 3,
isRolling: false,
rules: this.props.rules.map( r => ({...r})),
score: 0,
bestScore: window.localStorage.getItem("bestScore") || "0"
};
componentDidMount() {
this.roll();
};
my refactored functional component :
const Game = ({ nOfDices }) => {
const [isRolling, setisRolling] = useState(false);
const [score, setScore] = useState(0);
const [rollsRemaining, setRollsRemaining] = useState(3);
const [dices, setDices] = useState([Array(nOfDices).fill(1)]);
const [rules, setRules] = useState(rules.map(r => ({ ...r })));
const [bestScore, setBestScore] = useState(window.localStorage.getItem("bestScore") || "0");
const [locked, setLocked] = useState([Array(nOfDices).fill(false)]);
const [rotation, setRotation] = useState([Array(nOfDices).fill(0)]);
useEffect(() => {
roll();
//eslint-disable-next-line
}, []);
You are currently setting rules to a map of itself...
const [rules, setRules] = useState(rules.map(r => ({ ...r })));
should it be coming from props as it is in the original?
state = {
// ...
rules: this.props.rules.map( r => ({...r})),
// ...
}
If so you'll need to also destructure it out of props in the parameter declaration. (Here renaming it to avoid collision with the the state name Game = ({rules: _rules, nOfDices}) => ...)
Something like...
const Game = ({ rules: _rules, nOfDices }) => {
const [isRolling, setisRolling] = useState(false);
const [score, setScore] = useState(0);
const [rollsRemaining, setRollsRemaining] = useState(3);
const [bestScore, setBestScore] = useState(window.localStorage.getItem('bestScore') || '0');
// nOfDices
const [dices, setDices] = useState([Array(nOfDices).fill(1)]);
const [locked, setLocked] = useState([Array(nOfDices).fill(false)]);
const [rotation, setRotation] = useState([Array(nOfDices).fill(0)]);
// rules
const [rules, setRules] = useState(_rules.map((r) => ({ ...r })));
// update state if `nOfDices` changes in props
useEffect(() => {
setDices([Array(nOfDices).fill(1)]);
setLocked([Array(nOfDices).fill(false)]);
setRotation([Array(nOfDices).fill(0)]);
}, [nOfDices]);
// update state if `_rules` changes in props
useEffect(() => {
setRules(_rules.map((r) => ({ ...r })));
}, [_rules]);
useEffect(() => {
roll();
//eslint-disable-next-line
}, []);

how to listen page url change without page refresh in react?

I do have some breadcrumbs on the page. When I do replaceState I see that url change there is no problem with that but I couldn't catch that url change in react component I've tried but no sense it works only once when component mounted.
const usePath = () => {
const [path, pathSet] = useState(window.location.pathname);
const listenToPopstate = () => {
const winPath = window.location.pathname;
pathSet(winPath);
};
useEffect(() => {
window.addEventListener("locationchange", listenToPopstate);
return () => {
window.removeEventListener("locationchange", listenToPopstate);
};
}, []);
return path;
};
const Breadcrumb = () => {
const [state, stateSet] = useState(false);
const path = usePath();
useEffect(() => {
path.indexOf('StoreSelection') > -1 && stateSet(true)
}, [path]);
return (
<ol className="breadcrumb" style={breadcrumb}>
<li key="1" className="breadcrumb-item align-items-center">
{state ? 'AAA' : 'BBB'}
</li>
</ol>
);
}
I think you'll find instantiating a history object and using it to listen will be easier for you.
history
import { createBrowserHistory } from 'history';
const history = createBrowserHistory();
const usePath = () => {
const [path, pathSet] = useState(history.location.pathname);
const listener = (location, action) => {
pathSet(location.pathname);
};
useEffect(() => {
const unlisten = history.listen(listener);
return unlisten;
}, []);
return path;
};

Initialise helper class in a react functional component

The class methods which are passed as args from the functional component, are kept 'in memory' and doest not reflect the updated state. I can reinitialise on state changes but wish to avoid it.
const MyFunctional = (props) => {
const [state,setState] = useState(0);
const helper = useRef();
useEffect(()=>{
helper.current = new HelperClass(onSuccess,onFailure);
},[])
/* wish to avoid */
useEffect(()=>{
helper.current = new HelperClass(onSuccess,onFailure);
},[state])
const onSuccess = (result) =>{
/* Here state == 0 */
}
const onFailure = (error) =>{
/* Here state == 0 */
}
}
You'll need an additional ref to be able to use the latest values in an async callback.
Either
grab react-use's useLatest hook,
write one yourself according to the docs,
or steal this trivial reference implementation:
function useLatest(value) {
const ref = useRef(value);
ref.current = value;
return ref;
};
const MyFunctional = (props) => {
const [state, setState] = useState(0);
const latestStateRef = useLatest(state);
const helper = useRef();
useEffect(() => {
helper.current = new HelperClass(onSuccess, onFailure);
}, []);
const onSuccess = (result) => {
console.log(latestStateRef.current);
};
const onFailure = (error) => {
console.log(latestStateRef.current);
};
};

React setState hook is not working when trying to clear/empty/delete/set back to initial state

I have a clearState function which sets some useState hooks back to their initial state when the restart button is clicked. However, they say that my setState is not a function. Please check code below:
App.js
...
const [question, setQuestion] = useState(0);
const [response, setResponse] = useState({});
const [answer, setAnswer] = useState({});
const [answerId, setAnswerId] = useState({});
...
Modal.js
const Modal = ({
setResponse,
setAnswer,
setAnswerId,
setQuestion,
setAnswerNameArr,
}) => {
const [open, setOpen] = useState(false);
const clearState = () => {
setOpen(false); //works
setQuestion(0); //works
setAnswer({}); //does not work
setAnswerId({});
setResponse({});
setAnswerNameArr([]);
};
...
return (
<Modal
...
>
...
<Button
onClick={()=>handleSubmit()}
>
Restart
</Button>
</Modal>
);
};
export default Modal;
The error:
Uncaught TypeError: setAnswer is not a function
Thanks in advance.
It looks like you aren't passing your state setting hooks in to your <Modal> so they're not available.
It isn't a good idea to do that anyway, tbh. If you need a child to affect the state of a parent it would be better to pass a single call-back:
const Modal = ({
onSubmitCb
}) => {
const [open, setOpen] = useState(false);
const clearState = () => {
setOpen(false); //works
setQuestion(0); //works
onSubmitCb && onSubmitCb()
};
...
return (
<Modal>
...
<Button
onClick={()=>handleSubmit()}
>
Restart
</Button>
</Modal>
);
};
and in your parent:
const App = ()=>{
const clearState = () => {
setAnswer({});
setAnswerId({});
setResponse({});
setAnswerNameArr([]);
};
....
return {
<Modal ... onSubmitCb={clearState} />
}
}

rewrite componentDidUpdate(prevProps) into a hook with redux

I would like to rewrite this life cycle method into a hook but it does'nt work as expected.
when the componentdidmounted, if the user id exists in the local storage,the user is connected and his name is displayed in the navbar. And when he disconnects and reconnects his name is displayed in the navbar.
So i am trying to convert this class Component with hooks, when the username changes nothing is displayed in the navbar so i have to refresh the page and that way his name is displayed
The real problem is the componentDidUpdate
how can i get and compare the prevProps with hooks
The class Component
const mapStateToProps = state => ({
...state.authReducer
}
);
const mapDispatchToProps = {
userSetId,
userProfilFetch,
userLogout
};
class App extends React.Component {
componentDidMount() {
const userId = window.localStorage.getItem("userId");
const {userSetId} = this.props;
if (userId) {
userSetId(userId)
}
}
componentDidUpdate(prevProps, prevState, snapshot) {
const {userId, userProfilFetch, userData} = this.props; //from redux store
if(prevProps.userId !== userId && userId !== null && userData === null){
userProfilFetch(userId);
}
}
render() {
return (
<div>
<Router>
<Routes/>
</Router>
</div>
);
}
}
export default connect(mapStateToProps,mapDispatchToProps)(App);
With hooks
const App = (props) => {
const dispatch = useDispatch();
const userData = useSelector(state => state.authReducer[props.userData]);
const userId = window.localStorage.getItem("userId");
useEffect(()=> {
if(!userId){
dispatch(userSetId(userId))
dispatch(userProfilFetch(userId))
}
}, [userData, userId, dispatch])
return(
<Router>
<Routes/>
</Router>
)
};
export default App;
How to get the previous props or state?
Basically create a custom hook to cache a value:
const usePrevious = value => {
const ref = useRef();
useEffect(() => {
ref.current = value;
});
return ref.current;
}
Usage:
const App = (props) => {
const dispatch = useDispatch();
const userData = useSelector(state => state.authReducer[props.userData]);
const userId = window.localStorage.getItem("userId");
// get previous id and cache current id
const prevUserId = usePrevious(userId);
useEffect(()=> {
if(!userId){
dispatch(userSetId(userId))
dispatch(userProfileFetch(userId))
}
// do comparison with previous and current id value
if (prevUserId !== userId) {
dispatch(userProfileFetch(userId));
}
}, [userData, userId, prevUserId, dispatch])
return(
<Router>
<Routes/>
</Router>
)
};
FYI: You may want to refactor the code a bit to do the fetch from local storage in an effect hook that runs only on mount. If I understand your app flow correctly it would look something like this:
const App = (props) => {
const dispatch = useDispatch();
const { userId } = useSelector(state => state.authReducer[props.userData]);
useEffect(() => {
const userId = window.localStorage.getItem("userId");
userId && dispatch(userSetId(userId));
}, []);
// get previous id and cache current id
const prevUserId = usePrevious(userId);
useEffect(()=> {
if(!userId){
dispatch(userSetId(userId))
dispatch(userProfileFetch(userId))
}
// do comparison with previous and current id value
if (prevUserId !== userId) {
dispatch(userProfileFetch(userId));
}
}, [userId, prevUserId, dispatch])
return(
<Router>
<Routes/>
</Router>
)
};
now i resolve it, i made this
const App = props => {
const userId = window.localStorage.getItem("userId");
const dispatch = useDispatch();
const userData = useSelector(state=> state.authReducer[props.userData]);
const isAuthenticated = useSelector(state=> state.authReducer.isAuthenticated);
useEffect(()=> {
if(userId){
dispatch(userSetId(userId))
dispatch(userProfilFetch(userId))
}
}, [userId])
return(
<div>
<Router>
<Routes/>
</Router>
</div>
)
};

Categories