React state not available after using setState - javascript

I'm working on a trivia app using ReactjS and every time I try to update my state named "game", I get the following error message:
Uncaught TypeError: game is undefined.
My webapp is structured as follows:
App -> Question -> Answers.
in App:
const [game, setGame] = React.useState([]);
function holdAnswer(qKey) {
console.log(qKey);
setGame((oldGame) => {
oldGame.map((element) => {
return qKey === element.key ? {} : element;
});
});
console.log(qKey);
}
React.useEffect(function () {
console.log("Effect ran");
fetch("https://opentdb.com/api.php?amount=5")
.then((res) => res.json())
.then((data) =>
setGame(
data.results.map(function (element) {
return {
...element,
key: uniqid(),
answers: arrayShuffle([
...element.incorrect_answers.map(
(x) => new AnswerObj(x, false, uniqid())
),
new AnswerObj(element.correct_answer, false, uniqid()),
]),
};
})
)
);
console.log(game);
}, []);
var wholeQ = game.map((element) => {
return (
<Question wQ={element} holdAnswer={() => holdAnswer(element.key)} />
);
});
in Question Component:
export default function Question(props) {
const answers = arrayShuffle(props.wQ.answers).map((element) => {
return <Answers wholeAnswer={element} holdAnswer={props.holdAnswer} />;
});
return (
<div className="question">
<p>{decode(props.wQ.question)}</p>
<div className="answer-buttons-wrapper">{answers}</div>
</div>
);
}
in Answers Component:
export default function Answers(props) {
return (
<button className="answer-button" onClick={props.holdAnswer}>
{decode(props.wholeAnswer.answer)}
</button>
);
}

I believe the problem lies in the following block:
function holdAnswer(qKey) {
console.log(qKey);
setGame((oldGame) => {
oldGame.map((element) => {
return qKey === element.key ? {} : element;
});
});
console.log(qKey);
}
As setGame ends up not returning anything and therefor sets the state with an undefined value.
To address this we can remove the curly-braces in setGame in-order to make it an "implicit return".
Alternatively, we can add a return statement before the mapping function.
// Either this ->
setGame((oldGame) =>
oldGame.map((element) => {
return qKey === element.key ? {} : element;
});
);
// Or this ->
setGame((oldGame) => {
return oldGame.map((element) => {
return qKey === element.key ? {} : element;
});
});

Related

'x' refresh is not a function. (In 'x()', 'x' is an instance of Object)

How can I access to my refresh() method in my UpdateLokalListe function?
Is there any possibility to include the function in my class?
I used this guide: https://reactnavigation.org/docs/function-after-focusing-screen
Thanks
https://pastebin.com/NMfTS8tp
function UpdateLokalListe(refresh) {
useFocusEffect(
React.useCallback(() => {
refresh();
})
);
return null;
}
export default class LokaleBearbeitenScreen extends Component {
state = {
lokale: [],
isLoading: true,
};
_retrieveData = async () => {
...
};
_refresh = () => {
alert('refresh');
this.setState({ isLoading: true });
this._retrieveData();
};
componentDidMount() {
Firebase.init();
this._retrieveData();
}
render() {
...
return (
<>
<UpdateLokalListe refresh={this._refresh} />
...
</>
);
}
}
UpdateLokalListe looks like functional component, and you are passing refresh props
So change this :
UpdateLokalListe(refresh)
to :
UpdateLokalListe({refresh})
OR
function UpdateLokalListe(props) { // <---- Here
useFocusEffect(
React.useCallback(() => {
props.refresh(); // <---- Here
})
);
return null;
}

ReactJS: First useEffect doesn't update state before the second is triggered

