Set data in React hooks? - javascript

This is my code.
const initialNoticeState = {
id: props,
title : '',
description : '',
image : '',
updated_by : user.firstName+' '+user.lastName,
priority : ''
};
const [currentNotice, setCurrentNotice] = useState(initialNoticeState);
const notice = useSelector(state => state.notices.currentNotice)
const [noticeImage, setNoticeImage] = useState("");
const [imgData, setImgData] = useState(null);
const dispatch = useDispatch();
useEffect(() => {
dispatch(noticeActions.getById(props.id))
}, [props.id]);
I want to setCurrentNotice from notice value, just after dispatch finishes.
Here is the data of notice:

I want to setCurrentNotice from notice value, just after dispatch
finishes.
useEffect can be used to execute functions when variable is changed.
useEffect(() => {
setCurrentNotice(notice)
}, [notice]); //notice is the dependency

Just Like that
setCurrentNotice({your current notice data})

Related

event.target.value omitting last character

i've been solving this problem without any progress for the pas 2 hours or so, here is code:
export const useFetchAll = () => {
const [searchResult, setSearchResult] = useState([]);
const [loading, setLoading] = useState(false);
const [searchItem, setSearchItem] = useState("");
const [listToDisplay, setListToDisplay] = useState([]);
// const debouncedSearch = useDebounce(searchItem, 300);
const handleChange = (e) => {
setSearchItem(e.target.value);
if (searchItem === "") {
setListToDisplay([]);
} else {
setListToDisplay(
searchResult.filter((item) => {
return item.name.toLowerCase().includes(searchItem.toLowerCase());
})
);
}
console.log(searchItem);
};
useEffect(() => {
const searchRepo = async () => {
setLoading(true);
const { data } = await axios.get("https://api.github.com/repositories");
setSearchResult(data);
setLoading(false);
};
if (searchItem) searchRepo();
}, [searchItem]);
the problem is that when i enter characters in input and set state to event.target.value it doesn't pick up last character. here is an image:
enter image description here
BTW this is a custom hook, i return the onchange function here:
const HomePage = () => {
const { searchResult, loading, searchItem, handleChange, listToDisplay } =
useFetchAll();
and then pass it as a prop to a component like so:
<Stack spacing={2}>
<Search searchItem={searchItem} handleChange={handleChange} />
</Stack>
</Container>
any help? thanks in advance.
You are handling the searchItem and searchResult state variables as if their state change was synchronous (via setSearchItem and setSearchResult) but it isn't! React state setters are asynchronous.
The useEffect callback has a dependency on the searchItem state variable. Now every time the user types something, the state will change, that change will trigger a re-rendering of the Component and after that render finishes, the side-effect (the useEffect callback) will be executed due to the Components' lifecycle.
In our case, we don't want to initiate the fetch request on the next render, but right at the moment that the user enters something on the search input field, that is when the handleChange gets triggered.
In order to make the code work as expected, we need some a more structural refactoring.
You can get rid of the useEffect and handle the flow through the handleChange method:
export const useFetchAll = () => {
const [ loading, setLoading ] = useState( false );
const [ searchItem, setSearchItem ] = useState( "" );
const [ listToDisplay, setListToDisplay ] = useState( [] );
const handleChange = async ( e ) => {
const { value } = e.target;
// Return early if the input is an empty string:
setSearchItem( value );
if ( value === "" ) {
return setListToDisplay( [] );
}
setLoading( true );
const { data } = await axios.get( "https://api.github.com/repositories" );
setLoading( false );
const valueLowercase = value.toLowerCase(); // Tiny optimization so that we don't run the toLowerCase operation on each iteration of the filter process below
setListToDisplay(
data.filter(({ name }) => name.toLowerCase().includes(valueLowercase))
);
};
return {
searchItem,
handleChange,
loading,
listToDisplay,
};
};
function used for updating state value is asynchronous that why your state variable is showing previous value and not the updated value.
I have made some change you can try running the below code .
const [searchResult, setSearchResult] = useState([]);
const [loading, setLoading] = useState(false);
const [searchItem, setSearchItem] = useState("");
const [listToDisplay, setListToDisplay] = useState([]);
// const debouncedSearch = useDebounce(searchItem, 300);
const handleChange = (e) => {
setSearchItem(e.target.value); // this sets value asyncronously
console.log("e.target.value :" + e.target.value); // event.target.value does not omitting last character
console.log("searchItem :" + searchItem); // if we check the value then it is not set. it will update asyncronously
};
const setList = async () => {
if (searchItem === "") {
setListToDisplay([]);
} else {
setListToDisplay(
searchResult.filter((item) => {
return item.name.toLowerCase().includes(searchItem.toLowerCase());
})
);
}
};
const searchRepo = async () => {
const { data } = await axios.get("https://api.github.com/repositories");
setSearchResult(data);
setLoading(false);
};
// this useeffect execute its call back when searchItem changes a
useEffect(() => {
setList(); // called here to use previous value stored in 'searchResult' and display something ( uncomment it if you want to display only updated value )
if (searchItem) searchRepo();
}, [searchItem]);
// this useeffect execute when axios set fetched data in 'searchResult'
useEffect(() => {
setList();
}, [searchResult]);
// this useeffect execute when data is updated in 'listToDisplay'
useEffect(() => {
console.log("filtered Data") // final 'listToDisplay' will be availble here
console.log(listToDisplay)
}, [listToDisplay]);

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
}, []);

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);
};
};

