I have an input form and onSubmit, the input value will be rendered into a Checkbox. The data is being stored with MongoDB - I can see the input data I've typed using Robo 3T, so I know that part is working. However, the array is empty in the console and not being added to the Checkbox.
export const QUERY_ALL_CHORES = gql`
query chores {
chores {
_id
choreBody
}
}
`;
resolvers.js
addChore: async (parent, args, context) => {
// return console.log("chores: ", args.choreBody);
if (context.user) {
const chore = await Chore.create({
...args,
choreBody: args.choreBody,
});
await User.findByIdAndUpdate(
{ _id: context.user._id },
{ $push: { chores: chore._id } },
{ new: true }
);
return chore;
}
},
Then here is my AddChoreForm.js
export default function AddChore(props) {
const [choreBody, setBody] = useState("");
const [addChore] = useMutation(ADD_CHORE, {
update(cache, { data: { addChore } }) {
try {
const { chores } = cache.readQuery({ query: QUERY_ALL_CHORES });
cache.writeQuery({
query: QUERY_ALL_CHORES,
data: { chores: [addChore, ...chores] },
});
} catch (e) {
console.log(e);
}
// append new chore to the end of the array
const { me } = cache.readQuery({ query: QUERY_ME });
cache.writeQuery({
query: QUERY_ME,
data: { me: { ...me, chores: [...me.chores, addChore] } },
});
},
});
const handleFormSubmit = async (event) => {
event.preventDefault();
try {
// add chore to database
await addChore({
variables: { choreBody },
});
// clear form value
setBody("");
} catch (e) {
console.log(e);
}
};
return (
<Container>
<Form onSubmit={handleFormSubmit}>
<Form.TextArea
onChange={(event) => setBody(event.target.value)}
/>
<Button>
Add Chore
</Button>
</Form>
</Container>
);
}
Then the input data should be put into a Checkbox here, but when I check the console, the array is empty.
export default function ChoreList({ chores }) {
// get chore data
const { choreData } = useQuery(QUERY_ALL_CHORES);
const chores = choreData?.chores || [];
console.log("Chores: ", chores);
return (
<>
<Container chores={chores} >
{chores &&
chores.map((chore) => (
<div key={chore._id}>
<Form>
<Form.Field>
<List>
<List.Item>
<List.Content>
{/* {chore.choreBody} */}
<Checkbox label={chore.choreBody} />
</List.Content>
</List.Item>
</List>
</Form.Field>
</Form>
</div>
))}
</Container>
</>
);
}
Try passing the fetchPolicy: 'network-only' option with your useQuery hook to force fetch from DB rather than apollo cache.
From the docs:
By default your component will try to read from the cache first, and if the full data for your query is in the cache then Apollo simply returns the data from the cache.
Related
So I'm trying to get the userId from another queried looped map's id.
The error is:
Error: Rendered more hooks than during the previous render.
The error started showing when I added this inside the map:
const { data: { getUser } = {} } = useQuery(FETCH_USER_QUERY, {
variables: {
userId: something?.id,
},
});
on my component... This is my full component code:
export default function SomethingComponent() {
const { data: { getSomething } = {} } = useQuery(
FETCH_SOMETHING_QUERY
);
return (
<>
{getSomething?.map((something) => {
const { data: { getUser } = {} } = useQuery(FETCH_USER_QUERY, {
variables: {
userId: something?.id,
},
});
return (
<div>
<h1>{getUser.name}</h1>
{/* ... */}
{/* ... */}
{/* ... */}
{/* ... */}
{/* ... */}
{/* ... */}
</div>
);
})}
</>
);
}
And this is for the Query of Something:
const FETCH_SOMETHING_QUERY = gql`
{
getSomething {
id
}
}
`;
And for the query of the user:
const FETCH_USER_QUERY = gql`
query ($userId: ID!) {
getUser(userId: $userId) {
# ...
}
}
`;
Ive tried thinking on how to fix this myself but i dont know any other way to get the something.id without going inside the looped map. So i tried searching for the same error and they are about the hooks in the wrong order or place.
What you did was breaking the rules of hooks.
You'll need to utilize useApolloClient hook in order to manually execute the queries.
However, since we need to get the users individually, we can approach two ways for this.
The first way is to get the initial data first, then put it in useEffect hook with client extracted from useLazyQuery and then set the state one by one (which I think is a bit complicated):
The code below is just an idea. I won't guarantee that it will work if you copy+paste it.
// your previous code here
const [users, setUsers] = useState([])
const {
client
} = useApolloClient()
useEffect(() => {
if (getSomething.length > 0) {
getSomething.forEach(async({
id: userId
}) => {
const {
data: newUsers
} = await client.query({
query: FETCH_USER_QUERY,
variables: {
userId,
},
})
setUsers([...users, ...newUsers])
})
}
}, [getSomething])
2nd approach is to breakdown the component into smaller one with a fetch logic inside it.
export default function SomethingComponent() {
const { data: { getSomething } = {} } = useQuery(
FETCH_SOMETHING_QUERY
);
return (
<>
{getSometing.map((user) => <UserComponent userId={user.id} />)}
</>
);
}
// UserComponent.js
export default function UserComponent({ userId }) {
const { data: { user } = {} } = useQuery(
FETCH_USER_QUERY, { variables: { userId } }
);
return (
<>
{user?.name}
</>
);
}
Im tryin to get my queries to update using the subscribeToMore function. Im getting lost on why the code is a failing to execute. so far all operations query, mutation and subscribe will work independently.
FRONTEND
export const QUERY_MESSAGES = gql`
query Messages($convoId: ID!) {
messages(convoId: $convoId) {
text
senderId
convoId
createdAt
}
}
`;
export const SUBSCRIBE_MESSAGES = gql`
subscription Messages($convoId: ID!) {
messages(convoID: $convoID) {
text
convoId
}
}
`;
const ActiveChat = ({ currentConvo, me }: any) => {
const { subscribeToMore, data } = useQuery(QUERY_MESSAGES, {
variables: {
convoId: currentConvo,
},
});
return (
<section className="chat-wrapper">
<div className="messages-container">
<Messages
messages={data.messages}
me={me}
subscribeToMessages={() => {
subscribeToMore({
document: SUBSCRIBE_MESSAGES,
variables: { convoId: currentConvo },
updateQuery: (prev, { subscriptionData }) => {
console.log("hit");
if (!subscriptionData.data) return prev;
const newFeedItem = subscriptionData.data.messages;
return Object.assign({}, prev, {
messages: [newFeedItem, ...prev.messages],
});
},
});
}}
/>
<Input currentConvo={currentConvo} />
</div>
</section>
);
};
const Messages = ({ messages, me, subscribeToMessages }: any) => {
useEffect(() => {
subscribeToMessages();
console.log("use effect ran");
});
const formatTime = (time: number) =>
new Date(time * 1000).toLocaleTimeString();
console.log(messages);
return (
messages && (
<div>
{messages.map((message: any, i: number) => {
return message.senderId === me?._id ? (
<SenderBubble
key={i}
text={message.text}
time={message.createdAt}
formatTime={formatTime}
/>
) : (
<OtherUserBubble
key={i}
text={message.text}
time={message.createdAt}
formatTime={formatTime}
/>
);
})}
</div>
)
);
};
BACKEND
const pubsub = new PubSub();
Query: {
messages: async (parent, { convoId }, context) => {
if (context.user) {
return Message.find({ convoId }).sort({ createdAt: "asc" });
}
},
},
Mutation: {
/// creates a new message using sender id and emits MESSAGE_SENT event
sendMessage: async (parent, { text, convoId }, context) => {
pubsub.publish("MESSAGE_SENT", {
message: { text, convoId },
});
return Message.create({ text, convoId, senderId: context.user._id });
},
},
Subscription: {
userActive: {
subscribe: () => pubsub.asyncIterator("ACTIVE_USERS"),
},
message: {
subscribe: withFilter(
() => pubsub.asyncIterator("MESSAGE_SENT"),
(payload, variables) => {
return payload.message.convoId === variables.convoId;
}
),
},
},
tdlr: I think subscribeToMore.updateQuery isn't firing but i don't how to debug
SOLVED: turns out I placed the wrong url and port number while building the client so the ws wasn't connecting.
I'm trying to fetch data from graphQL, and I know that by putting function into the react UseEffect(), I would be able to call the function once the data is updated and constructed.
However, I'm working on a chatroom, and the data does not appear on the screen:
import {
CREATE_MESSAGE_MUTATION,
MESSAGES_QUERY,
MESSAGE_SUBSCRIPTION,
} from "../graphql";
import { useQuery, useSubscription } from "#apollo/client";
import React, { useEffect } from "react";
import { Tag } from "antd";
const ChatBox = ({ me, friend, ...props }) => {
//me friend are strings
const chatBoxName = [me, friend].sort().join("_");
const { loading, error, data, subscribeToMore } = useQuery(MESSAGES_QUERY, {
variables: { name: chatBoxName },
});
useEffect(() => {
try {
subscribeToMore({
document: MESSAGE_SUBSCRIPTION,
variables: { name: chatBoxName },
updateQuery: (prev, { subscriptionData }) => {
if (!subscriptionData.data) return prev;
const newMessage = subscriptionData.data;
console.log("Subscribing more data: ", newMessage);
},
});
} catch (e) {
console.log("Error in subscription:", e);
}
}, [subscribeToMore]);
if (loading) return <p>loading ...</p>;
if (error) return <p>Error in frontend chatbox: {error}</p>;
return (
<div className="App-messages">
{console.log(data.chatboxs[0].messages)}
{data.chatboxs[0].messages.map(({ sender: { name }, body }) => {
<p className="App-message">
<Tag color="blue">{name}</Tag>
{body}
</p>;
})}
</div>
);
};
export default ChatBox;
After a small delay of loading ..., it turns to the <div className="App-messages"> with no messages inside. However, on the console I can clearly see the messages that I want to print.
What is the problem of the function in UseEffect()? I would be so appreciated if anyone can help .
{data.chatboxs[0].messages.map(({ sender: { name }, body }) => { // <- this part
<p className="App-message">
<Tag color="blue">{name}</Tag>
{body}
</p>;
})}
As a callback, you declared a function that does not return JSX elements.
Replace with this
{data.chatboxs[0].messages.map(({ sender: { name }, body }) => (
<p className="App-message">
<Tag color="blue">{name}</Tag>
{body}
</p>;
))}
In an effort to figure out the problem I explain in my (unanswered) question "How do I update a react-bootstrap-table2 cell value after it's edited so a button component in a different column has it?", I attempted to pass a function that returns the cell value into the button component:
class NominationQueueBootstrapTable extends Component {
...
getInitialBid = (row) => {
console.log('getInitialBid');
return this.state.data.find(r => r.rank === row.rank).initialBid;
}
render() {
const { auctionId } = this.props;
const { teamId } = this.props;
function buttonFormatter(cell, row) {
return (
<NominateButton
row={ row }
auctionId={ auctionId }
teamId={ teamId }
getInitialBid={ this.getInitialBid }
/>
);
}
...
My NominateButton component returns another button wrapper component that calls a mutator:
class NominateButton extends Component {
render() {
const { row } = this.props;
const { auctionId } = this.props;
const { teamId } = this.props;
const playerId = parseInt(this.props.row.player.id, 10);
return (
<Query
query={TEAM_NOMINATIONS_OPEN_QUERY}
variables={{ team_id: teamId }}>
{({ data, loading, error, subscribeToMore }) => {
if (loading) return <Loading />;
if (error) return <Error error={error} />;
return (
<NominateButtonMutator
auctionId={ auctionId }
teamId={ teamId }
playerId={ playerId }
row={ row }
nominationsOpen={ data.team.nominationsOpen }
subscribeToNominationsOpenChanges={ subscribeToMore }
getInitialBid={ this.props.getInitialBid }
/>
);
}}
</Query>
);
}
}
And because I need to invoke the mutator when the button is pressed, my onClick function first calls the getInitialBid function passed in as a property, and then invokes the mutator:
class NominateButtonMutator extends Component {
...
handleButtonPressed = (submitBid) => {
this.setState({bidAmount: this.props.getInitialBid(this.props.row)});
submitBid();
};
render() {
const { auctionId } = this.props;
const { teamId } = this.props;
const { playerId } = this.props;
const { nominationsOpen } = this.props;
return (
<Mutation
mutation={SUBMIT_BID_MUTATION}
variables={{
auction_id: auctionId,
team_id: teamId,
player_id: playerId,
bid_amount: this.state.bidAmount
}}
>
{(submitBid, { loading, error }) => (
<div>
<Error error={error} />
<Button
disabled={ loading || !nominationsOpen }
onClick={() => this.handleButtonPressed(submitBid) }
variant="outline-success">
Nominate
</Button>
</div>
)}
</Mutation>
);
}
}
(The onClick= code was updated from azium's comment.)
When I run this, I get:
"TypeError: this.props.getInitialBid is not a function"
Is this a workable strategy? Why isn't this.props.getInitialBid a function?
You are using the old function syntax, so this is not bound correctly.
change:
function buttonFormatter(cell, row) {
return (
<NominateButton
row={ row }
auctionId={ auctionId }
teamId={ teamId }
// scoped to your local function not your class
getInitialBid={ this.getInitialBid }
/>
);
}
to
const buttonFormatter = (cell, row) => {
return (
<NominateButton
row={ row }
auctionId={ auctionId }
teamId={ teamId }
// this is scoped "lexically" aka to your class
getInitialBid={ this.getInitialBid }
/>
);
}
I'm making a React app using openweathermap API. Right now I receive the list of weather data. I'm trying to highlight the weather if I click it.
To make this happen, I wrote on App.js to pass a prop to WeatherDetail.js, but so far seems like WeatherDetail.js doesn't recognize props from its parent.
class App extends React.Component {
constructor(props) {
super(props);
}
state = { forecasts: [], selectedWeather: null }
getWeather = async city => {
const response = await weather.get('/forecast', {
params: {
q: city
}
});
this.setState ({
forecasts: response.data.list,
city: response.data.city.name,
selectedWeather: response.data.list[0]
})
}
}
onWeatherSelectFunction = (item) => {
this.setState({ selectedWeather: item });
};
render() {
return (
<div>
<Form loadWeather={this.getWeather} />
<WeatherDetail itemToChild={this.state.selectedWeather} />
<WeatherList
onWeatherSelect={this.onWeatherSelectFunction}
weathers={this.state.forecasts}
city={this.state.city}
/>
</div>
);
}
}
export default App;
const WeatherDetail = ({forecasts, itemToChild}, props) => {
const weather = props.itemToChild;
if(!weather) {
return <div>Loading...</div>;
}
return <div>{weather.humidity}</div> <-- This doesn't appear on screen
);
}
const WeatherItem = ({item, onWeatherSelectFromList, humidity, city, temp }) => {
return (
<div>
<div onClick={() => onWeatherSelectFromList(item)} >
{city}<br /> <-- Appears on screen
{humidity}<br /> <-- Appears on screen
</div>
</div>
);
};
const WeatherList = ({weathers, onWeatherSelect, city}) => {
const renderedList = weathers.map((item) => {
return (
<div>
<WeatherItem
city={city}
temp={item.main.temp}
humidity={item.main.humidity}
temperature={item.weather.icon}
onWeatherSelectFromList={onWeatherSelect}
/>
</div>
);
});
return (
<div className="flex">
{renderedList}
</div>
);
}
class Form extends React.Component {
state = { term: '' };
onFormSubmit = (event) => {
event.preventDefault();
this.props.loadWeather(this.state.term);
}
render() {
return (
<div>
<form onSubmit={this.onFormSubmit}>
<input
ref="textInput"
type="text"
value={this.state.term}
onChange={event => this.setState({term: event.target.value})}
/>
<button>Get Weather</button>
</form>
</div>
);
}
}
How do I connect App.js and WeatherDetail.js using props?
In your App.js file you are passing only one props called itemToChild
<WeatherDetail itemToChild={this.state.selectedWeather} />
In your WeatherDetail file from where you're getting forecasts? do you get forecasts from redux store?
const WeatherDetail = ({forecasts, itemToChild}, props) => {
const weather = props.itemToChild;
if(!weather) {
return <div>Loading...</div>;
}
return <div>{weather.humidity}</div> <-- This doesn't appear on screen
);
}
change your code with this.
const WeatherDetail = (props) => {
console.log("props.itemToChild", props.itemToChild) // check this console that do you get data as you want.
const weather = props.itemToChild;
if(!weather) {
return <div>Loading...</div>;
}
return <div>{weather.humidity}</div> <-- This doesn't appear on screen
);
}
You have already destructured the props so there is no need to mention props in WeatherDetail component
and also there is an extra parenthesis after the return statement you should remove that also...
Old:
const WeatherDetail = ({forecasts, itemToChild}, props) => {
const weather = props.itemToChild;
if(!weather) {
return <div>Loading...</div>;
}
return <div>{weather.humidity}</div> <-- This doesn't appear on screen
);
}
New:
const WeatherDetail = ({ forecasts, itemToChild }) => {
const weather = itemToChild;
if (!weather) {
return <div>Loading...</div>;
}
return <div>{weather.humidity}</div>;
};