I have a Chat component which uses API to populate the messages state, also there are different areas that have different chats which I pass as props to the component.
In this component I have 3 useEffects but I am interested in two of them which don't work properly. In the first useEffect I have some code that basically resets the messages state on area change to undefined. I need to do this to be able to distinguish between the API not being called yet where I display a loading component <Spinner /> or if the API has been called and it has retrieved an empty array to show the <NoData> component.
The problem that I have is that when I change areas the useEffects get triggered as they should but the first useEffect doesn't update the messages state to undefined before the second useEffect is called. And after a rerender because of history push the messages come as undefined but then the second useEffect doesn't get triggered anymore. I don't get why the state is not being updated in the first useEffect before the second. Also the weird thing is this used to work for me before now it doesn't. I changed some stuff up without pushing to git and now I am puzzeled. Code below:
export default function ChatPage({ history, match, area, ...props }) {
const [templates, setTemplates] = useState([]);
const [advisors, setAdvisors] = useState([]);
const [messages, setMessages] = useState(undefined);
const [conversation, setConversation] = useState([]);
const [chatToLoad, setChatToLoad] = useState(false);
const [isOpen, setIsOpen] = useState(false);
const [linkOrigin, setLinkOrigin] = useState("");
const [headerText, setHeaderText] = useState("");
// useEffect used to reset messages and conversation state
// triggered on area change(messages and conversation reset)
// and customer ID change(conversation reset).
// Required to distinguish between API call not being made yet
// and API returning no data.
useEffect(() => {
if (match.params.id) {
setLinkOrigin(match.params.id);
}
if (messages) {
if (match.params.id && messages.length !== 0) {
let matches = messages.filter(
(message) => message.ORIGINATOR === match.params.id
);
if (matches.length !== 0 && match.params.id === linkOrigin) {
setMessages(undefined);
history.push("/chats/" + match.params.area);
}
}
}
setConversation([]);
}, [area, match.params.id]);
// API calls
useEffect(() => {
if (templates.length === 0) {
api.getTemplates().then((templates) => {
setTemplates(templates);
});
}
if (advisors.length === 0) {
api.getAgents().then((advisors) => {
setAdvisors(advisors);
});
}
if (!messages || messages.length === 0) {
chooseQueue(match.params.area).then((queuesData) => {
let queues = queuesData.data.map((message) => ({
DATE_SORT: message.DATE_RECIEVED,
UNIQUEID: message.UNIQUEID,
ORIGINATOR: message.ORIGINATOR,
MESSAGE: message.MESSAGE,
MSG_TYPE: "SMS_OUTBOUND",
ASSIGNED_TO: message.ASSIGNED_TO || null,
}));
setMessages(orderMessagesByDate(queues));
setChatToLoad(queues[0]);
});
}
}, [area]);
useEffect(() => {
if (messages) {
if (messages.length) {
let loadId = match.params.id ? match.params.id : messages[0].ORIGINATOR;
const params = {
MobileNumber: loadId,
};
messagingApi.conversationHistory(params).then((conversationData) => {
setConversation(
conversationData.data.map((message) => ({
DATE_SORT: message.DATE_SORT,
UNIQUEID: message.UNIQUEID,
ORIGINATOR: message.ORIGINATOR,
MESSAGE: message.MESSAGE,
MSG_TYPE: message.MSG_TYPE2.replace("MobileOriginated", "SMS"),
ASSIGNED_TO: message.ASSIGNED_TO || null,
}))
);
});
setChatToLoad(
messages.find((message) => message.ORIGINATOR === loadId)
);
history.push("/chats/" + match.params.area + "/" + loadId);
}
}
}, [messages]);
function chooseQueue(queueType) {
switch (queueType) {
case "myqueue":
setHeaderText("My chats");
return queuesApi.getMyActiveQueues(area);
case "mycompleted":
setHeaderText("My completed chats");
return queuesApi.getMyCompletedQueues();
case "queues":
setHeaderText("Chats");
return queuesApi.getQueues(area);
case "completed":
setHeaderText("Completed chats");
return queuesApi.getCompletedQueues();
default:
setHeaderText("My chats");
return queuesApi.getQueues(area);
}
}
function classifyMessage(message) {
return message.MSG_TYPE.includes("OUTBOUND") ||
message.MSG_TYPE.includes("FAULT_TEST")
? "outbound"
: "inbound";
}
async function submitMessage(message) {
var params = {
number: message.ORIGINATOR,
message: message.MESSAGE,
smssize: message.MESSAGE.length
};
await messagingApi.replyToCustomer(params).then((res) => {
if (res.data[0].RVALUE === "200") {
let extendedMsg = [...messages, message];
let extendedConversation = [...conversation, message];
setConversation([...extendedConversation]);
setMessages(orderMessagesByDate([...extendedMsg]));
}
});
}
function orderMessagesByDate(list) {
return list.sort(function(x, y) {
return new Date(y.DATE_SORT) - new Date(x.DATE_SORT);
});
}
const modalHandler = () => {
setIsOpen(!isOpen);
};
let chatConfig = {
channelSwitch: true,
channels: channels,
templateModal: true,
templates: templates,
advisorModal: true,
advisors: advisors,
};
const onActiveChatChange = (message) => {
history.push("/chats/" + match.params.area + "/" + message.ORIGINATOR);
const params = {
MobileNumber: message.ORIGINATOR,
};
messagingApi.conversationHistory(params).then((conversationData) => {
setConversation(
conversationData.data.map((message) => ({
DATE_SORT: message.DATE_SORT,
UNIQUEID: message.UNIQUEID,
ORIGINATOR: message.ORIGINATOR,
MESSAGE: message.MESSAGE,
ASSIGNED_TO: message.ASSIGNED_TO || null,
}))
);
});
};
return (
<div data-test="component">
<BodyHeader
text={headerText}
children={
<FontAwesomeIcon
icon="plus-square"
aria-hidden="true"
size="2x"
onClick={modalHandler}
/>
}
/>
{messages && chatToLoad ? (
<>
<ChatWindow
messages={messages}
conversation={conversation}
chatToLoad={chatToLoad}
onActiveChatChange={onActiveChatChange}
classifyMessage={classifyMessage}
submitMessage={submitMessage}
config={chatConfig}
/>
<SendMessageModal isOpen={isOpen} toggle={modalHandler} />
</>
) : !messages ? (
<Spinner />
) : (
<NoDataHeader>There are no chats in this area</NoDataHeader>
)}
    
</div>
);
}
You can't get what you want this way. A state change applied in a useEffect won't have effect until the next rendering cycle, the following callbacks will still see the current const value.
If you want to change the value in the current rendering cycle the only option you have is to relax your const into let and set the variables yourself.
After all: you were expecting a const to change isn't it? ;)