Can't post using useEffect and axios

I'm trying to POST new users using useEffect(). I was able to do using componentDidMount.
My hooks are working okay (outside the useEffect), but when It comes to the api.post. The code doesn't post anything. I tried to throw the hook inside the useEffect() but says("Invalid Hook call"). I used some hooks to handle the inputs and when I press a button change the state of my hook normally, but the api doesn't post to my DB.
The code enters in the useEffect() but doesn't execute the api.post. Anyone can help me on this? I'm hunting this bug for 2 days. Here is my code:
//Hooks
const [name, setUserName] = useState('');
const [userEmail, setEmail] = useState('');
const [userPassword, setPassword] = useState('');
const [avatar, setAvatar] = useState('');
const [enteredUserName, setEnteredUserName] = useState('');
const [enteredEmail, setEnteredEmail] = useState('');
const [enteredPassword, setEnteredPassword] = useState('')
useEffect((userName, email, password) => {
api.post('users/signup', {userName: name , email: userEmail, password: userPassword}).then(data => {
navigation.navigate('Home');
}).catch(err => {
if (err === true) {
Alert.alert('Invalid Credentials')
}
}, []);
})
const userNameInputHandler = (enteredText) => {
setEnteredUserName(enteredText)
};
const userEmailInputHandler = (enteredText) => {
setEnteredEmail(enteredText);
};
const userPasswordHandler = (enteredText) => {
setEnteredPassword(enteredText);
};
const handler = () => {
setUserName(currentUserNames => [...currentUserNames, enteredUserName]);
setEmail(currentEmails => [...currentEmails, enteredEmail]);
setPassword(currentPasswords => [...currentPasswords, enteredPassword]);
};
The handler is my Register button. When pressed call the handler().
useEffect is calling once because you passes [] at the end. i.e
useEffect(()=>{},[]);
if you want to execute useEffect on change of something then pass that as dependency i.e []
if you're calling this function on button press, then it must be outside of useEffect.
also when you want to use hooks with an async function, you have to declare it outside of the hook and then calling it inside the hook; like:
`
const login = async () => {};
useEffect(login(),[]);
`
You may create the lifeCycle hook useComponentDidMount.js
export const useComponentDidMount = (cb) => {
useEffect(() => {
cb();
}, []);
};
and use it like
useComponentDidMount(() => {
axios.fetch(...)
})
useEffect have any arguments, please remove userName, email, password
and use like
useEffect(() => {
api.post('users/signup', {userName: name , email: userEmail, password: userPassword}).then(data => {
navigation.navigate('Home');
}).catch(err => {
if (err === true) {
Alert.alert('Invalid Credentials')
}
}, []);
})

infinite loop when querying api in redux action