How to return data using ES6 method?

I was trying to return data using ES6 function , but it is returning the function instead of result.
The result should be true or false as per my code
My Code
get_gathereddata_status.js
export default () => (dispatch, getState) => {
const { experiment } = getState();
const { selectedTab, gatherData } = experiment.tabs;
const { environmentalChanges: { environmentFactor, environmentLocation } } = experiment;
const { populationChanges: { populationlLocation, populationFactor } } = experiment;
if (selectedTab === 'tab1') {
return environmentFactor !== '' && environmentLocation !== '' && !gatherData[selectedTab];
} else if (selectedTab === 'tab2') {
return populationlLocation !== '' && populationFactor !== '' && !gatherData[selectedTab];
}
return false;
};
mapStateToProps
function mapStateToProps({ experiment }) {
const { selectedTab } = experiment.tabs;
const isGatherDataEnabled = gatherDataStatus();
console.log(isGatherDataEnabled);
return {
selectedTab,
isGatherDataEnabled
};
}
console.log in the mapStateToProps
ƒ (dispatch, getState) {
var _getState = getState(),
experiment = _getState.experiment;
var _experiment$tabs = experiment.tabs,
selectedTab = _experiment$tabs.selectedTab,
get_gathereddata_status.js is returning a function that returns another function:
() => (dispatch, getState) => { //...}
So when you call it with:
const isGatherDataEnabled = gatherDataStatus();
// assuming gatherDataStatus this is the same as get_gathereddata_status.js
isGatherDataEnabled is now the function returned by gatherDataStatus() not the result of your second function.
I think you just want to export the second function:
export default (dispatch, getState) => { //.. }
Alternatively you could call the returned function if you really need it:
console.log(isGatherDataEnabled(dispatch, getState));
// You need to call this with the expected arguments. Where do these come from?

how to prevent repetitive shuffle of my array using onClick

I need your fresh eyes to help me.
I have a set of answers in my array which I shuffle on the first render.
My problem here, is that I know if i am clicking on one of the answer, the setState will re-render and consequently re-shuffle my array which i dont want.
You can have a look at my code below:
export default class extends React.Component {
constructor(props) {
super(props)
this.state = {
user: this.props.user,
token: this.props.token,
data: this.props.data,
count: 0,
select: undefined
}
this.changeQuestion = this.changeQuestion.bind(this);
this.onCorrect = this.onCorrect.bind(this);
this.onFalse = this.onFalse.bind(this);
}
static async getInitialProps({req, query}) {
const id = query.id;
const authProps = await getAuthProps(req, 'Country/Questions?theory=' + id)
return authProps
}
componentDidMount() {
if (this.state.user === undefined) {
Router.push('/login')
}
}
changeQuestion() {
this.setState({
count: this.state.count + 1,
select: undefined
})
}
onCorrect() {
this.setState({
select: true
})
}
onFalse() {
this.setState({
select: true
})
}
mixAnswers() {
const answer = this.props.data.Properties.Elements
const answers = answer[this.state.count].Properties.Answers
const answersObj = answers.reduce((ac, el, i) => {
ac.push(
<p key={i} onClick={i === 0
? this.onCorrect
: this.onFalse} className={i === 0
? 'exercices__answers--correct'
: 'exercices__answers--false'}>{el}</p>
)
return ac
}, [])
const answersShuffled = answersObj.sort(() => 0.5 - Math.random())
return answersShuffled;
}
render() {
const {user, token, data} = this.state
const answer = this.props.data.Properties.Elements
const answers = answer[this.state.count].Properties.Answers
return (
<div>
{user !== undefined
? <Layout user={this.state.user}>
<div>
{answer[this.state.count].Properties.Sources !== undefined
? <img src={answer[this.state.count].Properties.Sources[0].URL}/>
: ''}
<h1>{answer[this.state.count].Properties.Question}</h1>
{this.mixAnswers().map((el, i) => <p key={i} onClick={el.props.onClick} className={this.state.select !== undefined
? el.props.className
: ''}>{el.props.children}</p>)
}
<p>{answer[this.state.count].Properties.Description}</p>
</div>
<button onClick={this.changeQuestion}>Next Question</button>
</Layout>
: <h1>Loading...</h1>}
</div>
)
}
}
Obviously, the way I am using the 'this.mixAnswers()' method is the issue. How can I prevent it to re-render then re-shuffle this array of questions.
PS: dont pay attention about onCorrect() and onFalse().
You should make sure the logic that shuffle the answers is called only once, you can get this behavior on ComponentWillMount or ComponentDidMount, then you save them in the state of the component and in the render function instead of
{this.mixAnswers().map((el, i) => <p key={i} onClick={el.props.onClick} className={this.state.select !== undefined
? el.props.className
: ''}>{el.props.children}</p>)
}
You use this.state.answers.map()...

How implement toggle with Rxjs

I am learning rxjs. I create decorator "toggleable" for Dropdown component. All work fine, but I don't like it. How can I remove condition "toggle/hide".
Uses rxjs, react.js, recompose.
It's toogleable decorator for Dropdown component.
export const toggleable = Wrapped => componentFromStream((props$) => {
// toogleHandler called with onClick
const { handler: toogleHandler, stream: toogle$ } = createEventHandler();
// hideHandler called with code below
const { handler: hideHandler, stream: hide$ } = createEventHandler();
const show$ = Observable.merge(
toogle$.mapTo('toogle'),
hide$.mapTo('hide'))
.startWith(false)
.scan((state, type) => {
if (type === 'toogle') {
return !state;
}
if (type === 'hide') {
return false;
}
return state;
});
return props$
.combineLatest(
show$,
(props, show) => (
<Wrapped
{...props}
show={show}
onToggle={toogleHandler}
onHide={hideHandler}
/>
));
});
It's decorator for Dropdown button
// hideHandler caller
class Foo extends Component {
constructor(props) {
super(props);
this.refButton.bind(this);
this.documentClick$ = Observable.fromEvent(global.document, 'click')
.filter(event => this.button !== event.target)
.do((event) => { this.props.onHide(event); });
}
componentDidMount() {
this.documentClick$.subscribe();
}
componentWillUnmount() {
this.documentClick$.unsubscribe();
}
refButton = (ref) => {
this.button = ref;
}
}
You can implement show$ with no conditions by mapping the toggle$/hide$ to functions on the previous state:
const show$ = Observable.merge(
toggle$.mapTo(prev => !prev),
hide$.mapTo(prev => false))
.startWith(false)
.scan((state, changeState) => changeState(state));
Another improvement you can do is with your toggleable implementation. Instead of using recompose componentFromStream, you can use recompose mapPropsStream:
export const toggleable = mapPropsStream(props$ => {
const { handler: toogleHandler, stream: toogle$ } = createEventHandler();
const { handler: hideHandler, stream: hide$ } = createEventHandler();
const show$ = Observable.merge(
toggle$.map(() => prev => !prev),
hide$.map(() => prev => false))
.startWith(false)
.scan((state, changeState) => changeState(state));
return props$
.combineLatest(
show$,
(props, show) => ({
...props,
show
onToggle: toogleHandler
onHide: hideHandler
})
);
});

Categories