I am attempting to query my Firebase backend through a redux-thunk action, however, when I do so in my initial render using useEffect(), I end up with this error:
Error: Maximum update depth exceeded. This can happen when a component repeatedly calls setState inside componentWillUpdate or componentDidUpdate. React limits the number of nested updates to prevent infinite loops.
My action simply returns a Firebase query snapshot which I then received in my reducer. I use a hook to dispatch my action:
export const useAnswersState = () => {
return {
answers: useSelector(state => selectAnswers(state)),
isAnswersLoading: useSelector(state => selectAnswersLoading(state))
}
}
export const useAnswersDispatch = () => {
const dispatch = useDispatch()
return {
// getAnswersData is a redux-thunk action that returns a firebase snapshot
setAnswers: questionID => dispatch(getAnswersData(questionID))
}
}
and the following selectors to get the data I need from my snapshot and redux states:
export const selectAnswers = state => {
const { snapshot } = state.root.answers
if (snapshot === null) return []
let answers = []
snapshot.docs.map(doc => {
answers.push(doc.data())
})
return answers
}
export const selectAnswersLoading = state => {
return state.root.answers.queryLoading || state.root.answers.snapshot === null
}
In my actual component, I then attempt to first query my backend by dispatching my action, and then I try reading the resulting data once the data is loaded as follows:
const params = useParams() // params.id is just an ID string
const { setAnswers, isAnswersLoading } = useAnswersDispatch()
const { answers } = useAnswersState()
useEffect(() => {
setAnswers(params.id)
}, [])
if (!isAnswersLoading)) console.log(answers)
So to clarify, I am using my useAnswersDispatch to dispatch a redux-thunk action which returns a firebase data snapshot. I then use my useAnswersState hook to access the data once it is loaded. I am trying to dispatch my query in the useEffect of my actual view component, and then display the data using my state hook.
However, when I attempt to print the value of answers, I get the error from above. I would greatly appreciate any help and would be happy to provide any more information if that would help at all, however, I have tested my reducer and the action itself, both of which are working as expected so I believe the problem lies in the files described above.
Try refactoring your action creator so that dispatch is called within the effect. You need to make dispatch dependent on the effect firing.
See related
const setAnswers = (params.id) => {
const dispatch = useDispatch();
useEffect(() => {
dispatch(useAnswersDispatch(params.id));
}, [])
}
AssuminggetAnswersData is a selector, the effect will trigger dispatch to your application state, and when you get your response back, your selector getAnswersData selects the fields you want.
I'm not sure where params.id is coming from, but your component is dependent on it to determine an answer from the application state.
After you trigger your dispatch, only the application state is updated, but not the component state. Setting a variable with useDispatch, you have variable reference to the dispatch function of your redux store in the lifecycle of the component.
To answer your question, if you want it to handle multiple dispatches, add params.id and dispatch into the dependencies array in your effect.
// Handle null or undefined param.id
const answers = (param.id) => getAnswersData(param.id);
const dispatch = useDispatch();
useEffect(() => {
if(params.id)
dispatch(useAnswersDispatch(params.id));
}, [params.id, dispatch]);
console.log(answers);
As commented; I think your actual code that infinite loops has a dependency on setAnswers. In your question you forgot to add this dependency but code below shows how you can prevent setAnswers to change and cause an infinite loop:
const GOT_DATA = 'GOT_DATA';
const reducer = (state, action) => {
const { type, payload } = action;
console.log('in reducer', type, payload);
if (type === GOT_DATA) {
return { ...state, data: payload };
}
return state;
};
//I guess you imported this and this won't change so
// useCallback doesn't see it as a dependency
const getAnswersData = id => ({
type: GOT_DATA,
payload: id,
});
const useAnswersDispatch = dispatch => {
// const dispatch = useDispatch(); //react-redux useDispatch will never change
//never re create setAnswers because it causes the
// effect to run again since it is a dependency of your effect
const setAnswers = React.useCallback(
questionID => dispatch(getAnswersData(questionID)),
//your linter may complain because it doesn't know
// useDispatch always returns the same dispatch function
[dispatch]
);
return {
setAnswers,
};
};
const Data = ({ id }) => {
//fake redux
const [state, dispatch] = React.useReducer(reducer, {
data: [],
});
const { setAnswers } = useAnswersDispatch(dispatch);
React.useEffect(() => {
setAnswers(id);
}, [id, setAnswers]);
return <pre>{JSON.stringify(state.data)}</pre>;
};
const App = () => {
const [id, setId] = React.useState(88);
return (
<div>
<button onClick={() => setId(id => id + 1)}>
increase id
</button>
<Data id={id} />
</div>
);
};
ReactDOM.render(<App />, document.getElementById('root'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>
<div id="root"></div>
Here is your original code causing infinite loop because setAnswers keeps changing.
const GOT_DATA = 'GOT_DATA';
const reducer = (state, action) => {
const { type, payload } = action;
console.log('in reducer', type, payload);
if (type === GOT_DATA) {
return { ...state, data: payload };
}
return state;
};
//I guess you imported this and this won't change so
// useCallback doesn't see it as a dependency
const getAnswersData = id => ({
type: GOT_DATA,
payload: id,
});
const useAnswersDispatch = dispatch => {
return {
//re creating setAnswers, calling this will cause
// state.data to be set causing Data to re render
// and because setAnser has changed it'll cause the
// effect to re run and setAnswers to be called ...
setAnswers: questionID =>
dispatch(getAnswersData(questionID)),
};
};
let timesRedered = 0;
const Data = ({ id }) => {
//fake redux
const [state, dispatch] = React.useReducer(reducer, {
data: [],
});
//securit to prevent infinite loop
timesRedered++;
if (timesRedered > 20) {
throw new Error('infinite loop');
}
const { setAnswers } = useAnswersDispatch(dispatch);
React.useEffect(() => {
setAnswers(id);
}, [id, setAnswers]);
return <pre>{JSON.stringify(state.data)}</pre>;
};
const App = () => {
const [id, setId] = React.useState(88);
return (
<div>
<button onClick={() => setId(id => id + 1)}>
increase id
</button>
<Data id={id} />
</div>
);
};
ReactDOM.render(<App />, document.getElementById('root'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>
<div id="root"></div>
You just need to add params.id as a dependency.
Don't dispatch inside the function which you are calling inside useEffect but call another useEffect to dispatch
const [yourData, setyourData] = useState({});
useEffect(() => {
GetYourData();
}, []);
useEffect(() => {
if (yourData) {
//call dispatch action
dispatch(setDatatoRedux(yourData));
}
}, [yourData]);
const GetYourData= () => {
fetch('https://reactnative.dev/movies.json')
.then((response) => response.json())
.then((json) => {
if (result?.success == 1) {
setyourData(result);
}
})
.catch((error) => {
console.error(error);
});
};

Categories